Skip to content

CH 10 · 세션 로그가 곧 진실의 원천

전체 글자 수약 2,340자소요 시간약 12분선수 지식CH 09 메시지 흐름난이도개념 이해

본 장 목표

CH 09에서 "장부"(세션)에 대해 다루었습니다. 이번 장에서는 그것을 풀어헤쳐 봅니다. 왜 그것이 기계 전체의 진실의 원천인지 — 모델이 기억하는 것, 사용자가 보는 Trajectory, 내보낼 수 있는 대화 기록, 새로 갈라 만들 수 있는 fork까지 모두 이 단 하나의 로그로부터 파생되며, 그 로그는 오직 증가만 하고 결코 수정되지 않습니다.

한 줄 정리: 세션 로그 = 에이전트의 장부, 기억, 기록 보관소

Session의 공식 정의는 단단합니다. 세 가지로 나뉩니다.

  1. 추가 전용(append-only): 끝에 기록만 더하며, 기존 항목을 수정하거나 삭제하지 않습니다.
  2. 타입이 지정된 이벤트(typed events): 각 줄은 자유 형식의 텍스트가 아니라 "이벤트" 한 종류입니다. turn/start, user/message, assistant/message, tool/result, turn/end 등.
  3. 단일 진실의 원천(single source of truth): 에이전트 상호작용의 전체 히스토리에서, 진짜인 것은 이것뿐이며 나머지는 모두 이로부터의 투영입니다.

CH 09와 이어 붙이면 매우 자연스럽습니다. 지난 장에서 본 "메시지 흐름"의 각 단계는 사실 이 로그에 이벤트를 쓰는 행위입니다turn/start가 turn을 열고, step/start가 step을 시작하며, user/message가 사용자가 보낸 것을 기록하고, assistant/message가 모델의 응답을 기록하며, tool/result가 실행된 도구를 기록하고, turn/end가 turn을 닫습니다.

기억법: 흐름은 "무슨 일이 일어나고 있는지", 로그는 "일어난 일의 기록", 둘은 1대1로 대응합니다.

왜 "진실의 원천"인가

공식 문서의 표현은 다음과 같습니다.

모델 메시지 히스토리는 로그로부터 파생되며, 별도로 저장되지 않습니다.

의미는 이렇습니다. dsh에는 대화 기록의 두 번째 사본이 없습니다. "모델이 앞에서 한 말을 기억한다"고 느끼지만, 모델이 보는 히스토리는 로그로부터 투영됩니다. 보이는 Trajectory 뷰, 내보낼 수 있는 대화 기록, 열 수 있는 fork — 모두 동일한 로그로부터 렌더링됩니다.

세션 로그 = 진실의 원천: 모든 것이 여기서 파생된다(일러스트레이션)

  • 모델 대화 히스토리: deriveMessages()가 로그로부터 모델이 보는 Message[]를 투영합니다 — 따라서 "모델이 기억하는 것" = "로그에 있는 것";
  • Trajectory 뷰: CH 04에서 본 ASSISTANT / TOOL 타임라인은 그저 로그의 시각화일 뿐입니다;
  • 대화 기록 / 내보내기: 전체 대화 텍스트는 로그로부터 재구성됩니다;
  • fork 가지치기: 특정 히스토리 노드에서 새 세션을 시작합니다;
  • 텔레메트리 / 통계: 토큰 사용량, 소요 시간 등이 로그로부터 계산됩니다;
  • 영속화 파일: $DSH_HOME/sessionssession.jsonl.zstd.

왜 이렇게 설계되어야 할까요? 진실의 원천이 하나여야만 "UI에 보이는 것, 모델이 기억하는 것, 내보내는 것 — 세 사본이 서로 어긋나는 상황"이 절대 생기지 않기 때문입니다. 다른 모든 뷰는 동일한 로그의 투영이며, 규칙이 같으므로 항상 일관됩니다.

"모델이 보는 것이 곧 기록된 것": 설계의 축

이것은 dsh의 엄격한 규칙이며, 앞 장에서 언급하고 여기서 펼칩니다.

모델 요청에 도달하는 모든 것은 로그로부터 재구성 가능해야 하며, 런타임은 이를 불변식(invariant)으로 검사합니다.

여기서 두 가지 직접적인 결과가 따라 나옵니다.

  1. 모델이 볼 새로운 것을 더하려면, 새로운 이벤트 타입을 더해야 합니다. 예를 들어 Agent에게 주입한 컨텍스트를 보여 주고 싶다면, 로그를 우회해 요청에 직접 끼워 넣을 수 없습니다. 대신 새 세션 이벤트를 정의해 로그에 쓰고, 로그로부터 투영해 내보내야 합니다. 이렇게 하면 모든 단계가 추적 가능해지며, 홈페이지의 "모든 실행은 추적 가능"이라는 문장의 근본적 보장이 됩니다.
  2. 로그는 무손실입니다. 모델이 반환한 원시 스트리밍 청크까지 그대로 보존되므로(assistant/chunk), 재생을 토큰 단위로 충실하게 할 수 있고 UI도 똑같이 복원할 수 있습니다.

한 줄로 요약하면: 이 설계는 "추적 가능"이라는 슬로건이 아니라, 아키텍처적으로 필연적인 결과로 만들어 줍니다.

CH 04와의 간단한 교차 참조: 컨텍스트를 압축할 때 쓰던 /compact 명령은, 내부적으로 그저 "압축했다"는 행위를 로그에 기록하는 것일 뿐이며(compaction/* 이벤트), 로그로부터 모델에 더 다듬어진 형태를 투영합니다. 히스토리를 다시 쓰지 않습니다 — 원래 이벤트는 로그에 그대로 있고, 모델이 보는 투영만 재정렬될 뿐입니다. 그래서 압축 후에도 Agent가 "더 다듬어진 버전"을 기억하듯 보이지만, 원래 기록은 온전히 남아 있습니다.

실제로는 어떤 모습인가: 로컬 세션 로그

자신의 컴퓨터로 돌아옵니다. 이 컴퓨터를 예로 들면(CH 05에서 headless를 돌렸다면 여기에 이미 존재합니다).

C:\Users\mortal\.dsh\
├─ profiles\            ← 프로필 목록(CH 08의 "메뉴판")
├─ sessions\            ← 세션 로그가 여기 있습니다
│  ├─ --E-software-workspace-DeepSeek~0020harness~0020demo--\
│  │  └─ session-307edce2-...\session.jsonl.zstd   ← CH 05 headless 실행 기록
│  └─ --E-software-workspace-doubaowork-DeepSeekHarnessGuide--\
│     └─ session-f417b4dd-...\session.jsonl.zstd   ← 이 프로젝트에서 사용한 세션들
├─ storages\
├─ settings.yaml
└─ .credentials.yaml

몇 가지 짚어둘 점입니다.

  • 작업 영역별 디렉터리: 디렉터리 이름은 작업 영역 경로를 escape한 형태입니다(공백은 ~0020이 됨). 따라서 어디서 작업하며 어떤 세션이 만들어졌는지 한눈에 볼 수 있습니다.
  • 세션마다 폴더 하나, 그 안에 session.jsonl.zstd가 있습니다 — JSONL 줄 단위 추가 + zstd 압축 영속화 파일입니다.
  • CH 05의 "Ungrouped"와 맞물려: 이 파일들은 작업 영역별로 저장되지만, Web UI의 세션 목록은 headless 실행을 Ungrouped로 묶습니다 — 두 차원을 혼동하지 마십시오.
  • 이 파일들은 세션의 "기록 보관소"이므로 함부로 지우지 마십시오. 지우면 그 세션의 "기억"이 정말로 사라집니다.

fork: 로그로부터 새 세션을 자라게 하기

로그를 완전히 재생할 수 있으므로 자연스럽게 "어떤 지점에서 다시 자라게 하기"가 가능합니다. 공식 팀이 명명한 이름은 fork입니다. 안정된 위치 이전의 모든 이벤트를 복제해 새 세션의 시작점으로 삼고, 그 뒤로 갈라지는 방식입니다. 용도는 직관적입니다. 원래 세션을 건드리지 않고, 특정 히스토리 노드에서 새 길을 시험해 보고 싶을 때. 구체적인 사용법은 후반 실습 장에서 다루며, 여기서는 "그런 것이 존재하며, 그것이 가능하기는 바로 로그를 완전히 재생할 수 있기 때문"이라는 사실만 기억하면 됩니다.

이번 장에서 배운 것

아래 항목들을 스스로 점검할 수 있으면 합격입니다.

  • [ ] 세션 로그의 세 가지 특성(추가 전용 / 타입이 지정된 이벤트 / 단일 진실의 원천)을 말할 수 있다
  • [ ] CH 09의 메시지 흐름을 로그 이벤트와 1대1로 대응시킬 수 있다(turn/start, user/message, assistant/message, tool/result, turn/end)
  • [ ] "모델 히스토리는 로그로부터 파생되며 별도로 저장되지 않는다"를 설명하고, 왜 이것이 어긋나는 세 사본을 막는지 말할 수 있다
  • [ ] "모델이 보는 것이 곧 기록된 것"의 두 가지 결과(새로 더하기 = 새 이벤트 타입 추가; 무손실 로그는 토큰 단위 재생 가능)를 말할 수 있다
  • [ ] 로컬 세션 로그의 위치를 알고 있으며($DSH_HOME/sessions의 작업 영역별 디렉터리, session.jsonl.zstd), Web UI의 "Ungrouped"와의 차이를 안다

Open Source · MIT · Community Driven