Session lifecycle
How CompozyOS creates, activates, stops, and classifies one durable runtime session.
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…
| State | Meaning | Valid live transition |
|---|---|---|
starting | A prompt to a stopped session is restoring the ACP process and durable provider history. | starting -> active |
active | CompozyOS 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 |
stopping | CompozyOS has accepted a stop request and is draining the session toward a stopped state. | stopping -> stopped |
stopped | Final 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-reviewCreate 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 IDworkspace_path: an absolute filesystem path
Behind that request, CompozyOS:
- Resolves the workspace and agent definition.
- Creates
~/.compozy/sessions/<session-id>/. - Opens
events.dbfor the session. - Persists the catalog row and session metadata.
- Activates the logical session with
runtime.status: "unbound". - Returns HTTP/UDS
201 Createdwithstate: "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>andparent_session_idonPOST /api/sessionslink 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 fastcurl -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_ididentifies the authored transcript row, whileidempotency_keyidentifies the submission. Keep both values and reuse them together only when retrying the exact same request.runtimeis a prompt-bound snapshot ofprovider,model,reasoning_effort, andspeed. 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_messageonly 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_aton real ACP events and metadata-only waiting heartbeats, then emits lower-frequencyruntime_progressandruntime_warningevents 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 byHEARTBEAT.mdwake decisions and thecompozy session health|status|inspectsurfaces.
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 jsonCompozyOS 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 maxSession 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.
| Mode | CLI | HTTP shape | Result |
|---|---|---|---|
queue | compozy session prompt <id> "<text>" --queue | POST /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. |
interrupt | compozy session prompt <id> "<text>" --interrupt --expected-turn-id turn_1234 | POST /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". |
steer | compozy session prompt <id> "<text>" --steer --expected-turn-id turn_1234 | POST /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_1234edit 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:
| Operation | Endpoint |
|---|---|
| List | GET /api/workspaces/:workspace/sessions/:id/prompt/queue |
| Edit | PUT /api/workspaces/:workspace/sessions/:id/prompt/queue/:queue_entry_id |
| Promote to steer | POST /api/workspaces/:workspace/sessions/:id/prompt/queue/:queue_entry_id/steer with expected_turn_id |
| Cancel | DELETE /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_1234The 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 allThe 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-1234Stop it over HTTP:
curl -X POST http://localhost:2123/api/workspaces/ws_alpha/sessions/sess-1234/stopThe stop path is cooperative first and forceful only when needed:
active -> stopping- wait for any in-flight prompt setup to finish
- send ACP
session/cancel - wait for the subprocess to exit
- escalate through the subprocess shutdown path if it does not exit cleanly
- classify the stop reason
- record a terminal
session_stoppedevent - close the recorder and write final metadata
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-1234The matching HTTP and UDS operation is:
curl -X POST http://localhost:2123/api/workspaces/ws_alpha/sessions/sess-1234/archiveArchive 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-1234The 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:
| Timeout | What it protects |
|---|---|
session.limits.timeout | Optional wall-clock session limit. 0s disables it. |
session.supervision.inactivity_timeout | Prompt inactivity limit. Long-running prompts remain healthy if activity continues. |
session.supervision.timeout_cancel_grace | Grace period after inactivity timeout cancel before CompozyOS stops the session as timeout. |
| ACP driver stop timeout | Subprocess shutdown escalation after cooperative stop. |
| Session lifecycle timeout | Recorder 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 reason | When CompozyOS uses it |
|---|---|
completed | The agent finished normally. |
user_canceled | A normal user stop request. |
max_iterations | A user stop request carried max_iterations detail. |
loop_detected | A user stop request carried loop_detected detail. |
budget_exceeded | A user stop request carried budget_exceeded detail. |
timeout | Runtime supervision detected inactivity, prompt cancel did not complete within the configured grace, and CompozyOS stopped the session. |
error | Start or stop failed, or the process exited unexpectedly without a crash wait error. |
agent_crashed | The subprocess exited with a wait error, or CompozyOS repaired stale active/stopping metadata after a daemon crash. |
hook_stopped | A required lifecycle hook denied continuation. |
shutdown | The daemon shut the session down. |
Inspect the current session state and stop classification at any time:
compozy session status sess-1234Removing persisted history
Remove a session only when its durable history is no longer needed:
compozy session remove sess-1234curl -X DELETE http://localhost:2123/api/workspaces/ws_alpha/sessions/sess-1234Removal 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 kindsummary: bounded redacted diagnostic textcrash_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 kind | Meaning |
|---|---|
startup_failure | CompozyOS could not finish preparing, launching, or initializing the provider. |
handshake_failure | The subprocess launched but ACP initialization did not complete. |
load_session_failure | An internal daemon-owned provider reload path failed. This is not resume attach. |
protocol_failure | ACP returned a structured protocol/request error outside a prompt-specific path. |
prompt_failure | ACP prompt submission or prompt streaming failed. |
cancellation | The operation was canceled by user action or context cancellation. |
permission_failure | A lifecycle hook or permission boundary denied continuation. |
process_exit | The provider subprocess exited unexpectedly or returned a wait error. |
transport_failure | The ACP stdio transport closed or failed while CompozyOS expected protocol traffic. |
timeout | Runtime supervision or lifecycle timeout expired. |
unknown_failure | CompozyOS 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
activebecomesstoppedwithagent_crashed,process_exit, and detaildaemon crashed while session active - stale
stoppingbecomesstoppedwithagent_crashed,process_exit, and detailstop did not complete - stale
startingbecomesstoppedwitherror,startup_failure, and detailstart 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 IDevents.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:
| Type | Meaning |
|---|---|
user | Normal interactive work created by a person or client. |
dream | Background memory consolidation work. |
system | Internal CompozyOS-managed work. |
coordinator | Managed autonomy coordinator for workspace-scoped coordinated task runs. |
spawned | Child 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.