정규화 히스토리
모든 런타임 네이티브 포맷으로 되돌릴 수 있는 평탄한 블록 포맷.
SNA는 대화 히스토리를 SQLite 안의 정규화 블록 시퀀스로 저장합니다. 각 블록은 두 축을 갖습니다.
actor:user|assistant|systemkind:text|thinking|tool_use|tool_result|status|error
바이너리 콘텐츠(이미지, 파일)는 id 기반 별도 embeds JSON에 두고,
블록의 content 텍스트는 인라인  참조를 갖습니다.
런타임 네이티브 포맷(Anthropic content array, Codex
ResponseItem, OpenCode parts)은 요청 시 파생됩니다. 저장소의 기준은
정규화 블록입니다.
왜 플랫 블록인가
대부분의 런타임은 text, tool_use, thinking을 하나의 "message" 객체 안에 묶는 중첩 메시지 포맷을 씁니다. 단일 런타임에서는 큰 문제가 없지만, 크로스 런타임 replay에서는 복잡해집니다. 런타임마다 순서, signature, optional 필드 규칙이 다르기 때문입니다.
평탄 블록 리스트는 세 런타임 모두 라운드트립이 가능한 최소 표현입니다. 각 런타임의 어댑터가 평탄 리스트를 걸으면서 그 런타임이 기대하는 중첩 모양을 재구성합니다.
actor와 kind 설계의 배경은 Actor와 kind 에서
다룹니다.
세션 이어가기와 캐시 친화성
대부분의 앱은 매 턴 resume을 호출할 필요가 없습니다. 세션이 실행 중이면
같은 SNA 세션에 다음 메시지를 보내면 됩니다. SNA가 런타임 네이티브
대화/thread를 유지하고, 재시작, 프로세스 복구, 명시적인 런타임 경계가
필요할 때만 정규화 블록에서 네이티브 포맷을 재구성합니다.
이 분리는 비용과 지연 시간에 중요합니다. 활성 런타임은 같은 대화의 상태와, 해당 런타임/벤더가 제공하는 캐시 동작을 계속 활용할 수 있습니다. 앱은 대화를 이어가기 위해 캐시 키, 히스토리 압축, 프롬프트 재전송을 직접 구현하지 않아도 됩니다.
Replay 어댑터
| 경로 | 어댑터 |
|---|---|
claude-code resume | history/claude-code.ts (thinking.signature 보존하는 JSONL) |
codex resume | history/codex.ts (thread/resume + ResponseItem[]) |
opencode resume | history/opencode.ts (prompt-prelude 재구성) |
POST /agent/resume은 새로 spawn된 프로세스에 히스토리를 replay하는
경로입니다. 입력은 세션 id와 정규화 블록뿐이며, 어댑터가 런타임별 주의점
(signature 필드, tool_use 순서, partial-text 합치기)을 모두 처리합니다.
3계층 어트리뷰션
모든 블록은 세 가지 어트리뷰션 차원을 기록합니다.
provider: 블록을 만든 런타임 (claude-code / codex / opencode)modelProvider: 모델을 소유한 벤더 (anthropic / openai / google)model: 정확한 슬러그 (claude-sonnet-4-6,gpt-5.4등)
이렇게 하면 사용자가 스레드 도중 런타임을 바꿔도 각 블록의 출처를 잃지 않습니다. UI는 "여기서 Codex의 gpt-5.4로 전환했습니다" 같은 표시를 정확히 렌더할 수 있습니다.