Session history and recovery
Event ordering, delivery, persistence and restart behavior.
Use Sessions for application examples. This page defines what saved history, event delivery and interruption mean for your app.
Saved history
session.transcript() returns the current model messages and through_sequence.
session.events(after) reads committed events in sequence order; session.stream(after) reads
those events and then follows new ones. Reads do not require an active agent turn.
The loop chooses which tool observations become model messages. A model call alone does not change the saved transcript. Transcript/state changes are saved independently before returning to the loop, so a failed turn may contain completed writes and external actions.
Every committed lifecycle, model/tool call, transcript and loop-state change has one session
sequence. (sessionId, sequence) identifies a record. transcript_delta carries { keep, append };
kv_set carries { key, value }; kv_delete carries { key }. A consumer can reconstruct those
projections by processing the events in order and persisting its cursor.
Reading events
This continuation assumes the quickstart's session. handle and saveCursor are your own
consumer and durable cursor store:
let cursor = 0;
for await (const event of session.events(cursor)) {
await handle(event);
cursor = event.sequence;
await saveCursor(cursor);
}Finite HTTP pages contain at most 1,000 events and normally at most 8 MiB of records. An oversized next event is returned alone so the cursor can advance. The SDK follows pages for you.
For live HTTP delivery, send Accept: text/event-stream to
GET /v1/sessions/{id}/events?after={cursor} with your API bearer token. SSE first catches up from
saved history, then follows new commits. Model token deltas are live-only and have no sequence;
reconnect to recover the completed model result, not missed deltas. Historical telemetry needs a
configured sink.
Your application owns reconnection, cursor persistence, retries and deduplication when forwarding events elsewhere. Processing an event and saving its cursor separately can cause redelivery after a crash. The event stream is not an external queue or backup.
Event origin
Recorded events expose their payload as event.data and, where present, Brain-assigned provenance
as event.origin. Origins can identify an agent loop, tool or environment. Extension-supplied
payload fields cannot override that origin. Older events may lack origin; do not infer authorship
from an event's name. Origin identifies its source, not the truth of its contents or human approval.
Turns, results and failure
session.submit() returns the committed turn_started sequence. session.outcome(sequence)
reads that turn as pending, ended or failed. Terminal outcomes include the original event
and, when the loop emitted one, an assistant answer. An idle session does not prove a successful
turn; an ended turn does not prove every tool or background job succeeded.
Brain saves a model/tool/environment operation before dispatch and sends it once. It saves the observation before returning it to the loop. Tool events distinguish emitted results, synchronous return and final completion; returning need not finish background work.
| Outcome | Meaning |
|---|---|
error | A known validation, execution or protocol failure |
timeout | The tool's original deadline expired |
cancelled | Work was explicitly interrupted |
unknown | Dispatch may have occurred, but no reliable result is available |
These tool failures have is_error: true. None promises rollback or automatic retry. Inspect the
external resource before deciding whether an uncertain action should be attempted again.
Background tools retain their original deadlines and emission budgets after a turn ends. Later results or actionable environment observations can activate the loop without a user message. The loop saves its decisions; successful return completes its delivered event batch; see the loop API.
Suspension and recovery
Brain can release inactive execution while keeping sessions readable. idleTtlMs changes how long
execution is retained; explicit zero retains it indefinitely. Releasing execution does not detach
environments or end the session. Provider expiry and cleanup rules still apply to their resources.
With storage intact, a server restart preserves committed history. First access after restart records an interrupted turn as failed; Brain never reruns it. An effect with no recorded terminal result becomes an ambiguous failure. Interrupted creation fails, and interrupted ending is closed without allowing new messages. A failed detach does not prevent ending.
Teardown is separate. If it fails, the environment record remains; an explicit session delete with a new idempotency key can request another attempt. There is no automatic retry.
Idempotency claims survive restart. Completed request responses are retained for 24 hours. If a claimed request has no recorded response, reusing its key returns an ambiguous error and does not execute again. Inspect session history before choosing a new operation key.
Committed records survive process and power failure when the local filesystem and storage honor flushes. Storage loss and lost provider resources are outside this guarantee. Follow the backup and upgrade procedure to protect stored sessions.
Lifecycle and cleanup
interrupt() requests cancellation of the turn and unfinished tools, including background tools
while the loop is idle. It keeps the session open. Cancellation is forwarded to executors; cleanup
can extend response time beyond the turn deadline. The configuration reference
defines model-call, turn and tool limits.
end() refuses new messages, cancels unfinished tools and waits for their cleanup before detaching
environments. History remains. delete() requires an ended or failed session and removes history,
tears down environments and forgets credentials.
brain.close() closes this client's connections and local handlers. It is idempotent and rejects
later client operations, but does not interrupt or end stored sessions. It does not wait for
application code that ignores cancellation. A client created by withToken() has its own lifetime.
Environment turn-end cleanup can finish after the answer is available. Cleanup failure does not replace that answer; read its events to inspect the result. Graceful server shutdown drains admitted work and cleanup while callbacks remain available, then closes workers and subscriptions. Forced termination or deadlines can still leave partial progress.