Normalized events

One event stream for every agent. Text, tools, plans, subagents and usage look the same whichever agent produced them.

Each agent speaks its own protocol. The core turns what they send into one set of events, so your UI handles a tool call the same way for Claude Code as for Codex. The vendor's original payload rides along on every event.

Subscribe

const unsubscribe = core.subscribe(event => {
  if (event.type === "session.event") render(event.sessionId, event.event)
})

One observer sees every session. Events are live notifications, not a durable log: store what you want to keep. An observer that throws is reported to onObserverError, if you passed one.

Core events

EventWhen
session.created, session.resumedA session was created or came back. Carries its record.
session.stateChangedThe session's state changed: idle, running, interrupting, closing, closed or failed.
session.eventA normalized event from the agent or the core.
session.updateA raw update from the agent, in its own protocol.
session.failedThe session's runtime failed.
message.accepted, message.started, message.completedYour message was queued, started, and finished (with its result).
account.switched, account.exhausted, account.unavailable, account.available, account.refreshedSee Accounts.
tool.called, tool.finishedA tool from your own MCP server ran.
context.updated, context.degradedThe session's skills, plugins or MCP servers changed, or some couldn't be applied.

Normalized events

A session.event carries an envelope and a body:

{
  sessionId, agent, seq, ts, turnId?, origin: "live" | "replay",
  native: { protocol, method?, payload },   // the vendor's original frame
  kind: "tool-call", callId: "…", tool: "Bash", phase: "started", category: "execute", …
}

The body kinds:

KindWhat it is
turn-start, turn-completeA turn began, or ended (reason: "ok" or "interrupt").
assistant-delta, assistant-messagePart of the reply as it streams, and the full message.
reasoning-delta, reasoningThe model's reasoning, when the agent shares it.
tool-callA tool call, with phase (started, updated, completed, failed), a category like edit or execute, and its input and output.
command-outputOutput of a running command, by callId.
file-diffA file change, with its diff.
web-search, mcp-toolA web search, or a call to an MCP tool.
planThe agent's to-do list, with each entry's status.
taskBackground work: a shell, a subagent, a monitor.
subagentA subagent started, made progress, or finished.
permission-requestThe agent needs approval. See Permissions.
user-questionThe agent asks a question, with options.
request-resolvedA request was answered or cancelled.
permission-autoA request was answered automatically by the session's policy.
permissions-updateThe session's permissions changed, now or from the next-turn.
usageTokens, context size, cost and rate limits, where the agent reports them.
compactionThe agent compacted its context.
commands-update, mode-update, session-infoThe agent's slash commands, mode or model, title or folder changed.
warning, errorSomething went wrong; error may say if it's recoverable.

Any body can carry a subagentId when it comes from a subagent.

Not every agent sends every kind. Antigravity, for example, sends no reasoning, plans, diffs or subagents. A frame the core doesn't recognize arrives only as a raw session.update.

Coming in the next release

The code after 0.17.1 adds a conversation-reset event (Claude Code's /clear starts a new conversation and the session follows it) and richer commands-update entries with argument hints and aliases.

Order

For a message you send:

  1. message.accepted
  2. session.stateChanged → running, then turn-start
  3. message.started
  4. the turn's events
  5. message.completed, then session.stateChanged → idle, then the final messages and turn-complete

So await completed can resolve before the last events of the turn. Wait for turn-complete to act on the whole turn.

A turn the agent starts on its own (for example when a background task finishes) has turn-start and turn-complete but no message.* events.

seq counts events per Session object, and turnId is turn:1, turn:2… A resumed session is a new object, so both start over.

Replay

  • origin: "live": happening now, or caught up from the keeper after you re-attached.
  • origin: "replay": history the agent replays when it loads a conversation. The core never normalizes replayed history when it opens a session.

On this page