One stream, five views
Order by seq
seq is the per-Session sequence. Feed events to a projection in seq order; an event at or below the high-water mark is a no-op.Project, don't copy
A projection derives a view from the canonical event. It never replaces the log, never moves the cursor, and never reaches the network.
Settle on events
Read settlement from status events and their
stop_reason, never from stream quiescence. See Resume a stream.CHECKFU_API_KEY and CHECKFU_WORKSPACE_ID come from Get access; sessionId is the sess_… you are following. createSessionProjections() bundles all five views behind one observe, so a single pass over the stream keeps every view current. Each view is also available on its own — new MessageProjector(), new ContextLedgerProjector() — when a consumer wants only one.
Messages
The readable conversation: user, system, and agent turns in log order. It folds the events whosepayload.content is the point of the turn, and represents a screen-withheld agent message as a structural marker rather than the judged text.
text is null and multimodal is true, so a renderer reads the canonical event by seq for the block bodies. A withheld agent.message has withheld set and text null — its message.screened verdict is a separate event you can correlate by content digest.
Tool calls
The conversational tool-activity timeline: eachagent.tool_use paired with its settling agent.tool_result.
This is the read-side timeline, not the ToolServing loop. The SDK’s
ToolServingProjector (used by tools.serve) is the serving state machine
that decides when to invoke your handler. Platform-witnessed exec.tool_invocation
settlement is a separate governance lane — it correlates by tool_invocation_id, not
tool_use_id — so it is documented in the events reference
rather than folded here. See Serve tools from your app.ActionApprovals and questions
Both park a Run on a human, so they share a view. A governed ActionApproval is a durable permission decision; a Question is ordinary input that grants no authority. They never collapse: an ActionApproval is answered through the ActionApprovals API, a Question throughuser.question_answer.
run.requires_action to learn what a Run is parked on (see Answering a parked run); this projection is the read-side summary of those waits.
Multiagent state
The multi-agent view keeps two distinct mechanisms. A projection presents them as separate lists so observed harness-internal activity is never conflated with governed coordinator threads.Context ledger
The inspectable accounting of what is in context: token usage, model routing, compaction and condensation boundaries, and definition-version changes. It is the read-side fold of the same events theGET /v1/sessions/{id}/ledger endpoint summarizes, so a live consumer keeps a current ledger without re-reading the endpoint after every event.
Feeding projections durably
A live tail is enough for a transient view. When the projection must survive a process restart — a Slack thread, a ticket, a dashboard backed by its own store — feed it fromcreateSessionObserver, which owns lossless cursor resume, live-event custody, and terminal cleanup:
eventDelta is only the recognized, unsettled delta and can be redelivered; it is not the complete Session prefix. Your provider receipt transaction must update cumulative provider state and its cursor together. Unknown future events do not appear in the typed delta but still contribute to throughSequence, so read the canonical log when their exact envelopes matter. The SDK projectors have no durable snapshot format: use them for a process-lifetime view, or replay the authoritative log under an explicit retention/work policy before publishing a complete view. The full observer contract — coalescing, backoff, and terminal cleanup — is in the SDK reference.
Next steps
Events reference
The envelope, the full event catalog, and the drive events these projections fold.
Resume a stream reliably
The cursor and replay rules every projection inherits.