Sessions & Lifecycle
A session is a single conversation between a user and an AI agent. Every message, status change, and tool call lives inside one session.
Creating a session
Call client.createSession() to start a new conversation. Each session gets a unique ID, its own message history, and an independent status.
const client = createChatClient({ auth, transport });
const session = await client.createSession();
// session.sessionId — unique identifier
// session.getStatus() — current status
// session.getMessages() — message history
Session status
The session moves through a state machine as it authenticates, connects, sends messages, and receives responses. Here are the key statuses:
idle -> authenticating -> connecting -> ready
ready -> submitted -> streaming <-> waiting_for_tool
streaming -> ready (turn complete)
any -> error | disconnected -> recovering -> ready
ready -> ended | shutdown
Status summary:
| Status | Meaning |
|---|---|
idle | Session created, not yet started |
authenticating | Acquiring an auth token |
connecting | Opening the transport connection |
ready | Able to send messages |
submitted | User message sent, waiting for response |
streaming | AI response arriving incrementally |
waiting_for_tool | AI requested a tool call; SDK is executing it |
ended | Conversation finished (server or client ended it) |
error | Something went wrong; may recover |
disconnected | Transport connection lost |
recovering | Attempting to reconnect |
shutdown | Session permanently closed via session.shutdown() |
Listen for status changes with the status_changed event:
session.on('status_changed', ({ status, previousStatus }) => {
console.log(`${previousStatus} -> ${status}`);
});
Streaming behavior
When the AI is responding, the session status is streaming. During streaming:
- The SDK blocks concurrent sends. Calling
session.send()while streaming throws aSESSION_INVALID_STATEerror. Callsession.stop()first if you need to interrupt. - Messages update incrementally as transport events arrive. Listen for
message_updatedto re-render. session.storeexposesisStreamingas a reactive boolean for UI binding.
The send flow
Here is what happens when you call session.send("Hello"):
- The user message is normalized and added to the message history (
message_addedfires). - Status moves to
submitted, thenstreaming. - Transport events arrive:
response.started,text.delta(repeated),response.completed. - Each delta updates the agent message in place (
message_updatedfires). - If the agent calls a tool, status moves to
waiting_for_tool, the SDK executes the tool, then returns tostreaming. - When the response completes, status returns to
ready.
Session resumption
Resume an existing conversation with client.resumeSession():
const session = await client.resumeSession({
sessionId: 'existing-session-id',
});
If the session ID exists in the local conversation registry, the SDK reconnects to it. If not, it throws CONVERSATION_NOT_FOUND.
Message operations
Beyond send(), ChatSession exposes a handful of higher-level operations:
session.editLastMessage(input)— marks the previous user message and any trailing agent messages asstatus: 'replaced'(kept in history for undo and edit indicators) and sends a fresh user message taggededitOf: <originalId>. Useful for "redo with a different prompt" affordances.session.regenerate()— re-runs the last agent turn. Stable API; uses the same transport contract assend().chat.trackPurchase({ orderId, revenue, currency, items?, paymentMethod? })— first-class commerce outcome. Emits apurchase_completedanalytics event with ISO 4217 currency validation. The event isNEVER_SAMPLED, and the analytics summary trackspurchaseCount,grossMerchandiseValue(per-currency), andaverageOrderValue.session.shutdown()— tears down the session. If aVoiceSessionis active, it stops voice before tearing down the chat transport. Auth providerclear?.()is called inside a try/catch so a buggy provider can't block teardown.
Voice subsystem lifecycle
chat.voice is a lazy getter. Configuring voice on ChatClientConfig does not request the microphone, open a WebSocket, or run the provider factory. The provider factory runs only when something first reads session.voice. This lets hosts gate voice behind a button and defer permission requests until the user explicitly wants voice.
A VoiceSession borrows chatSession.identity verbatim — no new identity primitives. Switching from voice back to text is voiceSession.stop(); the chat session is unaffected.
What's next
- Architecture -- how sessions, transports, and auth fit together
- Transports -- the pluggable layer that connects sessions to a backend
- Voice and Multimodal -- the
VoiceSessionlifecycle - Handoff -- transfer types, the completion FSM, and internal vs external handoff
docs/concepts/sessions-and-lifecycle.md