Skip to content

CH 10 · セッションログ=真実の源

全文字数約 2340 字所要時間約 12 分前提CH 09 メッセージの流れ難易度理解重視

この章の目標

前章では「帳簿」(session)を扱いました。この章ではそれをほどきます:なぜそれが機械全体の真実の源なのか —— モデルが覚えていること、見える軌跡、書き出せる文字起こし、開けるフォーク、すべてがこの 1 つのログから派生し、ログ自身は追記のみで決して編集されません。

1 行で:セッションログ = Agent の帳簿、記憶、アーカイブ

Session の公式定義は強烈で、3 点に分解できます:

  1. 追記専用:末尾に記録を足すだけ、修正しない、過去のものを削除しない。
  2. 型付きイベント:各行はフリーフォーマットのテキストではなく「イベント」の一種 —— turn/startuser/messageassistant/messagetool/resultturn/end...
  3. 単一の真実の源:Agent のインタラクション履歴全体、これが唯一の実在;その他はすべてその投影。

前章とつなげると実にスムーズです:前章で見た「メッセージの流れ」、各ステップは実際にはこのログへイベントを書き込んでいる —— turn/start でターンが開き、step/start でステップが始まり、user/message はあなたが送ったものを記録し、assistant/message はモデルの返答を記録し、tool/result はツールの実行を記録し、turn/end でターンが閉じる。

記憶法:流れは「起きていること」、ログは「起きたことの記録」、1 対 1 対応。

なぜ「真実の源」なのか

公式の言葉:

モデルのメッセージ履歴はログから派生し、決して別途保存しない。

意味:dsh では会話記録の 2 つ目のコピーがない。「モデルは前に言われたことを覚えている」と思うかもしれませんが、モデルが見る履歴はログから投影されたもの;CH 04 で見た軌跡ビュー、書き出せる文字起こし、開けるフォーク —— すべてこの同じログから描画されています:

セッションログ=真実の源:すべてそこから派生(模式図)

  • モデル会話履歴:deriveMessages() がモデルに見せる Message[] をログから投影 —— 「モデルが覚えているもの」=「ログにあるもの」;
  • 軌跡ビュー:CH 04 で見た ASSISTANT / TOOL のタイムライン、ログの可視化に過ぎない;
  • 文字起こし/エクスポート:完全会話テキスト、ログから再生;
  • fork ブランチ:ある歴史的ノードから新しいセッションを伸ばす;
  • テレメトリ/統計:トークン使用量、所要時間、ログから算出;
  • 永続化ファイル:$DSH_HOME/sessionssession.jsonl.zstd

なぜこう設計しなければならないのか?真実の源が 1 つだからこそ、「UI に表示されているもの、モデルが覚えているもの、エクスポートされるもの —— 3 つのコピーが食い違う」が絶対に起きない。 他のビューはすべて同じログの投影で、ルールは一貫、永久に一貫。

「モデルに見えたものが記録されている」:設計の軸

これは前章でも触れた dsh の厳しいルールで、ここで展開します:

モデルリクエストに到達するあらゆるものは、ログから再構成可能でなければならず、ランタイムはこれを不変条件としてチェックする。

ここから直接の帰結が 2 つ:

  1. モデルに見せる新しいものを足したいなら、新しいイベント型を足す必要がある。 例えば Agent に注入された文脈チャンクを見せたいなら、ログを迂回して直接リクエストに詰め込むことはできず、代わりに新しいセッションイベントを定義し、それをログに書き込み、ログから投影して出します。これによりすべてのステップが追跡可能になり、ホームページの「すべての実行が追跡可能」の下層保証になります。
  2. ログはロスレス。 モデルが返した生のストリーミングチャンクすら保存され(assistant/chunk)、再生はトークン単位で忠実に再現でき、UI は正確にそれを復元できます。

1 行で:この設計は「追跡可能」をスローガンではなくアーキテクチャ上の必然にします。

CH 04 との簡単な相互参照:コンテキスト圧縮に使う /compact コマンドは、水面下では単に「圧縮した」というアクションをログに記録し(compaction/* イベント)、ログからモデルにより洗練された形を投影するだけです。履歴は書き換えない —— 元のイベントはログに残り続け、モデルに見せる投影だけが並べ替えられます。だから圧縮後も Agent が「洗練された版」を覚えているように見えても、元の記録は依然として完全なのです。

本当の姿:ローカルのセッションログ

自分のマシンに戻ります —— この PC を例に(CH 05 で headless を走らせた後、ここにあります):

C:\Users\mortal\.dsh\
├─ profiles\            ← profile 一覧(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

ポイントいくつか:

  • 作業ディレクトリ別のディレクトリ:ディレクトリ名は作業ディレクトリパスのエスケープ版(スペースは ~0020)、どこで作業中に生成されたセッションか一目で分かる;
  • 1 セッション 1 フォルダ、中には session.jsonl.zstd —— JSONL 1 行ずつ追記 + zstd 圧縮の永続化ファイル;
  • CH 05 の「未分類」と呼応:これらのファイルは作業ディレクトリ別に保存されますが、Web UI のセッション一覧は headless 実行を未分類にまとめます —— 2 つの次元を混同しないでください;
  • これらのファイルはセッションの「記憶アーカイブ」、気軽に削除しないでください。 削除すれば、そのセッションの「記憶」は本当に消えます。

fork:ログから新しいセッションを伸ばす

ログが完全に再生できるので、当然「ある位置から再生し直す」も可能 —— 公式チームはこの機能を fork と呼びます:安定した位置より前の全イベントを新しいセッションのオープニングとして複製し、そこから別々に行きます。使い方は直接的:元のセッションを触らずにある歴史的ノードで新しいパスを試したいとき。使い方の詳細は後ほどの実操作章で扱います;今は「存在する、そして存在できる理由はまさにログが完全再生できるからだ」とだけ覚えてください。

この章で学んだこと

  • [ ] セッションログの 3 つの特徴(追記専用 / 型付きイベント / 単一の真実の源)を言える
  • [ ] 前章のメッセージフローをログイベントと 1 対 1 対応させられる(turn/start、user/message、assistant/message、tool/result、turn/end)
  • [ ] 「モデル履歴はログから派生し、別途保存されない」を説明でき、なぜこれが「3 つのコピーが食い違う」を防げるか言える
  • [ ] 「モデルに見えたものが記録されている」の 2 つの帰結を言える(新しいものを足す = 新しいイベント型を足す;ロスレスログはトークン単位再生可能)
  • [ ] ローカルのセッションログの場所($DSH_HOME/sessions の作業ディレクトリ別ディレクトリ、session.jsonl.zstd)を知り、Web UI の「未分類」との違いを知っている

Open Source · MIT · Community Driven