Sessions
Sessions represent conversations with an agent. They store conversation history, track resources and variables, and enable stateful interactions.
Creating Sessions
Create a session by specifying the agent ID and initial input variables:
Create and Trigger in One Call
create() followed by attach().execute() is two round trips, and it forces you to create the session before you know whether the user will ever send a message. agentSessions.start() does both in a single streaming request: it creates the session and runs its first trigger, so per-session configuration (model, system-prompt inputs, anything routed through session input) is captured at the moment of the first message, and no session is created for a conversation that never starts.
After the first trigger, the handle behaves exactly like one from attach() - continuations and later triggers automatically target the now-created session. Calling execute() with a continue request before the first trigger throws, since there is no session to continue yet.
Learning the session id
The created session id reaches you through two channels:
onSessionCreated(sessionId)- fired as soon as the id is known, before events stream. This is the ergonomic way to persist your chat-to-session mapping.- The first
startevent - carriessessionId, which the client SDK also surfaces (see Client SDK Overview). Useful when the browser needs the id, or for transports that do not expose response headers.
getSessionId() returns undefined on a deferred handle until the first trigger creates the session, then returns the id.
Retry safety
start() sends a client-supplied idempotencyKey (auto-generated if you omit it). A transient retry of the first request resolves to the same session instead of creating a duplicate or double-counting session usage, so it is safe to retry. Using a stable, meaningful key such as your chatId guarantees one session per chat even across retries or double submits.
Start Options
| Option | Type | Description |
|---|---|---|
agentId | string | Agent to start a session for |
input | Record<string, unknown> | Immutable session input, captured at the first trigger |
idempotencyKey | string | Makes a retried start resolve to the same session; auto-generated |
onSessionCreated | (sessionId: string) => void | Called with the session id as soon as it is assigned |
start() also accepts the same tools, resources, mcpServers, onToolResults, and rejectClientToolCalls options as attach().
The two-step create() + attach() flow is unchanged and fully supported. start() is additive - reach for it when you want to defer creation to the first message.
Getting Session Messages
To restore a conversation on page load, use getMessages() to retrieve UI-ready messages:
The returned messages can be passed directly to the client SDK's initialMessages option.
UISessionState Interface
Full Session State (Debug)
For debugging or internal use, you can retrieve the complete session state including all variables and internal message format:
Note: Use getMessages() for client-facing code. The get() method returns internal message format that includes hidden content not intended for end users.
Getting Execution Logs
getLogs() returns the chronological execution trace for a session - triggers, messages, tool calls, LLM responses, errors, and other events emitted while the agent ran. Useful for debugging, observability, and building custom timeline views.
Each entry is a typed variant of ExecutionLogEntry (a discriminated union) so consumers can narrow on entry.type:
Excluding Model Request Payloads
Model-request entries include the full provider request body and can be large. Pass excludeModelRequests: true to skip them:
Truncation
Responses are capped at 1000 entries (most recent). When the log exceeds that cap, the response includes total and truncated so consumers can detect this:
Response Types
| Status | Type | Description |
|---|---|---|
active | ExecutionLogsResult | { sessionId, entries, total?, truncated? }. total and truncated are present when known |
expired | ExpiredSessionState | { sessionId, agentId, status: 'expired', createdAt } |
Forward-compatible types: ExecutionLogEntry may gain new variants over time. Include a default case when switching on entry.type so unknown variants are handled gracefully.
Attaching to Sessions
To trigger actions on a session, you need to attach to it first:
Attach Options
| Option | Type | Description |
|---|---|---|
tools | ToolHandlers | Server-side tool handler functions |
resources | Resource[] | Deprecated - resource watchers for real-time updates. Use tools instead. |
onToolResults | (results: ToolResult[]) => void | Callback invoked after server-side tool results are produced |
rejectClientToolCalls | boolean | If true, reject tool calls that have no server handler (no client forwarding) |
For MCP tool integration (browser, filesystem, shell via @octavus/computer), register dynamic tools after attaching with session.setDynamicTools(). See Computer for details.
Executing Requests
Once attached, execute requests on the session using execute():
Request Types
The execute() method accepts a discriminated union:
This makes it easy to pass requests through from the client:
Attributing Messages in Multi-User Chats
When several people share one conversation, set sender on the trigger so each user message is attributed to its author. Set it server-side from your authenticated user - never trust a client-supplied identity:
The runtime stamps the sender onto the user message it creates, so it comes back on UIMessage.sender from getMessages() and survives restore. sender is turn metadata - it is never added to your protocol's trigger input, and agent-initiated turns (no sender) stay unattributed. For instant optimistic display in the browser, also pass it on the client send() (see Client SDK Messages).
Stop Support
Pass an abort signal to allow clients to stop generation:
When the client aborts the request, the signal propagates through to the LLM provider, stopping generation immediately. Any partial content is preserved.
WebSocket Handling
For WebSocket integrations, use handleSocketMessage() which manages abort controller lifecycle internally:
The handleSocketMessage() method:
- Handles
trigger,continue, andstopmessages - Automatically aborts previous requests when a new one arrives
- Streams events via the
onEventcallback - Calls
onFinishafter streaming completes (not called if aborted)
See Socket Chat Example for a complete implementation.
Session Lifecycle
Session Expiration
Sessions expire after a period of inactivity (default: 24 hours). When you call getMessages() or get(), the response includes a status field:
Response Types
| Status | Type | Description |
|---|---|---|
active | UISessionState | Session is active, includes messages array |
expired | ExpiredSessionState | Session expired, includes sessionId, agentId, createdAt |
Persisting Chat History
To enable session restoration, store the chat messages in your own database after each interaction:
Best Practice: Store the full UIMessage[] array. This preserves all message parts (text, tool calls, files, etc.) needed for accurate restoration.
Restoring Sessions
When a user returns to your app:
Restore Response
Complete Example
Here's a complete session management flow:
Clearing Sessions
To programmatically clear a session's state (e.g., for testing reset/restore flows), use clear():
After clearing, the session transitions to expired status. You can then restore it with restore() or create a new session.
This is idempotent - calling clear() on an already expired session succeeds without error.