SNA

정규화 히스토리

모든 런타임 네이티브 포맷으로 되돌릴 수 있는 평탄한 블록 포맷.

SNA는 대화 히스토리를 SQLite 안의 정규화 블록 시퀀스로 저장합니다. 각 블록은 두 축을 갖습니다.

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

바이너리 콘텐츠(이미지, 파일)는 id 기반 별도 embeds JSON에 두고, 블록의 content 텍스트는 인라인 ![](embed://<id>) 참조를 갖습니다. 런타임 네이티브 포맷(Anthropic content array, Codex ResponseItem, OpenCode parts)은 요청 시 파생됩니다. 저장소의 기준은 정규화 블록입니다.

왜 플랫 블록인가

대부분의 런타임은 text, tool_use, thinking을 하나의 "message" 객체 안에 묶는 중첩 메시지 포맷을 씁니다. 단일 런타임에서는 큰 문제가 없지만, 크로스 런타임 replay에서는 복잡해집니다. 런타임마다 순서, signature, optional 필드 규칙이 다르기 때문입니다.

평탄 블록 리스트는 세 런타임 모두 라운드트립이 가능한 최소 표현입니다. 각 런타임의 어댑터가 평탄 리스트를 걸으면서 그 런타임이 기대하는 중첩 모양을 재구성합니다.

actorkind 설계의 배경은 Actor와 kind 에서 다룹니다.

세션 이어가기와 캐시 친화성

대부분의 앱은 매 턴 resume을 호출할 필요가 없습니다. 세션이 실행 중이면 같은 SNA 세션에 다음 메시지를 보내면 됩니다. SNA가 런타임 네이티브 대화/thread를 유지하고, 재시작, 프로세스 복구, 명시적인 런타임 경계가 필요할 때만 정규화 블록에서 네이티브 포맷을 재구성합니다.

이 분리는 비용과 지연 시간에 중요합니다. 활성 런타임은 같은 대화의 상태와, 해당 런타임/벤더가 제공하는 캐시 동작을 계속 활용할 수 있습니다. 앱은 대화를 이어가기 위해 캐시 키, 히스토리 압축, 프롬프트 재전송을 직접 구현하지 않아도 됩니다.

Replay 어댑터

경로어댑터
claude-code resumehistory/claude-code.ts (thinking.signature 보존하는 JSONL)
codex resumehistory/codex.ts (thread/resume + ResponseItem[])
opencode resumehistory/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로 전환했습니다" 같은 표시를 정확히 렌더할 수 있습니다.

목차