SNA

正規化履歴

すべてのランタイムネイティブ形式へ戻せるフラットブロック形式。

SNA は会話履歴を SQLite 内の 正規化ブロック のシーケンスとして 保存します。各ブロックは 2 つの軸を持ちます:

  • actor: user | assistant | system
  • kind: text | thinking | tool_use | tool_result | status | error

バイナリコンテンツ (画像、ファイル) は id をキーにした別の embeds JSON に置かれ、ブロックの content テキストには ![](embed://<id>) のインライン参照が入ります。ランタイムネイティブの形 (Anthropic content 配列、Codex ResponseItem、OpenCode parts) は 要求時に派生 されます。 保存上の基準は正規化ブロックです。

なぜフラットブロックか

ほとんどのランタイムは text、tool_use、thinking を 1 つの "message" オブジェクトにまとめるネストしたメッセージ形式を使います。単一ランタイム では大きな問題になりませんが、クロスランタイム replay では複雑になります。 ランタイムごとに順序、signature、optional フィールドのルールが異なるためです。

フラットブロックリストは 3 つのランタイムすべてで round-trip 可能な 最小の表現です。各ランタイムのアダプタがフラットリストを歩いて、 そのランタイムが期待するネストした形を再構築します。

actorkind の設計背景は Actor と kind で 扱います。

セッション継続とキャッシュしやすさ

ほとんどのアプリでは、毎ターン resume を呼ぶ必要はありません。 セッションが動いている間は、同じ SNA セッションに次のメッセージを送るだけです。 SNA がランタイムネイティブの会話/thread を維持し、再起動、プロセス復旧、 明示的なランタイム境界が必要な時だけ正規化ブロックからネイティブ形式を 再構築します。

この分離は、コストとレイテンシに効きます。アクティブなランタイムは、 同じ会話の状態と、そのランタイム/ベンダが提供するキャッシュ動作を 使い続けられます。アプリ側で会話を続けるためだけに、キャッシュキー、 履歴圧縮、プロンプト再送を実装する必要はありません。

Replay アダプタ

パスアダプタ
claude-code resumehistory/claude-code.ts (thinking.signature を保持する JSONL)
codex resumehistory/codex.ts (thread/resumeResponseItem[])
opencode resumehistory/opencode.ts (prompt-prelude 再構成)

POST /agent/resume は、新たに spawn したプロセスに履歴を replay する経路です。入力はセッション id と正規化ブロックだけで、アダプタが ランタイム固有の注意点 (signature フィールド、tool_use 順序、partial-text の合体) をすべて処理します。

3 層アトリビューション

すべてのブロックは 3 つのアトリビューション次元を記録します。

  • provider: ブロックを作ったランタイム (claude-code / codex / opencode)
  • modelProvider: モデルを所有したベンダ (anthropic / openai / google)
  • model: 正確なスラッグ (claude-sonnet-4-6gpt-5.4 等)

これにより、ユーザーがスレッド途中でランタイムを切り替えても各ブロックの 出どころを失いません。UI は「ここで Codex の gpt-5.4 に切り替えました」 のような表示を正確に出せます。

目次