第 37 章:階層型圧縮
「古いものを圧縮すればいい」——ところが 40 ステップ前に読んだそのファイルこそ、いま編集しているファイルだ。
5 分で要点掴む
- 経過時間は「もう不要」の代理指標であって、その実体ではない。代理は外れることがある
- 初回処理時に末尾からの距離で 3 段階へ分類:完全、意味要約、メタデータのスタブ
- 汎用圧縮器では本文を担うツールに下限を置く。専用の経時処理は別に適用できる
- 圧縮自体がモデル呼び出しを消費する。パスごとに上限を置かないと隠れたカスケードになる
- すべての書き換えで tool call と tool result の対応を保つ。崩せば Provider に拒否される
10 分パス:37.1-37.3 → 37.5 → Kocoro OSS Lab
公開ソースのスナップショット:実装例は 2026-07-27 に確認した Kocoro 公開
origin/mainのコミット4ec6772に基づきます。学ぶべき対象は不変条件であり、定数は最新ソースで再確認してください。
37.1 「まだ編集中だったファイル」の話
Agent がリファクタリングの 30 反復目にいます。ずっと前——3 反復目——に internal/auth/session.go へ file_read を実行しました。タスク全体がそのファイルについてのものです。その結果の後ろには、いま Tool Result を含む Message が 27 件あります。
最初のコンテキスト逼迫処理はそれを古いツール結果だと判断し、一律の経過時間ベース圧縮によってメタデータへ剥ぎ落とします。
[file_read: internal/auth/session.go — 412 行, 18.2 KB]
規則どおりには正しく、効果としては壊滅的です。モデルは編集対象のファイルを手放しました。だからもう一度読みます。18 KB が Prompt へ戻り、節約したトークンはすべて返上、加えて往復が 1 回。そして数反復後、その読み取りもまた古くなり、同じことが起きます。
規則は経過時間について間違えていません。間違えたのは経過時間が何を意味するかです。経過時間は「必要になる見込みが低い」の代理指標で、大半のツール出力ではその代理が成り立ちます。いま手を入れているファイルでは、まったく成り立ちません。
37.2 末尾からの距離による 3 段階
この梯子の目盛りは、各 Result の後ろに Tool Result を含む Message が何件あるかです。
Tier 3——最新 8 件のツール結果メッセージは手つかず。 これはワーキングメモリです。モデルが能動的に推論している対象で、触るコストのほうが節約より高くつきます。
Tier 2——距離 8 から 19 は圧縮するが実体は残す。 2,000 文字を超える結果はモデル呼び出しで意味要約にします。それ未満、あるいは要約器が使えないときは、機械的な先頭・末尾の切り出しにフォールバックします。どちらの経路でも、内容は縮小形で生き残ります。
Tier 1——距離 20 以遠はメタデータのみ。 ツール名、引数、サイズ。何が起きたかを知るには足り、再構成するには足りません。
圧縮後の Tier 2 出力は 300 文字で頭打ちです。攻めた値であり、意図的です。その距離では、その結果の仕事は何を学んだかをモデルに思い出させることであって、教え直すことではありません。
この階段を読むうえで重要な実装上の注意があります。ネイティブな tool_result ブロックでは、これは最初に触れた時点での分類であり、結果を継続的に下段へ運ぶベルトコンベアではありません。Tier 2 で一度処理した結果には CompressedTier=2 が付き、その後に距離が 20 を超えても、後続処理は同じバイト列を保ちます。したがって Tier 1 は「初めて処理された時点ですでに距離 20 以上だった未圧縮の結果」を意味し、「すべての結果が最後はメタデータになる」という意味ではありません。
最初の分類時点で、劣化の形に注目してください。線形ではありません。新しい内容は無傷、中年の内容は形式を失って意味を保ち、古い内容は骨格だけを保ちます。各段で変わるのは「どれだけ」ではなく「何を」残すかです。
37.3 37.1 を直す下限
Tier 1 は大半のツールに正しく、ある一群には誤りです。本文こそが呼び出した理由であるツール群です。
この汎用圧縮処理の中では、file_read、grep、glob、directory_list、そして browser_* に一致するものに Tier 2 の下限があります。未圧縮の結果が距離 20 以降で初めて処理されても、先頭・末尾の切り詰めまでで止まり、メタデータのスタブには落ちません。3 反復目に読んだファイルも、この処理後は冒頭と末尾を保持します。
理由は、これらのツールにメタデータのスタブを当てると、その呼び出しを価値あるものにしていた当のものを破壊するからです。[grep: "handleRequest" — 47 件一致] は検索が起きたことを教えますが、何に一致したかは一切教えません。そして grep を走らせる唯一の理由がそれです。
ブラウザ系ツールが同じ理由でこの集合に入るのは、はっきり書く価値があります。まだ操作対象となるページスナップショット自体がタスクのペイロードです。 Web フローを進める Agent が必要とするのは、ページに何があったかであって、ページを観測したという事実だけではありません。
ただし、この下限は全体を通じた保持保証ではありません。通常のリクエスト組み立てでは、第 45 章のブラウザ・GUI 観測ウィンドウが別に動き、最新 3 件より古いテキスト観測を一行のスタブへ置き換えます。Tier 2 の下限は汎用の逼迫時圧縮から本文を守り、観測ポリシーは古い画面がいつ操作価値を失うかを判断します。
一般化できる判定:この結果のメタデータ・スタブで、モデルは先へ進めるか。それともツールの再実行を強いるか。 再実行しか手がないなら、それは圧縮ではなく、コストの先送りと往復 1 回の追加です。
37.4 圧縮は無料ではない
Tier 2 の意味要約はモデル呼び出しです。1 パスで 12 件圧縮すれば、コンテキストを節約するためにモデル呼び出しを 12 回増やしたことになります。節約したコンテキストより高くつくことは容易にあります。
そこで 1 パスあたりの意味圧縮は 2 回に制限されます。Tier 2 帯の残りは機械的な先頭・末尾へフォールバックし、こちらはコストゼロです。上限は成功回数ではなく試行回数に掛かっているので、壊れた要約器が黙って予算を焼き切ったうえで結局圧縮もされない、という事態を防げます。
並走する 2 つ目の除外もあります。一部のツールは micro-compaction を丸ごとスキップします。論理は Tier 2 の下限と同じです。ページのスナップショットを散文へ要約すると、モデルがこれから辿るはずだった構造が失われます。
持ち帰る価値のある規則:モデル呼び出しを消費する圧縮には予算が要り、その予算は試行に掛ける。 さもないと要約器の調子が悪い日に、コンテキスト最適化がコストの増幅器へ変わります。
37.5 書き換えは Turn を壊しうる
どちらの Tier もメッセージ内容をインプレースで書き換えます。壊れ方は 2 通りあります。
対応関係。 tool_use ブロックと対応する tool_result は一組です。片方だけ書き換える——あるいは呼び出しが残っているのに結果を落とす——と、Provider はリクエスト全体を拒否します。だから圧縮は開始前に tool_use_id からツール名と引数へのマップを作る必要があります。各結果が何であったかを知らなければメタデータのスタブは書けませんし、組は無傷で残さなければなりません。
キャッシュ。 インプレースの書き換えはすべて、そのメッセージ以降の Prompt Cache を無効化します。これは避けられません。内容を変えるのが目的だからです。しかし帰属可能でなければなりません。書き換えごとに、どの Tier が行ったかを示すタグ付きの cache-compaction イベントを出します。そうすればキャッシュヒット率が落ちたとき、Tier 1 なのか Tier 2 なのか、まったく別の何かなのかを切り分けられます。
計装のない書き換えこそ、キャッシュのデバッグを不可能にするものです。ヒット率が落ちたのは見えるのに、十数ある Prompt 整形機構のどれが原因か探る手段がありません。第 39 章がこの規律の全体です。
37.6 スナップショットの根拠
| 観測 | 4ec6772 のソース位置 |
|---|---|
| 最新 8 件のツール結果は完全保持 | loop.go L2565 |
| Tier 2 の圧縮出力は 300 文字で頭打ち | loop.go L2566 |
| Tier 1 は距離 20 から | loop.go L6104 |
| 本文を担うツールの Tier 2 下限 | loop.go L6095 |
| Tier 2 の圧縮経路 | loop.go L6211 |
| ネイティブ Tier 2 ブロックはバイト安定性のため処理後に終端扱い | loop.go L6224 |
| 意味要約の閾値 2,000 文字 | microcompact.go L19 |
| 1 パスあたり意味要約の試行上限 2 | microcompact.go L23 |
| micro-compaction をスキップするツール | microcompact.go L40 |
これらは特定時点の実装例であり、普遍的な契約ではありません。
37.7 階層化が誤ったモデルになるとき
経過時間ベースの階層化は、関連性が単調に減衰することを前提にします。そうでないとき、このモデルは破綻します。
長時間の比較タスクでは、最後に結果 #2 と結果 #40 を並べて見る必要があるかもしれません。デバッグセッションでは、20 ステップ探索した後で最初のスタックトレースへ戻るかもしれません。どちらでも「新しいほど関連が高い」という前提は端的に偽で、下限も救ってくれません。下限が守るのは先頭・末尾であって、中間ではありません。
それがあなたのワークロードなら、答えはより良い Tier 境界ではなく退避です。ディスクへ書き、モデルに意図して取りに行かせる。トランスクリプト上の位置から「後で何を欲しがるか」を当てにいくのをやめる、ということです。
もう 1 つ。短いセッションを階層化しないこと。Tier 3 のウィンドウより短ければ圧縮する対象はなく、それでも機構を走らせるのは失敗しうるコードパスを 1 本増やすだけです。
37.8 よくある落とし穴
本文を担うツールに下限がない。 37.1 です。モデルは読み直し、トークンを節約するためにトークンと往復を払います。
意味圧縮に上限がない。 12 回の要約呼び出しに値しないコンテキストを節約するために、要約呼び出しを 12 回。
上限を試行ではなく成功に掛ける。 壊れた要約器が永遠にリトライし、予算に一度も触れません。
ツール呼び出しの対応を壊す。 Provider がリクエスト全体を拒否し、エラーはあなたの圧縮コードではなくメッセージ添字を指します。
書き換えを計装しない。 キャッシュヒット率が落ちても、どの機構が原因かテレメトリが何も言いません。
圧縮済みの内容を再び書き換える。 300 文字の Tier 2 要約にもう 1 パスは要りません。再度変えればキャッシュ接頭辞も汚れます。何を縮小済みか記録してください。
Kocoro OSS Lab(10 分)
isTier2FloorToolを読み、browser_が列挙ではなくプレフィックスで一致している点に注目します。新しい Playwright ツールが増えたとき、汎用圧縮器の中で何が得られるか考えてください。compressOldToolResultsが圧縮前にtool_use_idマップを作る箇所を探し、それが無い場合の Tier 1 スタブがどうなるか考えます。- 自分の Agent のツールを列挙し、各々に印を付けます。20 ステップ後もその本文に判断価値があるか、メタデータで足りるか。そのリストがあなたの下限です。
ここだけは押さえる
- 経過時間は「もう不要」の代理であり、代理は外れる。 編集中のファイルは古く、かつ不可欠です。
- 量だけでなく種類で分類する。 初回処理時に完全、意味だけ、輪郭だけを選び、書き換え後のバイト列を安定させます。
- 本文を担うツールには下限が要る。 grep のメタデータ・スタブは、その grep が存在した理由を破壊します。
- 圧縮自体に、試行ベースの予算を。 さもないと節約がコスト増幅に変わります。
- 対応を保ち、書き換えを計装する。 前者はリクエストを壊し、後者はキャッシュをデバッグする能力を壊します。
次章:第 38 章は結果のサイズから Schema のサイズへ——Prompt を埋めるもう半分の話です。