Build with sessions
Send messages, stream progress, retrieve results and reconnect your tools.
A session is one conversation plus the work the agent does for it. It remembers the model, loop and tools you chose, along with every message and step of progress. Save its ID to pick it up later.
Finish the quickstart first. These TypeScript/JavaScript excerpts use its
brain and session inside the .then() callback. Keep the client alive to send more messages.
Send messages and read the answer
await session.send("Where is order A-1001?");
const { messages } = await session.transcript();
console.log(messages);Each message starts a turn, in which the loop can call the model and tools before it finishes.
send() waits for the turn and returns session state, not the answer, so read the transcript for
messages. A session can go idle after a failed turn: check for turn_failed events as the
quickstart does, or use outcome(sequence) for a submitted turn. Request errors reject the promise;
catch them at your application boundary, and close the client when you're done with it.
Stream progress
Start reading before you send. This pattern prints events until the turn ends or fails, and closes the subscription even if the request fails:
const subscription = new AbortController();
let cursor = session.state.lastSequence;
const watching = (async () => {
for await (const event of session.stream(cursor, subscription.signal)) {
console.log(event.type, event.data);
if (event.sequence !== undefined) cursor = event.sequence;
if (event.type === "turn_failed") throw new Error(JSON.stringify(event.data));
if (event.type === "turn_ended") return;
}
throw new Error("Stream closed before the turn finished");
})();
try {
await Promise.all([watching, session.send("Explain event streaming in one sentence.")]);
} finally {
subscription.abort();
await watching.catch(() => {});
}The complete streaming example
uses the quickstart's server, packages and model key; run it with node streaming-session.mjs.
Aborting a subscription only stops listening. It doesn't cancel the agent's work; call
session.interrupt() for that. A send() signal requests cancellation of a turn you exclusively
own, so don't send concurrently through another owner of the same session.
Reconnect from a cursor
Save the sequence of the last event you processed. Open the session with
await brain.sessions.get(sessionId), then call session.events(cursor) to read saved events and
stop, or session.stream(cursor) to catch up and keep listening. Neither reconnects on its own
after a broken stream: your app opens the next subscription from its saved cursor.
Model deltas are live-only and carry no sequence. Reconnecting recovers saved events and the completed model response, but not missed token deltas. See history for delivery and deduplication.
Submit work and collect the outcome
const sequence = await session.submit("Summarize the conversation.", {
idempotencyKey: "summary-1",
});
console.log({ sessionId: session.id, sequence });submit() returns as soon as Brain has saved the request. Store the session ID and sequence. If
the response gets lost, repeat the same request with the same key; never reuse a key for new work.
Server-side work keeps going after the client closes, but application tools still need their host
process. Use HTTP or remote tools for independent execution.
Later, open the saved session from a new client:
const session = await brain.sessions.get(sessionId);
const outcome = await session.outcome(sequence);
if (outcome.status === "failed") {
throw new Error(JSON.stringify(outcome.terminal.data));
}
if (outcome.status === "ended") {
console.log(outcome.answer ?? await session.transcript());
}pending means check back later. ended means the loop finished, though individual tool calls or
background jobs may still have failed or be running. answer is only present if the loop emitted
an assistant answer event. The complete submission example
closes the submitter, polls from another client, checks for failure and keeps the transcript.
Let an idle client disconnect
After five seconds with no active turns, unfinished tools or follow-up work, the SDK releases its
shared tool connection. Session handles, callbacks and history stay available. The next send(),
submit() or Environment operation through the same live client reconnects before it starts work.
Reading history never opens the tool connection.
Set the timeout when you create the client:
const brain = new Brain({
baseUrl: "http://127.0.0.1:8080", token: "quickstart",
connectionIdleTimeoutMs: 5_000,
});Use connectionIdleTimeoutMs: 0 when other callers or future autonomous events need to reach this
client's tools. Nobody else can wake a disconnected browser. Closing the tab or process loses its
callbacks; restore them as shown below. The timeout never ends sessions or disposes of
Environments. Event subscriptions stay open until you stop iterating or abort their signal.
brain.close() disposes of the client permanently, and later operations reject.
Reattach application tools
An open session handle doesn't bring your application's functions back on its own. Before stopping
the original app, save await brain.credentials() in your credential store alongside the session
ID, then close that client. These host credentials are separate from the Brain API token and the
model key. Treat them as secrets.
After restarting, restore the same tool definitions and pass in the saved credentials:
const brain = new Brain({
baseUrl: "http://127.0.0.1:8080", token: "quickstart", credentials: savedCredentials,
});
try {
const session = await brain.sessions.get(sessionId, { tools: [lookupOrder()] });
await session.send("Look up order A-1002.");
console.log(await session.transcript());
} finally {
await brain.close();
}Here savedCredentials and sessionId come from your storage, and lookupOrder is the
quickstart's function. Supply exactly the tools originally placed in that host, with identical
schemas and environment names; changed contracts need a new session. Reattaching accepts future
calls. It doesn't replay interrupted calls or restore state held by the old process.
Stop, finish or delete
| Call | Effect |
|---|---|
session.interrupt() | Stop the current turn and unfinished tools; allow later messages |
session.end() | Finish the conversation and keep its history |
session.delete() | Delete an ended or failed session and release its environments |
brain.close() | Close this client's connections; keep stored sessions |
Cancelling never undoes external actions that already completed. After a server restart with storage intact, saved history is preserved, but interrupted work fails and isn't retried. See history and recovery for the exact guarantees.