Sessions
Creating, streaming, resuming, and saving workspace-bound sessions
Sessions
Agent owns configuration and provider state. Session binds that agent to one workspace and one conversation lifecycle.
import { Agent } from '@a3s-lab/code';
const agent = await Agent.create('agent.acl');
const session = agent.session('/repo', {
model: 'openai/MiniMax-M2.7-highspeed',
builtinSkills: true,
planningMode: 'enabled',
goalTracking: true,
autoDelegation: { enabled: true, maxTasks: 4 },
autoParallel: false,
});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.
Send
const result = await session.send('Review this repository and list release blockers');
console.log(result.text);
console.log(result.totalTokens);
console.log(result.verificationStatus);Stream
const stream = await session.stream('Run the focused tests and explain failures');
while (true) {
const { value: event, done } = await stream.next();
if (done) break;
if (!event) continue;
if (event.text) process.stdout.write(event.text);
if (event.toolName) console.log('tool:', event.toolName);
}Side Questions
btw() asks an ephemeral read-only side question. It snapshots the current history and does not append the answer back into the session.
const answer = await session.btw('What files has this session already inspected?');
console.log(answer.answer);Resume
import { Agent, FileSessionStore } from '@a3s-lab/code';
const agent = await Agent.create('agent.acl');
const session = agent.resumeSession('release-review', {
sessionStore: new FileSessionStore('./.a3s/sessions'),
});Set autoSave: true or call await session.save() when using a session store.
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).
await session.close();
if (session.isClosed()) {
// send/stream now reject with CodeError::SessionClosed
}session.close()
if session.is_closed():
# send/stream now reject with CodeError::SessionClosed
passCancellation 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.
const ids = await agent.listSessions();
await agent.closeSession(ids[0]);
await agent.close(); // closes all remaining sessions + global MCP
console.log(agent.isClosed());ids = agent.list_sessions()
agent.close_session(ids[0])
agent.close() # closes all remaining sessions + global MCP
print(agent.is_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:
| Node (camelCase) | Python (snake_case) | Meaning |
|---|---|---|
tenantId | tenant_id | Multi-tenant label |
principal | principal | User / service that triggered the session |
agentTemplateId | agent_template_id | Agent template / definition the session was instantiated from |
correlationId | correlation_id | Distributed-trace correlation id |
const session = agent.session('/repo', {
tenantId: 'acme-corp',
principal: 'user-42',
agentTemplateId: 'planner-v3',
correlationId: 'trace-deadbeef',
});opts = SessionOptions(
tenant_id='acme-corp',
principal='user-42',
agent_template_id='planner-v3',
correlation_id='trace-deadbeef',
)
session = agent.session('/repo', opts)
print(session.tenant_id, session.principal)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:
const runs = await session.runs();
const latest = runs.at(-1);
if (latest) {
console.log(await session.runSnapshot(latest.id));
console.log(await session.runEvents(latest.id));
}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:
const current = await session.currentRun();
if (current?.id && current.status === 'running') {
await session.cancelRun(current.id);
}Agent Definitions
sessionForAgent() applies a named agent definition from built-in agents, .a3s/agents, or configured agentDirs.
const session = agent.sessionForAgent('/repo', 'explore', ['./agents'], {
builtinSkills: true,
});