Sessions
Persistent conversations, context, and session lifecycle.
Messages, tool calls, and metadata survive across requests. Each session is identified by an ID you provide — typically a chat or thread ID from your application.
Creating a Session
import { myAgent } from "@/agent";
const session = myAgent.session(chatId);session() is synchronous — it returns a handle bound to the given ID. No network call happens until you use the handle.
Sending Messages
await session.send("What files are in the project?");send() accepts a string, a UIMessage object, a parts array, or a structured input:
await session.send("Hello");
await session.send({
role: "user",
parts: [{ type: "text", text: "Hello" }],
});
await session.send({ type: "message", message: "Hello" });For approvals:
await session.send({
type: "approval",
approval: { approvalId: "abc123", approved: true },
});Send Options
Pass options as the second argument:
await session.send(message, {
interruptIfStreaming: true,
context: { authToken: req.headers.get("authorization") ?? "" },
});| Option | Type | Description |
|---|---|---|
interruptIfStreaming | boolean | { lastPart: { index, part } } | Interrupt the current response before sending. |
context | TContext | Transient context (validated by contextSchema). Not persisted. Passed to resolvable agent options. |
sandboxId | string | Override sandbox ID for this session. |
Runtime config like model, system prompt, and active tools are resolved from resolvable agent options each step, using the context you pass here.
SendResult
send() returns a SendResult:
const result = await session.send("Hello");
result.assistantMessageId; // ID of the assistant response message
result.done; // true or Promise<true>Context: Transient Per-Request Data
Context is not persisted. Use it for secrets like auth tokens, API keys, or request-scoped data. Define contextSchema on the agent:
export const myAgent = agent("my-agent", {
model: "anthropic/claude-sonnet-4-6",
contextSchema: z.object({
authToken: z.string(),
tenantId: z.string().optional(),
}),
});Pass context when sending:
await session.send("List my repos", {
context: { authToken: req.headers.get("authorization") ?? "" },
});Context is available to resolvable agent options (system, model, activeTools, etc.) and to custom tools via ToolContext. See Tools for details.
State: Mutable Per-Session Data
State is persisted across messages within a session. Use it for counters, accumulated results, or any data that tools should read and write over the lifetime of a conversation.
Define stateSchema on the agent:
export const myAgent = agent("my-agent", {
model: "anthropic/claude-sonnet-4-6",
stateSchema: z.object({
filesEdited: z.array(z.string()).default([]),
}),
});Tools receive mutable state:
import { tool } from "experimental-agent";
const TrackEdit = tool({
stateSchema: z.object({ filesEdited: z.array(z.string()).default([]) }),
inputSchema: z.object({ path: z.string() }),
execute: async ({ path }, { state }) => {
state.filesEdited.push(path);
return { tracked: state.filesEdited.length };
},
});Resolvable options (system, activeTools, etc.) can read state but not mutate it:
system: ({ state }) => `You have edited ${state.filesEdited.length} files so far.`,State is persisted to storage after each LLM step.
Context vs. State:
| Context | State | |
|---|---|---|
| Defined by | contextSchema | stateSchema |
| Passed via | session.send(input, { context }) | Automatic (from storage) |
| Persisted | No | Yes |
| Mutable by tools | No | Yes |
| Use case | Secrets, request-scoped data | Accumulated session data |
Streaming
After sending, get a stream for the response:
const stream = await session.stream();
return createUIMessageStreamResponse({ stream });When using workflow, pass the workflow result to stream from it:
const result = await start(agentWorkflow, [sessionId, message]);
const stream = await session.stream(result);
return createUIMessageStreamResponse({ stream });See Streaming for reconnection and status handling.
History
Load conversation history from storage:
const history = await session.history();Returns messages and parts for the session. Use this for initial page load or as a fallback when no active stream exists.
Interrupting
Stop the current assistant response:
await session.interrupt();Or interrupt inline when sending a new message:
await session.send("Actually, do this instead", {
interruptIfStreaming: true,
});Updating a Session
Update persistent session fields:
await session.update({
sandboxId: "custom-sandbox-123",
});update() is scoped to persistent session fields (sandboxId, lastMessageId). To change model, system prompt, or tools dynamically, use resolvable agent options with context.
Usage Tracking
const usage = await session.usage();Returns usage data with token counts. Use this for billing, quotas, or analytics.
Accessing the Sandbox
Every session exposes a sandbox property — a lazy handle that resolves to the session's sandbox:
const session = myAgent.session(chatId);
// No need to know the sandbox ID
await session.sandbox.exec({ command: "npm", args: ["test"] });
const status = await session.sandbox.getStatus();The sandbox ID is resolved from the session's storage record. This is useful when you need to interact with the sandbox outside of agent tool execution (e.g. in API routes or workflows).
Next Steps
- How the Agent Works — Architecture and lifecycle
- Tools — Built-in and custom tools
- Sandbox — Sandbox types, setup, and API
- Streaming — Reconnection and status handling