Aex Brain
Concepts

Sessions

The log, turns, recovery, and reading events.

A session is a conversation plus everything Brain did on its behalf, recorded as an ordered log.

Writes land before effects

Before Brain calls a model, activates the agent loop, or dispatches a tool call, it writes the intent to the log. Before the loop sees a result, that result is written too.

The log is written behind the turn — no fsync on the hot path — so a crash may lose its tail. On restart Brain rebuilds every session from the records that reached the disk: a session whose last turn finished comes back idle with its history readable, and one that was mid-turn comes back with a turn_interrupted event and returns to idle. Whether the in-flight call reached the model or a tool is not knowable from here, so Brain records exactly that and lets the client resume or abandon on its own terms.

A session also does not have to be resumed in place. Pass the events you already received back as history when creating a session, and Brain writes them as the new session's opening records and tells the agent loop what it is continuing — which is how a conversation moves across processes or machines.

Turns

A turn starts when you send a message and ends when the agent loop decides it is finished. In between, Brain and the loop alternate: the loop returns a decision, Brain performs it, the loop sees the result and decides again.

BRAIN_MAX_DECISIONS bounds how many decisions one turn may take. The default is 128.

Reading events

Events are the log, exposed. Read from a cursor and you get everything committed since:

let cursor = 0;
for await (const event of session.events(cursor)) {
  await handle(event);
  cursor = event.sequence;
  await saveCursor(cursor);
}

This reads the durable log, so it is replayable and gap-free. The live stream on top of it is bounded and best-effort: under sustained pressure it drops rather than blocking a turn or growing without limit. If you need at-least-once delivery into your own systems, own the cursor and do the forwarding yourself — Brain is not a queue.

The live stream is the same endpoint asked for a different media type:

GET /v1/sessions/{id}/events?after={cursor}
Accept: text/event-stream

It begins with the page after names and then carries records as they are appended, so opening it before sending a message is how you see that turn's first output. While a turn is running it also carries the model's output as it arrives — assistant_delta and tool_call_delta events. Those are never written to the log and carry no id: the id is the resume cursor, and a client that reconnects gets the completed message from the log rather than the tokens that built it. The stream ends when the session ends, and it ends if you fall too far behind — reconnect with the last id you saw and the log hands back exactly what you missed. Accept: application/json returns one page and is what session.events() uses.

Lifecycle

cancel stops work in flight. end closes the session to new messages. delete removes it and its log. They are separate on purpose: ending a session keeps its history readable.

On this page