Sessions
Agent owns configuration and provider state. Session binds that agent to one workspace and one conversation lifecycle.
This is where UI integration usually begins: subscribe to AgentEvent values
from the session and map them to progress, tool calls, confirmations, and results.
Rust Construction
Rust session construction is async-first because default memory, file-backed stores, queues, trajectory recording, and MCP discovery can require I/O:
session_async, resume_session_async, session_for_agent_async, and
session_for_worker_async are direct async alternatives. Node and Python keep
their existing factory names and delegate to this async construction kernel
inside the native binding.
The synchronous Rust Agent::session factory is a strict compatibility path.
It accepts explicitly pre-initialized resources and never starts or blocks an
async runtime. A default/file memory store, file session store, queue,
trajectory recorder, or any host MCP manager supplied in SessionOptions
returns CodeError::AsyncSessionBuildRequired; session-option MCP capability
discovery is always async. Use the builder instead of catching that error and
silently changing backends. The sync path can only inherit agent-global MCP
tools already cached during agent initialization.
planningMode is explicit: use 'auto' for the default structured pre-analysis
path, 'enabled' to force planning, and 'disabled' to turn planning off for
latency-sensitive calls. The legacy boolean planning option remains available
for compatibility.
Planning emits run-scoped state. Hosts can render that state as a TaskList and update each item as the runtime records progress instead of trying to infer progress from text tokens.
Single-Flight Operations
A session admits only one transcript-affecting operation at a time. send,
stream, their attachment variants, slash commands, and resumeRun share the
same fail-fast admission gate. An overlapping call returns
CodeError::SessionBusy before reading history or dispatching a command; it is
not queued behind the active operation.
Wait for the active result, consume the stream to completion, or cancel it before starting another conversation operation. The runtime retains a stream's lease until its producer has actually stopped, even if the public stream handle is dropped or aborted. Direct host tool helpers do not mutate the transcript and do not claim this lease.
Node and Python stream iterators wait for that lifecycle cleanup at the terminal boundary. Once a fully consumed iterator reports completion, an immediate next conversation operation will not inherit a stale busy state from that stream.
Send
Stream
Every SDK event is an EventEnvelopeV1 projection with version === 1, an open
type string, complete payload, and optional metadata. Convenience fields
such as text and toolName are derived from that envelope. Keep a default
branch and retain the payload for future event types.
Side Questions
The SDK does not expose a dedicated ephemeral-question helper. Snapshot the
current history and pass it explicitly to send or stream. Explicit history
is used for that call only; it does not append the answer back into the session
history.
Resume
Set autoSave: true or call await session.save() when using a session store.
This resumes a saved session snapshot; interrupted run checkpoints use
session.resumeRun(runId) instead. See Persistence.
Lifecycle and Close
session.close() is a full graceful stop. The first call flips the session
into the closed state — further send/stream calls fast-fail with
CodeError::SessionClosed instead of starting a new run — then cancels the
active run, every in-flight delegated subagent task, and all pending
human-in-the-loop confirmations. Subsequent calls are no-ops and never panic.
Check the closed state with session.isClosed() (Node) / session.is_closed()
(Python).
Cancellation token
Every run derives its per-operation cancellation token from a single
session-level parent via child_token(), so close() cascades to all
in-flight work in one shot. Embedders that need the raw token — for example to
wire it into a host-side select! or to abort the session without the
run-store and hook side effects of close() — can clone it through
AgentSession::session_cancel_token().
Agent-side registry
The owning Agent tracks its live sessions by Weak reference (pruned
lazily) so a control plane can drive lifecycle without holding a session
handle:
Agent::list_sessions()returns the live session IDs (sorted, stable).Agent::close_session(id)closes one session by ID — the same cleanup asAgentSession::close(), invoked out-of-band.Agent::close()closes every live session and tears down agent-owned background resources (it also disconnects the global MCP connections). After it returns, newsession/resumeSessioncalls fail fast withCodeError::SessionClosed.Agent::is_closed()reports whether the agent itself has been closed.
See CHANGELOG [3.3.0] — "Session / Agent lifecycle control".
Host Identity Labels
SessionOptions carries four opaque identity fields that the host can attach
at session creation. The framework only transports them — it never interprets
or enforces them. They are propagated into SessionData, hooks, and traces,
and restored on resume, so the host can drive multi-tenant aggregation,
billing, and distributed tracing:
See CHANGELOG [3.3.0] — "Host-provided identity labels".
Run Replay
Each send() or stream() creates a run record. Applications can inspect those
records for UI state, audit, replay, cancellation, and tests:
currentRun() is for the operation that is current at the time of the call.
During an active send() or stream(), pass its id to cancelRun(id) to
request cancellation. When idle, currentRun() may return null or a retained run
snapshot depending on the preceding control flow, so use runs() for completed
history and inspect status before treating a snapshot as cancellable:
Agent Definitions
sessionForAgent() applies a named agent definition from built-in agents, .a3s/agents, or configured agentDirs.