SNA

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 | system
  • kind: 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 ![](embed://<id>) 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

PathAdapter
claude-code resumehistory/claude-code.ts (JSONL with thinking.signature preservation)
codex resumehistory/codex.ts (thread/resume with ResponseItem[])
opencode resumehistory/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.

On this page