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:

StatusMeaning
idleSession created, not yet started
authenticatingAcquiring an auth token
connectingOpening the transport connection
readyAble to send messages
submittedUser message sent, waiting for response
streamingAI response arriving incrementally
waiting_for_toolAI requested a tool call; SDK is executing it
endedConversation finished (server or client ended it)
errorSomething went wrong; may recover
disconnectedTransport connection lost
recoveringAttempting to reconnect
shutdownSession 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 a SESSION_INVALID_STATE error. Call session.stop() first if you need to interrupt.
  • Messages update incrementally as transport events arrive. Listen for message_updated to re-render.
  • session.store exposes isStreaming as a reactive boolean for UI binding.

The send flow

Here is what happens when you call session.send("Hello"):

  1. The user message is normalized and added to the message history (message_added fires).
  2. Status moves to submitted, then streaming.
  3. Transport events arrive: response.started, text.delta (repeated), response.completed.
  4. Each delta updates the agent message in place (message_updated fires).
  5. If the agent calls a tool, status moves to waiting_for_tool, the SDK executes the tool, then returns to streaming.
  6. 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 as status: 'replaced' (kept in history for undo and edit indicators) and sends a fresh user message tagged editOf: <originalId>. Useful for "redo with a different prompt" affordances.
  • session.regenerate() — re-runs the last agent turn. Stable API; uses the same transport contract as send().
  • chat.trackPurchase({ orderId, revenue, currency, items?, paymentMethod? }) — first-class commerce outcome. Emits a purchase_completed analytics event with ISO 4217 currency validation. The event is NEVER_SAMPLED, and the analytics summary tracks purchaseCount, grossMerchandiseValue (per-currency), and averageOrderValue.
  • session.shutdown() — tears down the session. If a VoiceSession is active, it stops voice before tearing down the chat transport. Auth provider clear?.() 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 VoiceSession lifecycle
  • Handoff -- transfer types, the completion FSM, and internal vs external handoff
Source: docs/concepts/sessions-and-lifecycle.md