Codex · 상태·메모리 영속성 (Rollout JSONL + state SQLite + Memories)

한 줄 요약

Codex는 한 세션의 기억을 속기록(Rollout) · 색인장부(state) · 학습위키(Memories) 세 갈래로 따로 보관한다. AI가 “어제 한 일을 이어서” 하고 “예전에 배운 걸” 써먹게 하려면, 무엇을 어디에 어떻게 남기는지를 알아야 직접 만들거나 디버깅할 수 있다.


그림

flowchart TD
  subgraph 세션진행중["세션 진행 중"]
    T["turn 사건<br/>(사용자말·모델답·도구호출)"] --> P{"policy:<br/>남길 가치 있나?"}
    P -- "예" --> R["('Rollout JSONL<br/>한 줄씩 추가')"]
    R --> X["apply_rollout_item<br/>(메타만 추려서)"] --> S["('state SQLite<br/>threads 색인')"]
  end
  R -- "스레드가 한가해지면" --> M1["1단계 추출<br/>스레드별 교훈 뽑기"]
  M1 --> M2["2단계 통합<br/>MEMORY.md로 정리"]
  subgraph 다음세션["다음 세션 시작"]
    RES["Resume:<br/>로그 다시 읽기"] --> H["복원된 대화 이력"]
    MEM["use_memories:<br/>MEMORY.md 읽기"] --> CTX["모델 프롬프트"]
    H --> CTX
  end
  R -.."재개".-> RES
  M2 -.."주입".-> MEM

쉽게 풀기

회사 비유로 보면 기억은 세 종류의 문서다.

flowchart LR
  R["Rollout<br/>회의 속기록<br/>(JSONL 원본)"]
  S["state<br/>캐비닛 장부<br/>(SQLite 색인)"]
  M["Memories<br/>사내 위키<br/>(MD 교훈집)"]
  R -- "메타만 베껴" --> S
  R -- "교훈만 추려" --> M

1) Rollout = 회의 속기록. 세션 중 모든 사건(사용자 말·모델 답·도구 호출·추론)을 발생 순서대로 한 줄씩 받아적는다(~/.codex/sessions/2025/06/15/rollout-….jsonl, JSON Lines). 이것만 있으면 회의를 그대로 다시 틀어(resume) 이어갈 수 있다. 단, 속기사는 다 적지 않는다 — “음…” 같은 잡음(스트리밍 중간 글자, 명령 시작/끝 신호)은 빼고 재생에 꼭 필요한 발언만 적는다. 이 선별 규칙이 policy.rs다.

2) state = 캐비닛 장부(색인). 속기록이 수천 개 쌓이면 목록·정렬·검색을 매번 전부 펼쳐 읽기엔 느리다. 그래서 회의의 제목·날짜·작업폴더 같은 메타만 SQLite에 거울처럼 베껴둔다. 빠른 조회용 색인일 뿐 진짜 원본은 속기록이며, 장부가 망가져도 속기록에서 다시 만든다.

3) Memories = 사내 위키(교훈집). 본문이 아니라 여러 회의에서 반복 학습한 교훈(이 사람 스타일, 이 프로젝트의 함정)만 추려 마크다운으로 정리한다. 다음 세션 시작 시 모델이 이 얇은 위키를 읽는다. 본문 전체를 끌어오면 컨텍스트가 폭발하므로 교훈만 압축해 싸게 주입한다. 기본은 꺼져 있다(OFF).

한 문장으로 구분

Rollout = “이 세션에 뭐가 있었나”의 원본 재생 로그 · state = “내 세션들이 뭐가 있나”의 SQLite 색인 · Memories = “과거에서 뭘 배웠나”의 학습 위키. 셋은 파일도, 포맷도, 수명도 다르다.


핵심 정리

시스템한 줄 정체모델과 닿는 시점
Rollout사건별 재생 로그(JSONL)매 turn append → 다음 세션에 통째 주입
state스레드 메타 색인(SQLite)닿지 않음(앱 UI/CLI 조회용)
Memories압축된 장기 교훈(MD)다음 세션 시작 시 주입(기본 OFF)

state는 SQLite 4개 파일로, Memories는 ~/.codex/memories/ 산출물로 나뉜다.

설계 원칙 체크 포인트:

  • Rollout이 진실 원본, state는 파생 캐시 — 손상 시 rollout 재스캔으로 복구
  • Rollout 파일명은 콜론(:) 금지 → 하이픈(-)으로 대체(FS 호환)
  • resume 시 파일에서 처음 만난 SessionMeta의 id를 thread_id 정본으로 사용
  • Memories는 교훈만, 사용 시 <oai-mem-citation>으로 출처 표기

실제 예시

Rollout 한 줄의 구조

한 줄은 { "timestamp", "type", "payload" } 구조다. typesession_meta / response_item / inter_agent_communication / compacted / turn_context / event_msg 중 하나이며, 첫 줄은 항상 session_meta(스레드 ID·cwd·CLI버전·git정보 등)다. 검사는 jq -C . rollout-….jsonl.

직렬화·파일명 생성 코드

핵심은 두 가지다. ① 매 줄을 timestamp + item으로 flatten해 직렬화 후 flush. ② 경로를 연/월/일 폴더로 나누고 파일명의 콜론을 하이픈으로 치환.

무엇을 기록할지 거르는 게이트 (policy.rs)

모든 사건을 적지 않는다. is_persisted_rollout_item이 문지기다 — 재생에 필요한 본문만 통과시키고, 수명주기 잡음은 버린다.

flowchart TD
  E["사건 발생"] --> G{"is_persisted_<br/>rollout_item"}
  G -- "메시지·추론·함수호출·패치<br/>UserMessage·AgentMessage<br/>TokenCount·Turn시작/완료" --> Y["기록 "]
  G -- "스트리밍 델타·ExecBegin<br/>Other·CompactionTrigger" --> N["버림 "]
  • ResponseItem: 메시지/추론/함수호출/패치만 true. Other·CompactionTrigger는 false.
  • EventMsg: UserMessage·AgentMessage·TokenCount·TurnStarted/Complete·ContextCompacted만 true. AgentMessageContentDelta·ExecCommandBegin 등은 false.

state의 threads 테이블 (조회용 색인)

threads 테이블은 rollout을 역참조(rollout_path)하고 정렬·필터에 필요한 메타만 담는다. 진단 코드는 절대 DB를 생성/수리하지 않는 read-only다.

Memories 파이프라인 (2단계 추출)

스레드별로 교훈을 뽑고(1단계) → 전역에서 통합·중복제거(2단계) → 다음 세션에서 주입(read)한다.

flowchart LR
  RO["rollout 본문"] -- "스레드 한가" --> S1["1단계 추출<br/>Low·동시성8<br/>컨텍스트 70% truncate"]
  S1 --> RAW["raw_memory /<br/>rollout_summary"]
  RAW --> S2["2단계 통합<br/>Medium 서브에이전트<br/>diff 보고 삭제분 제거"]
  S2 --> MD["MEMORY.md /<br/>memory_summary.md"]
  MD -- "use_memories ON" --> RD["다음 세션 주입<br/>+ 출처 표기"]
  1. 1단계(extract, 스레드별): 스레드가 한가해지면 rollout 본문을 모델(ReasoningEffort=Low, 동시성 8)이 요약 → stage1_outputs.raw_memory/rollout_summary 저장. 입력은 컨텍스트의 70%로 truncate.
  2. 2단계(consolidate, 전역): 여러 스레드의 raw 메모리를 통합 서브에이전트(Medium)가 MEMORY.md/memory_summary.md로 정리·중복제거. 워크스페이스 diff(phase2_workspace_diff.md)를 먼저 읽고 삭제된 증거 기반 메모리는 제거.
  3. 주입(read): use_memories가 켜져 있으면 다음 세션에서 모델이 MEMORY.md를 읽고, 사용 시 출처를 표기.

Memories 게이트

기본 OFF([features] memories=true 필요). active/단명 세션 제외, 비밀 redaction, rate-limit 잔량이 임계 미만이면 백그라운드 패스 스킵, disable_on_external_context면 MCP/웹검색 쓴 스레드 제외.

직접 만들 때: 최소 rollout writer (개념 복제)

append-only 기록 + policy 게이트 + 첫 session_meta.id를 정본으로 쓰는 resume — 핵심 3요소만 담은 재현이다.


요약 & 셀프체크

3줄 요약:

  1. Rollout(JSONL 속기록)은 매 turn 선별 append되고, 다음 세션 resume 시 대화 이력으로 통째 복원된다.
  2. state(SQLite)는 빠른 조회용 색인일 뿐 모델과 직접 닿지 않으며, 망가지면 rollout에서 재구축한다.
  3. Memories(MD 위키)는 본문이 아닌 교훈만 2단계로 압축해 다음 세션에 싸게 주입한다(기본 OFF).

스스로 답해보기:

  • 같은 사건도 Rollout엔 남고 state엔 안 남을 수 있다. 왜? (힌트: 진실 원본 vs 파생 색인, policy 게이트)
  • Rollout 파일명에 콜론(:) 대신 하이픈(-)을 쓰는 이유는?
  • Memories가 rollout 본문 전체를 끌어오지 않고 “교훈만” 저장하도록 설계한 이유는?

연결

CX_개요 · _분석축_루브릭

근거 파일 (소스 교차검증)

  • rollout/src/recorder.rs — RolloutRecorder, Create/Resume params, precompute_log_file_info(경로·파일명), JsonlWriter(RolloutLineRef·flush), load_rollout_items/get_rollout_history(resume)
  • rollout/src/policy.rs — is_persisted_rollout_item / should_persist_response_item / should_persist_event_msg (기록 선별)
  • rollout/src/lib.rs — SESSIONS_SUBDIR, INTERACTIVE_SESSION_SOURCES, 공개 API
  • protocol/src/protocol.rs — RolloutLine, RolloutItem(enum tag/payload), SessionMeta, SessionMetaLine, GitInfo
  • state/src/lib.rs — DB 파일명 상수(state_5/logs_2/goals_1/memories_1), SQLITE_HOME_ENV, SQLite 버전 assert
  • state/src/runtime.rs — RuntimeDbSpec(STATE/LOGS/GOALS/MEMORIES_DB), path=codex_home.join, StateRuntime::init
  • state/src/migrations.rs — 4개 Migrator, runtime_migrator(ignore_missing)
  • state/src/audit.rs — ThreadStateAuditRow, read-only SELECT
  • state/migrations/0001_threads.sql — threads 테이블 스키마
  • state/memory_migrations/0001_memories.sql — stage1_outputs / jobs 테이블
  • memories/write/src/lib.rs — memory_root, 산출물 파일명, stage_one/stage_two 상수
  • memories/write/src/storage.rs — raw_memories.md / rollout_summaries 재구성, file_stem
  • memories/write/src/prompts.rs — stage_one_input / consolidation 프롬프트 렌더, 70% truncate
  • memories/read/src/lib.rs, usage.rs, citations.rs — memory_root, MemoriesUsageKind, citation 파싱
  • thread-store/src/store.rs, lib.rs — ThreadStore 트레이트(create/resume/append/load_history), InMemory vs Local
  • /mnt/d/6study/10_프레임워크분석/_원문아카이브/codex/62_memories.md — 공식 Memories 문서(OFF 기본, use_memories/generate_memories, 저장 위치, /memories)
  • 경로 루트: /home/seunghyeong/harness-work/codex/codex-rs/