Canonical history
One flat block format that round-trips to every runtime's native shape.
SNA stores conversation history as a flat sequence of canonical blocks in SQLite. Each block has two orthogonal axes:
actor:user|assistant|systemkind:text|thinking|tool_use|tool_result|status|error
Binary content (images, files) lives in a separate embeds JSON keyed
by id; the block's content text holds inline 
references. Runtime-native shapes (Anthropic content arrays, Codex
ResponseItems, OpenCode parts) are derived on demand, they are
not the source of truth.
Why flat blocks
Most runtimes ship a nested message format that bundles text, tool_use, and thinking inside a single "message" object. That works fine for a single runtime but makes cross-runtime replay painful: every runtime has slightly different rules about ordering, signatures, optional fields.
A flat block list is the smallest representation that round-trips across all three runtimes. The adapter for each runtime walks the flat list and rebuilds the nested shape that runtime expects.
For the design rationale behind actor and kind, see
Actor and kind.
Continuation and cache locality
Most apps should not call resume on every turn. Once a session is
running, send the next message to the same SNA session and SNA keeps the
runtime-native conversation/thread alive. When a restart, process
recovery, or explicit runtime boundary requires it, SNA rebuilds the
native shape from canonical blocks.
That split matters for cost and latency. The active runtime can keep using its own conversation state and any vendor-side cache behavior available for that conversation. The host app does not implement cache keys, history compaction, or prompt replay just to continue a chat.
Replay adapters
| Path | Adapter |
|---|---|
claude-code resume | history/claude-code.ts (JSONL with thinking.signature preservation) |
codex resume | history/codex.ts (thread/resume with ResponseItem[]) |
opencode resume | history/opencode.ts (prompt-prelude reconstruction) |
POST /agent/resume is how you replay history into a freshly spawned
process. The session id and canonical blocks are the only inputs; the
adapter handles every runtime-specific footgun (signature fields,
tool_use ordering, partial-text aggregation).
3-layer attribution
Every block records three attribution dimensions:
provider: runtime that produced the block (claude-code / codex / opencode)modelProvider: vendor that owned the model (anthropic / openai / google)model: exact slug (claude-sonnet-4-6,gpt-5.4, etc.)
This lets the UI render "you switched to Codex with gpt-5.4 here" without losing the per-row truth when the user changes runtimes mid-thread.