Sessions and turns
A session is one durable conversation with the agent, identified by a
UUIDv7 sessionId that is simultaneously the wire identity, the runtime
stream id, and the on-disk directory name. A turn is one foreground run
of the agent inside a session. This page covers how a client gets a host,
opens a connection, and drives turns.
The process model: one host, one lease, one connection
Section titled “The process model: one host, one lease, one connection”A host is one muse serve process holding zero or more loaded sessions;
every loaded session is exclusively owned by exactly one host at a time,
enforced by an on-disk writer lease that the OS releases if the host dies. A
v1 host accepts exactly one client connection, and a second connection
attempt is refused.
In v1 the only out-of-process transport is stdio: the client spawns the host, and the connection lifetime is the process lifetime. Closing stdin triggers an orderly drain — any active run is cancelled with a durable cancelled terminal, a durable session-end record is written, leases are released, and the process exits; killing the process instead writes nothing, by design, and the next load of the log backstops the gap. Because the spawner is the client, the spawn boundary is also the security boundary: a host must not accept a connection from a party that did not spawn it.
Two separately spawned hosts can still contend for the same session on disk;
the loser gets the typed
sessionInUse error
rather than a generic failure.
The connection lifecycle
Section titled “The connection lifecycle”Every connection must complete the
initialize handshake before
any other method is accepted; a request before that fails with a
not-initialized error. The handshake fixes per-connection state: client
identity, capabilities,
experimental gating, and notification opt-outs.
After the handshake, the connection’s subscription set decides which
sessions stream view events to it. Starting
(session/start), resuming
(session/resume), or
forking (session/fork) a
session auto-subscribes the calling connection; nothing else subscribes you.
The server announces newly loaded sessions to all connections, and announces
unloads with a final cursor you can later resume from.
A loaded session unloads when it has had zero subscribers, no active or pending run, and no pending approval or user-input prompt for the idle window. A session parked on an unanswered approval is never idle-unloaded.
The Session object, at concept level
Section titled “The Session object, at concept level”Every lifecycle method returns or lists one shape: the Session object. Its concept-level fields:
sessionIdandpath— identity and the absolute path of the durable log; under the ephemeral profile the path is the empty string, the one value a client must never hand to a filesystem call.status— not loaded, idle, or running; a host only knows its own load state, so foreign ownership is detected by thesessionInUseerror, never by status.activeTurnId— the current foreground turn when running, so a rejoining client can steer or interrupt precisely without folding history first.approvalMode— the folded effective approval mode, the read path for the mode so a client never needs a mode-change command to learn it.- Unknown fields must be ignored: the object evolves additively.
Turn lifecycle through the command plane
Section titled “Turn lifecycle through the command plane”The command plane is deliberately thin: a command’s response tells you only
what the runtime decided at admission, and everything that actually happened
arrives on the session view as cursor-bearing events. Submitting user input
is turn/start; when the
session is busy, an ifBusy
disposition chooses queueing (the default), steering the running turn, or
replacing it. The ack’s
disposition field tells
you which happened without folding history.
A turn actually begins when
turn/started folds —
fresh submits immediately, queued submits at their launch boundary, and
steered submits never, because a steer joins the running turn instead of
minting a new one.
What turn terminal states mean
Section titled “What turn terminal states mean”A turn is over when its
turn/completed view
event folds, carrying one of three
terminals:
- completed — the run finished normally.
- failed — the run ended in a run-fatal error; the event carries a
turnErrorobject with a kind, a human message, and the server’s judgment on whether resubmitting the same input may succeed. - cancelled — the terminal for both stop lanes
(
turn/interruptandturn/cancel) and for host-shutdown cancellation.
Acceptance of a stop command means the stop was admitted, not that the turn is already stopped: the turn is over when you fold its cancelled terminal. Mid-turn failures reach the client as this terminal event, never as a JSON-RPC error.
Two edge exits matter for anyone awaiting a turn. A queued turn can be
reclaimed before it ever runs, in which case
turn/unqueued is its
terminal event and no turn/completed will ever arrive — a turn-wait that
folds only turn/completed hangs forever. And a queued or replace follow-up
whose launch fails at the terminal boundary terminates with a failed
turn/completed and no preceding turn/started.
The stop gesture is scoped to the active turn only: interrupting does not
consume an already-queued successor, which may still launch once the
interrupted turn folds its terminal; un-sending a queued turn is
turn/unqueue.
Two stop lanes
Section titled “Two stop lanes”turn/interrupt is the
user’s stop gesture: it takes the runtime’s priority lane and can pair a
retract intent that durably restores the prompt to the composer when no
assistant output committed. turn/cancel
is plain programmatic cancellation with no priority intent and no retract
pairing. Both fold the same cancelled terminal. Background tasks started by
the turn are not killed by either lane; they have their own stop verbs.
How the streamed item events between turn/started and turn/completed fold
into client state is covered by the fold model.