The Log is the second of the Harness's three nouns: everything that happened in a thread, as an append-only sequence of durable events. It is deliberately not called a chat transcript, because it is a shared workspace log — the user and the agent write to the same one, with the same five verbs, and there is no side channel between them.
Physically it is the session_events table: one CBOR payload per (session_id, seq), with seq assigned as MAX(seq)+1 per session.
Event shapes
type SessionEventPayload =
| {type: 'message'; role: 'user' | 'assistant' | 'tool'; content: string
toolCallId?; rawMarkdown?; blocks?; contextLines?; attachments?; clientMessageId?; actor?; meta?}
| {type: 'tool_call'; id: string; name: string; input: unknown; actor?}
| {type: 'tool_result'; toolCallId: string; name: string; output?; error?; actor?; meta?}
| {type: 'error'; message: string; actor?}
| Record<string, unknown>The open final member is intentional: an unknown event shape from a newer server renders generically rather than breaking an older client.
meta (SessionEventMeta) carries {model, provider, usage, durationMs} — which is what the ⓘ bubble on any event opens. Usage is per turn, in tokens, split across input, output, cache reads and cache writes.
Actors
Every event carries an actor: user, agent, system, or trigger. This one field is what makes the log honest.
The runtime speaks as itself. Every runtime-authored message is durably actor: 'system' and renders as a quiet grey aside; it never wears the user's voice.
Trigger firings are attributed to trigger, not disguised as a user message.
User tool calls are marked as such, which is what the "You" chip renders.
Legacy events written before actors existed derive theirs at read time from their shape (sessionEventActor): a user message is user, an error is system, everything else is agent. There was no migration and no backfill — computing the answer is what keeps it identical after replay or compaction.
The symmetric half
The composer's wrench palette lets the user run read, write, and call themselves through InvokeSessionTool, with forms generated from each tool's JSON Schema. The invocation and its result land on the Log as actor: 'user' events, and the agent sees them on its next turn as <user_action> / <user_action_result> context — reading the user's tool results as shared ground truth rather than as reported hearsay, because they came through the same dispatchers its own calls do.
User verbs execute inline against those same dispatchers, guarded by a lock that an in-flight agent turn refuses with a 409: two writers, one log, no interleaving surprises.
What the Log is the source of truth for
More than display:
The promoted tool set is reconstructed purely from durable tool_call events — the transcript is the pin (Tools as Documents).
Replay rebuilds the model's entire message list from events on every turn, with tool results reattached behind their calls and still-running children given honest placeholders (Sessions).
Two things deliberately do not live on the Log: the checklist, which is rendered fresh into each turn from session state so the agent always sees its current shape, and window context from the client, which is passed to the model but kept out of the durable content field.
Live delivery
A client subscribes to sessions/<id> over the signed WebSocket and receives append events for durable rows and appendPartial patches ({textDelta, done, usage, activity}) while a response streams. Subscribing with afterSeq replays the gap — the sequence number is the whole recovery protocol (Protocol).
Related
Sessions and Threads — what holds the Log
Obligations — how an honest ending is recorded here
Do you like what you are reading? Subscribe to receive updates.
Unsubscribe anytime