Claude Code を作って学んだこと: プロンプトキャッシュがすべて
エンジニアリングの世界ではよく「キャッシュがすべてを支配する (cache rules everything around me)」と言われますが、エージェントについても同じことが当てはまります。
Claude Code のような長時間稼働するエージェント型プロダクトを実現可能にしているのが プロンプトキャッシュ です。これにより、過去のラウンドトリップでの計算を再利用でき、レイテンシとコストを大幅に削減できます。
Claude Code では、ハーネス全体をプロンプトキャッシュを軸に構築しています。プロンプトキャッシュのヒット率が高いほどコストが下がり、サブスクリプションプランでより寛大なレート制限を提供できるようになります。そのため、私たちはプロンプトキャッシュのヒット率にアラートを仕掛け、もし低すぎれば SEV を宣言します。
これは、私たちが大規模にプロンプトキャッシュを最適化するなかで学んだ(しばしば直感に反する)教訓です。
プロンプトはキャッシュを意識してレイアウトする

Claude Code のシステムプロンプトは、安定した部分はキャッシュに残り、会話そのものだけがターンごとに伸びていくように構成されています。
プロンプトキャッシュはプレフィックスマッチングで動作します。API はリクエストの先頭から各 cache_control ブレークポイントまでをキャッシュします。つまり、要素を並べる順序が極めて重要であり、できる限り多くのリクエストが同じプレフィックスを共有するようにしたいのです。
そのための最良の方法は、静的なコンテンツを先に、動的なコンテンツを後ろに置くことです。Claude Code では次のような構成になっています。
- 静的なシステムプロンプト とツール(グローバルにキャッシュ)
- CLAUDE.md(プロジェクト内でキャッシュ)
- セッションコンテキスト(セッション内でキャッシュ)
- 会話メッセージ
こうすることで、できるだけ多くのセッションがキャッシュヒットを共有できるようになります。
ただし、このアプローチは意外なほど壊れやすいものです。私たちもさまざまな理由でこの順序を壊してきました。たとえば、静的なシステムプロンプトに詳細なタイムスタンプを入れてしまったり、ツール定義の順序を非決定的にシャッフルしてしまったり、ツールのパラメータを更新してしまったり(たとえば Agent ツールが呼び出せるエージェントを変更するなど)といったケースです。
更新はメッセージで行う
プロンプトに含めた情報が古くなる場面は、たとえば時刻を載せていたり、ユーザーがファイルを変更したりしたときに発生します。プロンプトを更新したくなるかもしれませんが、それはキャッシュミスを引き起こし、ユーザーにとって非常に高コストになりかねません。
代わりに、エージェントの次のターンのメッセージとしてその情報を渡せないか検討してみてください。Claude Code では、次のユーザーメッセージやツール結果のなかに <system-reminder> タグを追加し、更新された情報をモデルに伝えることでキャッシュを維持しています。
セッションの途中でモデルを切り替えない
プロンプトキャッシュはモデルごとに固有のものであり、これがプロンプトキャッシュの計算を非常に直感に反するものにしています。
たとえば、Opus との会話が10万トークンに達した時点で、比較的簡単に答えられる質問を投げたい場合、Haiku に切り替えた方が高くつくことがあります。Haiku 用にプロンプトキャッシュを再構築する必要があるためです。
どうしてもモデルを切り替える必要がある場合は、サブエージェントを使うのが最善です。先ほどの例を拡張すると、Opus に対して別のモデル向けに「ハンドオフ」メッセージを準備するように促すサブエージェントをデプロイすればよいのです。私たちも Claude Code の Explore エージェントでこれをよく行っており、こちらは Haiku を使っています。
セッションの途中でツールを追加・削除しない
会話の途中でツールセットを変更することは、プロンプトキャッシュを壊してしまう最もよくある方法の一つです。一見すると直感的に思えるかもしれません。今この瞬間に必要だと思うツールだけをモデルに与えるべきだ、と。しかしツールはキャッシュされるプレフィックスの一部なので、ツールを追加・削除すると会話全体のキャッシュが無効になります。
Plan Mode をキャッシュ前提で設計する
Plan Mode は、キャッシュの制約を踏まえて機能を設計した好例です。直感的なアプローチは、ユーザーが Plan Mode に入ったらツールセットを読み取り専用のものだけに差し替える、というものですが、それではキャッシュが壊れてしまいます。
代わりに、私たちは すべての ツールをリクエストに常に含めたうえで、EnterPlanMode と ExitPlanMode 自体をツールとして扱っています。ユーザーが Plan Mode をオンにすると、エージェントは Plan Mode に入っていること、そして指示内容(コードベースを探索する、ファイルを編集しない、計画ができたら ExitPlanMode を呼ぶ)を説明するシステムメッセージを受け取ります。ツール定義そのものは一切変わりません。
これにはおまけの利点もあります。EnterPlanMode はモデル自身が呼び出せるツールなので、難しい問題を検出したときには、キャッシュを壊すことなく自律的に Plan Mode に入ることができるのです。
ツール検索で削除ではなく遅延読み込みを行う
同じ原則は私たちの tool search tool にも当てはまります。Claude Code は何十もの MCP ツールを読み込めますが、それらすべてを毎回のリクエストに含めるのはコストがかかりますし、会話の途中で削除するとキャッシュが壊れてしまいます。
私たちの解決策は defer_loading です。ツールを削除する代わりに、軽量なスタブ(ツール名だけと defer_loading: true)を送り、必要に応じてモデルがツール検索を通じて「発見」できるようにしています。完全なツールスキーマは、モデルがそれを選択したときに初めて読み込まれます。同じスタブが常に同じ順序で存在するため、キャッシュされるプレフィックスは安定します。
私たちの API を通して tool search tool を使えば、これをよりシンプルに実現することもできます。
キャッシュを壊さずにコンパクション (compaction) する

コンテキストウィンドウが埋まると、Claude Code はキャッシュ済みの呼び出しをフォークして会話を要約し、その要約を元のメッセージの代わりに置いて再開します。
コンパクション は、コンテキストウィンドウを使い果たしたときに行われる処理です。それまでの会話を要約し、その要約を持って新しいセッションを続けます。
コンパクションはプロンプトキャッシュと相互作用しますが、その扱いを間違えやすいのです。会話をコンパクションするには、要約を書かせるためにモデルへ会話全文を送る必要があります。最も単純な方法は、独自のシステムプロンプト(「これを要約して」のようなもの)を持ちツールを付けない別の API 呼び出しを行うことですが、まさにそこにコストの罠があります。プロンプトキャッシュは、リクエストのプレフィックスが先頭からバイト単位でキャッシュ済みのものと一致したときにのみ適用されます。メインの会話はあるシステムプロンプトとツールセットの下でキャッシュされていますが、要約呼び出しは別のシステムプロンプトを使い、ツールも持たないため、最初のトークンの時点でプレフィックスが分岐し、キャッシュは一切効きません。送り込む会話全文に対して、キャッシュなしの満額のインプットレートを支払うことになり、しかも会話が長いほど(つまりコンパクションがそもそも必要な状況であるほど)、その一回の呼び出しが高くついてしまいます。
解決策: キャッシュセーフなフォーク
私たちはコンパクションを実行するとき、親の会話と まったく同じ システムプロンプト、ユーザーコンテキスト、システムコンテキスト、ツール定義を使います。親の会話メッセージを先頭に並べ、最後に新しいユーザーメッセージとしてコンパクション用のプロンプトを追加します。
API から見ると、このリクエストは親の最後のリクエストとほぼ同一に見えます。プレフィックスもツールも履歴も同じなので、キャッシュ済みのプレフィックスが再利用されます。新しいトークンはコンパクションプロンプトそのものだけです。
ただし、これはコンテキストウィンドウのなかにコンパクションメッセージと要約の出力トークンを収める余地を確保するため、「コンパクションバッファ」を確保しておく必要があるということでもあります。
コンパクションは扱いが難しいですが、幸いなことに、皆さんがこの教訓を自分で学ぶ必要はありません。Claude Code から得た学びをもとに、私たちは コンパクション を API に直接組み込みました。これらのパターンを自分のアプリケーションでも適用できます。
学んだこと
エージェントを構築するうえでプロンプトキャッシュを最適化する際に役立つと感じたパターンをいくつか紹介します。
- プロンプトキャッシュはプレフィックスマッチである。 プレフィックスのどこか一箇所でも変更があれば、その後のすべてが無効化されます。システム全体をこの制約を中心に設計してください。順序を正しく整えれば、キャッシュ機能のほとんどはタダで手に入ります。
- システムプロンプトを変更するのではなくメッセージを使う。 Plan Mode に入る、日付を変える、といった操作のためにシステムプロンプトを編集したくなるかもしれませんが、それらは会話中のメッセージに差し込む方が実際には優れています。
- 会話の途中でツールやモデルを変更しない。 ツールセットを変更するのではなく、状態遷移(Plan Mode のような)をモデル化するためにツールを使ってください。ツールを削除するのではなく、ツールの読み込みを遅延させてください。
- アップタイムを監視するのと同じようにキャッシュヒット率を監視する。 私たちはキャッシュ破壊にアラートを仕掛け、それをインシデントとして扱っています。キャッシュミス率がほんの数パーセント違うだけで、コストとレイテンシは劇的に変わりえます。
- フォーク操作は親のプレフィックスを共有する必要がある。 副次的な計算(コンパクション、要約、スキル実行)を実行する必要がある場合は、同じキャッシュセーフなパラメータを使い、親のプレフィックスでキャッシュヒットが得られるようにしてください。
Claude Code は最初の日からプロンプトキャッシュを軸に構築されています。エージェントを構築する際に最良の結果を得たいなら、皆さんも同じようにすることをおすすめします。
今すぐ Claude Code を始めましょう。
この記事は Claude Code チームの Member of Technical Staff である Thariq Shihipar が執筆しました。