Skip to content

Session lifecycle

How CompozyOS creates, activates, stops, and classifies one durable runtime session.

For people running agent work7 pages in this section

A session is the runtime object CompozyOS manages. It ties together:

  • one logical agent session, which can bind an ACP-compatible agent subprocess when a prompt runs
  • one workspace boundary
  • one per-session SQLite event store
  • one permission policy
  • one durable CompozyOS session ID

That CompozyOS session ID identifies one durable lifecycle and its retained record.

State machine

Rendering diagram…

CompozyOS creates an active logical session without starting ACP. Its first prompt binds a runtime; stopping normally finalizes the session through `stopping`.
StateMeaningValid live transition
startingA prompt to a stopped session is restoring the ACP process and durable provider history.starting -> active
activeCompozyOS has durably allocated the session ID, prepared its storage, and accepts prompts. Before the first prompt, its runtime is unbound; after binding, it can emit ACP events.active -> stopping
stoppingCompozyOS has accepted a stop request and is draining the session toward a stopped state.stopping -> stopped
stoppedFinal metadata is written and the recorder is closed. Reads remain available. A normal user prompt can restore an eligible stopped user session; a terminal process_exit remains read-only history and must be forked into a new session.stopped -> starting

Creating a session

Create a new session from the CLI:

compozy session new \
  --agent general \
  --cwd "$PWD" \
  --name code-review

Create the same session over HTTP:

curl -X POST http://localhost:2123/api/sessions \
  -H "Content-Type: application/json" \
  -d '{
    "agent_name": "general",
    "name": "code-review",
    "workspace_path": "/absolute/path/to/repo"
  }'

name is optional for user sessions. With roles.auto_title.enabled = true, CompozyOS starts one bounded title pass after the first assistant response is persisted, then stores a successful title in session metadata and the catalog. An explicit name always wins any race. If generation is disabled or fails, the session remains unnamed; daemon-managed session types are never eligible. Configure the title pass agent, provider, model, reasoning, and fallback routes under [roles.auto_title].

The request must include exactly one of:

  • workspace: a registered workspace name or ID
  • workspace_path: an absolute filesystem path

Behind that request, CompozyOS:

  1. Resolves the workspace and agent definition.
  2. Creates ~/.compozy/sessions/<session-id>/.
  3. Opens events.db for the session.
  4. Persists the catalog row and session metadata.
  5. Activates the logical session with runtime.status: "unbound".
  6. Returns HTTP/UDS 201 Created with state: "active" and no ACP process or ACP session ID.

Creation never accepts a prompt or runtime selector. It only creates the durable logical session. The agent definition supplies the defaults that the first prompt can use, but CompozyOS does not launch ACP or negotiate provider options until that prompt arrives.

Creation provenance

A session created from inside another session records that origin in its lineage:

  • compozy__session_create (native tool) links the calling session automatically. The link is server-derived from the bound caller, never a tool input, and is recorded only when the new session lands in the caller's workspace.
  • compozy session new --parent <session-id> and parent_session_id on POST /api/sessions link explicitly. The parent must exist in the target workspace; it does not need to be active.
  • An HTTP/UDS create carrying validated agent identity headers and no explicit parent infers the caller's session as the parent.

Provenance sets lineage.parent_session_id, inherits root_session_id from the parent's tree, and computes spawn_depth server-side. The session stays type: "user" with no TTL, auto-stop, spawn budget, or permission narrowing — governed children come only from safe spawn. Query the hierarchy with the catalog filters parent=<id> (direct children) and root=<id> (the whole tree, root included), exposed as compozy session list --parent/--root and on compozy__session_list.

Running an active session

Once a session is active, a prompt supplies an immutable runtime snapshot. The first prompt binds the logical session to that runtime; later prompts can choose a different snapshot:

compozy session prompt sess-1234 "Review the current changes and report correctness risks." \
  --provider codex \
  --model gpt-5.6-sol \
  --reasoning-effort high \
  --speed fast
curl -N -X POST http://localhost:2123/api/workspaces/ws_alpha/sessions/sess-1234/prompt \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Review the current changes and report correctness risks.",
    "message_id": "msg_1234",
    "idempotency_key": "idem_1234",
    "runtime": {
      "provider": "codex",
      "model": "gpt-5.6-sol",
      "reasoning_effort": "high",
      "speed": "fast"
    }
  }'

Two operational details matter here:

  • message_id identifies the authored transcript row, while idempotency_key identifies the submission. Keep both values and reuse them together only when retrying the exact same request.
  • runtime is a prompt-bound snapshot of provider, model, reasoning_effort, and speed. The accepted snapshot is retained with the turn and is not rewritten by later runtime changes.
  • If a snapshot keeps the same provider, CompozyOS configures the existing ACP session in place when the provider exposes the needed live options. A provider or harness change replaces the process and replays the canonical stored transcript into the replacement ACP session.
  • CompozyOS records user_message only after runtime binding or reconfiguration succeeds. If binding, replacement, or its rollback fails, the authored message is not persisted as a user turn.
  • One session processes one prompt turn at a time. Stop logic waits for in-flight prompt setup to finish before it asks the ACP driver to stop the subprocess.
  • Long-running prompts stay healthy through runtime activity supervision. CompozyOS updates activity.last_activity_at on real ACP events and metadata-only waiting heartbeats, then emits lower-frequency runtime_progress and runtime_warning events for clients.
  • CompozyOS also maintains a separate metadata-only session health record (state, health, attachable, eligible_for_wake). Activity supervision feeds health, but the two are distinct authorities — supervision owns timers and persisted progress events, health is consumed by HEARTBEAT.md wake decisions and the compozy session health|status|inspect surfaces.

When the provider disconnects during a prompt

CompozyOS persists each accepted agent event before forwarding it. If the ACP subprocess disconnects after sending part of an answer, the stored assistant chunks remain in events and transcript reads. The prompt stream ends with an error instead of done, and the session stops with failure.kind: "process_exit". HTTP and UDS clients receive the terminal error frame because the response status cannot change after streaming has started.

compozy session prompt <id> "..." -o jsonl writes every frame it received, including the terminal error frame, then exits nonzero. The compozy__session_prompt native tool returns tool_backend_failed with reason backend_dead rather than reporting success. Inspect the session before continuing:

compozy session status sess-1234 -o json
compozy session events sess-1234 --last 20 -o json

CompozyOS does not automatically retry the interrupted prompt. Its tool or external effects may have completed before the disconnect. A later, explicit prompt restarts the stopped runtime under the same CompozyOS session ID and retained transcript. Send a new continuation only after deciding whether the interrupted work is safe to repeat.

Selecting the next runtime

Persist next-prompt runtime intent without starting or reconfiguring ACP:

compozy session runtime set sess-1234 \
  --provider claude \
  --model claude-fable-5 \
  --reasoning-effort max

Session reads expose runtime.selected, runtime.selection_revision, and runtime.effective. selected survives stops and daemon restarts; effective describes the runtime already applied to the current process. A normal prompt uses its explicit runtime first, then selected, then effective.

HTTP and UDS use PUT /api/workspaces/:workspace/sessions/:id/runtime with the complete runtime and current expected_revision. Clear the selection with DELETE /api/workspaces/:workspace/sessions/:id/runtime?expected_revision=<revision>. A stale revision returns 409; read the session again before deciding what to save.

Workspace knowledge on live turns

Put workspace-owned reference material under <workspace>/knowledge/ as Markdown files. Before an accepted user, Network, or synthetic turn reaches the agent, CompozyOS reopens that tree and adds a bounded <workspace-knowledge-snapshot> to the prompt. The snapshot carries the current file bytes, workspace-relative paths, a revision digest, and omission metadata when a safety limit is reached.

CompozyOS reads regular .md files only and does not follow symbolic links. The read stays inside the session workspace. A file change does not wake a session by itself; the next eligible turn receives the new snapshot. This includes task, task-creator, and Heartbeat wakes, so an active worker does not need a second operator prompt to observe a knowledge change.

The snapshot is prompt context, not durable CompozyOS memory. Use the memory system when the information must be curated, searched, or retained across workspaces and sessions.

Busy input modes

An active session still processes one prompt turn at a time, but CompozyOS accepts explicit busy-input modes so operators and agents do not have to guess what happens when a prompt is already running. session.busy_input.default_mode defaults to queue; session.busy_input.queue_cap bounds the persisted queue.

ModeCLIHTTP shapeResult
queuecompozy session prompt <id> "<text>" --queuePOST /api/workspaces/:workspace/sessions/:id/prompt with {"mode":"queue","message":"...","message_id":"msg_queue_1234","idempotency_key":"idem_queue_1234","runtime":{...}}Persists the input and runtime snapshot in FIFO order. The result reports status: "queued", delivery: "after_turn", queue ID, position, and generation.
interruptcompozy session prompt <id> "<text>" --interrupt --expected-turn-id turn_1234POST /api/workspaces/:workspace/sessions/:id/prompt with {"mode":"interrupt","expected_turn_id":"turn_1234","message":"...","message_id":"msg_interrupt_1234","idempotency_key":"idem_interrupt_1234","runtime":{...}}Requires the active-turn fence, advances the queue generation, and returns status: "interrupting" with delivery: "interrupt_then_prompt".
steercompozy session prompt <id> "<text>" --steer --expected-turn-id turn_1234POST /api/workspaces/:workspace/sessions/:id/steer with {"text":"...","message_id":"msg_steer_1234","idempotency_key":"idem_steer_1234","expected_turn_id":"turn_1234"}Requires the active-turn fence and returns the daemon's status and delivery decision.

The prompt response is authoritative. Use its status and delivery fields to decide what happened; transcript markers retain history but are not a second action result channel.

Managing queued input

Use the queue entry ID from a queued result or from session input list. The daemon owns the list, so read it again after each mutation instead of keeping a client-side queue.

compozy session input list sess-1234
compozy session input edit sess-1234 queue_entry_1234 "Run the focused test first."
compozy session input steer sess-1234 queue_entry_1234 "Prefer the smaller patch." \
  --expected-turn-id turn_1234
compozy session input cancel sess-1234 queue_entry_1234

edit and steer author a replacement message_id and idempotency_key; provide both original values together only to retry the exact same mutation. steer requires --expected-turn-id, which rejects a request that targets an older turn. HTTP and UDS expose the same daemon-owned operations:

OperationEndpoint
ListGET /api/workspaces/:workspace/sessions/:id/prompt/queue
EditPUT /api/workspaces/:workspace/sessions/:id/prompt/queue/:queue_entry_id
Promote to steerPOST /api/workspaces/:workspace/sessions/:id/prompt/queue/:queue_entry_id/steer with expected_turn_id
CancelDELETE /api/workspaces/:workspace/sessions/:id/prompt/queue/:queue_entry_id

Use POST /api/workspaces/:workspace/sessions/:id/prompt/cancel to stop the current turn without a replacement. Use prompt mode interrupt or steer with expected_turn_id when replacement text must run next. Every busy-input transition writes transcript marker evidence for recap, replay, and audit.

Queue and interrupt preserve the runtime snapshot accepted with their replacement input. A queued prompt therefore runs with the selected provider, model, reasoning effort, and speed even if a later prompt changes the session binding first.

Both interrupt and steer submit only the explicit replacement text. They never salvage, reuse, or combine the canceled authored prompt. The expected turn fence rejects stale actions before they can affect a newer turn.

Runtime activity supervision

Activity supervision is designed for prompts that may run for hours. It treats inactivity as the failure mode, not total elapsed time.

For each active prompt turn, CompozyOS tracks:

  • turn ID and source
  • turn start time
  • last activity time, kind, and detail
  • current tool and tool call ID when known
  • last progress notification time
  • idle and elapsed seconds

Short heartbeats keep last_activity_at fresh in session metadata and health output. They do not enter the persisted event stream. runtime_progress events are persisted only at session.supervision.progress_notify_interval, and runtime_warning is persisted once when session.supervision.inactivity_warning_after is crossed.

When session.supervision.inactivity_timeout is crossed, CompozyOS cancels the prompt cooperatively. If the prompt does not finish within session.supervision.timeout_cancel_grace, the session is stopped with stop reason timeout.

Rewinding a conversation

Rewind an idle user session when you want to retry from an earlier prompt without creating another CompozyOS session. The selected durable user message and every later active event are removed from the active transcript. CompozyOS returns the selected text as draft_text, keeps the same session ID, and starts a fresh ACP context from the retained prefix.

compozy session rewind sess-1234 \
  --message-id msg_1234

The CLI reads the current transcript fences immediately before the request. Scripts retrying one known request can pass all three --expected-* flags together with the original idempotency key. HTTP and UDS use POST /api/workspaces/:workspace_id/sessions/:session_id/rewind and require those same fences. An identical idempotency_key retry returns the original result. A stale fence, busy session, queued input, pending approval, or daemon-managed session returns 409 without cutting the transcript.

Rewind changes the conversation only. It does not undo file changes, tool effects, network actions, saved memory, or external provider actions. Removed events remain archived for audit:

compozy session events sess-1234 --archive archived
compozy session history sess-1234 --archive all

The Web action appears on durable user messages. It requires an empty composer, confirms the same side-effect boundary, reloads the daemon-owned transcript, and places draft_text in the composer.

Stopping a session

Stop a running session from the CLI:

compozy session stop sess-1234

Stop it over HTTP:

curl -X POST http://localhost:2123/api/workspaces/ws_alpha/sessions/sess-1234/stop

The stop path is cooperative first and forceful only when needed:

  1. active -> stopping
  2. wait for any in-flight prompt setup to finish
  3. send ACP session/cancel
  4. wait for the subprocess to exit
  5. escalate through the subprocess shutdown path if it does not exit cleanly
  6. classify the stop reason
  7. record a terminal session_stopped event
  8. close the recorder and write final metadata
  9. stopping -> stopped

Stopping preserves the session directory, catalog row, metadata, events, and selected runtime. It ends attach eligibility. A later normal prompt restores the provider history and continues under the same CompozyOS session ID; attach, queue, steer, interrupt, and other control operations do not restart it.

Archiving a stopped session

Archive a stopped session when it should leave the default catalog but its history must remain:

compozy session archive sess-1234

The matching HTTP and UDS operation is:

curl -X POST http://localhost:2123/api/workspaces/ws_alpha/sessions/sess-1234/archive

Archive is catalog metadata, separate from the session lifecycle state. Only a stopped session can be archived. Its metadata, events, ledger, runtime selection, and direct read operations remain available. Attach, prompt, and resume operations are rejected while it is archived.

Restore the session to the default catalog before restarting it:

compozy session unarchive sess-1234

The matching HTTP and UDS endpoint ends in /unarchive. Unarchiving preserves the stopped state, so a later normal prompt can restart the same logical session. Use removal only when the complete session record should be deleted.

Timeout behavior

CompozyOS has separate timeout concepts:

TimeoutWhat it protects
session.limits.timeoutOptional wall-clock session limit. 0s disables it.
session.supervision.inactivity_timeoutPrompt inactivity limit. Long-running prompts remain healthy if activity continues.
session.supervision.timeout_cancel_graceGrace period after inactivity timeout cancel before CompozyOS stops the session as timeout.
ACP driver stop timeoutSubprocess shutdown escalation after cooperative stop.
Session lifecycle timeoutRecorder close and final cleanup work during stop.

The important distinction is that inactivity timeout is not wall-clock timeout. A long prompt can run beyond inactivity_timeout as long as CompozyOS keeps observing real activity or controlled waiting heartbeats.

Stop reasons

The session manager currently emits these persisted stop classifications:

Stop reasonWhen CompozyOS uses it
completedThe agent finished normally.
user_canceledA normal user stop request.
max_iterationsA user stop request carried max_iterations detail.
loop_detectedA user stop request carried loop_detected detail.
budget_exceededA user stop request carried budget_exceeded detail.
timeoutRuntime supervision detected inactivity, prompt cancel did not complete within the configured grace, and CompozyOS stopped the session.
errorStart or stop failed, or the process exited unexpectedly without a crash wait error.
agent_crashedThe subprocess exited with a wait error, or CompozyOS repaired stale active/stopping metadata after a daemon crash.
hook_stoppedA required lifecycle hook denied continuation.
shutdownThe daemon shut the session down.

Inspect the current session state and stop classification at any time:

compozy session status sess-1234

Removing persisted history

Remove a session only when its durable history is no longer needed:

compozy session remove sess-1234
curl -X DELETE http://localhost:2123/api/workspaces/ws_alpha/sessions/sess-1234

Removal stops an active session first, deletes its durable catalog row and session directory, and emits a workspace-scoped catalog deletion signal. After a successful removal, later status or detail reads return session not found. If deletion fails before the directory is removed, CompozyOS preserves or restores catalog truth instead of reporting a partially deleted session.

Failure diagnostics

Stop reasons answer "why did the CompozyOS session stop?" Failure diagnostics answer "what kind of lifecycle failure did CompozyOS classify, and what evidence is available?" When a lifecycle failure occurs, CompozyOS persists a failure object with:

  • kind: stable machine-readable failure kind
  • summary: bounded redacted diagnostic text
  • crash_bundle_path: path to a redacted crash bundle when CompozyOS captured one

The same failure object is exposed through session status, session list JSON, session SSE terminal events, and compozy status.

compozy session status sess-1234 -o json | jq '.session.failure'

Current failure kinds are:

Failure kindMeaning
startup_failureCompozyOS could not finish preparing, launching, or initializing the provider.
handshake_failureThe subprocess launched but ACP initialization did not complete.
load_session_failureAn internal daemon-owned provider reload path failed. This is not resume attach.
protocol_failureACP returned a structured protocol/request error outside a prompt-specific path.
prompt_failureACP prompt submission or prompt streaming failed.
cancellationThe operation was canceled by user action or context cancellation.
permission_failureA lifecycle hook or permission boundary denied continuation.
process_exitThe provider subprocess exited unexpectedly or returned a wait error.
transport_failureThe ACP stdio transport closed or failed while CompozyOS expected protocol traffic.
timeoutRuntime supervision or lifecycle timeout expired.
unknown_failureCompozyOS had an error but no more specific kind was available.

Crash bundles are written under ~/.compozy/logs/crash-bundles/ by default. They are JSON documents with schema compozy.session_crash_bundle.v2, session identity, provider identity, failure kind, process metadata, error text, and captured stderr when available. Completed process metadata also includes exit_code and the terminating signal when the operating system exposes them. Bundle contents are bounded and redacted before they are written, and files are created with owner-only permissions.

Crash classification and transcript repair

CompozyOS repairs stale persisted metadata the next time it reads a session after an unclean daemon exit. That classification keeps status, list, history, and repair consistent after a crash:

  • stale active becomes stopped with agent_crashed, process_exit, and detail daemon crashed while session active
  • stale stopping becomes stopped with agent_crashed, process_exit, and detail stop did not complete
  • stale starting becomes stopped with error, startup_failure, and detail start did not complete

During daemon boot, CompozyOS also inspects stopped sessions whose stop reason is agent_crashed or error. If the final persisted turn was interrupted, CompozyOS appends repair events to terminalize the transcript: dangling tool calls receive interrupted tool results, then the turn receives a terminal error event. The repair is append-only; CompozyOS does not truncate, delete, or resequence session events.

Operators and agents can inspect or run the same append-only transcript repair explicitly:

compozy session repair <session-id> --dry-run
compozy session repair <session-id>

What gets persisted

Every session owns a directory under ~/.compozy/sessions/<session-id>/:

  • meta.json: durable session metadata such as state, workspace, session CWD, stop reason, failure diagnostics, and ACP session ID
  • events.db: persisted event, token usage, and hook-run history

That durable store supports live resume attach, event replay, deterministic recap, and approval audit. Attach requires a live, attachable session; the other reads remain useful after terminal stop.

Session types

CompozyOS records why a session exists:

TypeMeaning
userNormal interactive work created by a person or client.
dreamBackground memory consolidation work.
systemInternal CompozyOS-managed work.
coordinatorManaged autonomy coordinator for workspace-scoped coordinated task runs.
spawnedChild session created by an agent through safe spawn.

Dream sessions are special for permissions: they always start with approve-all.

Coordinator sessions are root lineage rows. They are created only after executable coordinated work is enqueued, not when a task is merely created. Spawned sessions record parent, root, depth, role, TTL, and permission metadata so CompozyOS can reap children when their TTL expires or their parent stops.

System sessions created for a Loop Goal record the session that started the Loop as creation provenance when one is available. That relationship groups the Goal under its origin in session views, but it does not make the Goal a safe-spawn child: it has no inherited spawn permissions, TTL, child cap, or parent-stop behavior. Deleting the origin keeps the Goal session and its recorded lineage; views show it as a root when the recorded parent is no longer available.

Next steps

  • Use Resume Attach and Replay when you need to attach to an eligible live session or reconstruct prior work from durable reads.
  • Use Event Streaming when you need the exact stored and streamed session record.
  • Use Runtime Autonomy for coordinator, task lease, and safe-spawn behavior.
  • Use Permissions when you need to control or approve agent actions.

On this page