クラウドエージェントがプロンプトキャッシュを外していた理由と、1時間のTTLで変わったこと
私たちのクラウドエージェントは、本番での最初の19ターンで入力の42.6%をプロンプトキャッシュから読み込み、大きなキャッシュ書き込みはどれも、キャッシュの寿命である5分を超える間隔のあとに起きていました。1時間のTTLにすると、計測用の9回の呼び出しからなるセッションのヒット率は71.8%から85.7%(最初の呼び出し以降は93.9%)に上がり、費用は8%下がりましたが、5分以内に終わるチャットでは約23%高くなります。計測の方法、4つのブレークポイントの置き場所、そして長いTTLが割に合うのはどんなときかを紹介します。

このページの内容
Foxlのクラウドエージェントは、本人のコンピューターの電源が切れているあいだも、スマートフォンやWebから届くチャットに答えます。本番環境での最初の19ターンで、入力のうちプロンプトキャッシュから読み込めたのは42.6%でした。大きなキャッシュ書き込みを調べると、どれも5分を超える間隔のあとに起きていました。5分は、キャッシュエントリーのデフォルトの寿命です。スマートフォンから返信する人は、それより長く間を空けることがよくあります。
そこでエージェントのキャッシュエントリーの寿命を1時間にし、計測用の9回の呼び出しからなるセッションをもう一度実行しました。ヒット率は71.8%から85.7%に上がり、必ず書き込みになる最初の呼び出しを除けば93.9%でした。1時間の書き込みは基本入力価格の2倍かかるにもかかわらず、セッションの費用は全体で8%、入力では15%下がりました。5分以内に終わるチャットでは逆の結果になります。書き込みのたびに2倍を払うのに、それを一度も読み返さないからです。間隔を取り除くと、同じ9回の呼び出しは、1時間の設定のほうが5分の設定より約23%高くつきました。この変更はFoxl v0.9.3-beta.5で出荷しました。この方法は、AnthropicでもAmazon Bedrockでも、Messages APIを通じてClaudeを呼び出すあらゆるエージェントに当てはまります。
プロンプトキャッシュのヒット率はどう測るのか
ヒット率とは、1回の呼び出しの入力トークンのうち、プロバイダーが改めて処理せずにキャッシュから読み込んだ割合で、式にするとcache_read / (uncached input + cache_read + cache_write)です。キャッシュはブレークポイント単位で働きます。キャッシュブレークポイントとは、リクエストのあるブロックに付けるcache_controlのマーカーで、そのブロックまでのすべて、つまりプレフィックスをキャッシュするようプロバイダーに求めるものです。エントリーはTTL(time to live、有効期間)のあいだ残り、デフォルトは5分です。同じプレフィックスで始まる次のリクエストは、その分を改めて支払う代わりにエントリーを読み込みます。Bedrockではブレークポイントをチェックポイントと呼びます。
Messages APIのレスポンスはどれも3つの入力カウンターを返し、Anthropicのページにはその関係がtotal_input_tokens = cache_read_input_tokens + cache_creation_input_tokens + input_tokensと書かれています。つまりinput_tokensが数えるのは、キャッシュから読み込まれもせず、キャッシュに書き込まれもしなかった分だけです。cache_readをinput_tokensで割ると、まったく別の、ずっと大きな数字になります。BedrockのConverse APIも、自身のフィールドについて同じことを記載しています。キャッシュが有効なとき、inputTokensは「キャッシュされていない入力トークンだけを表す」とあります。
私たちは実行ごとに2つの数字を報告します。全呼び出しでのヒット率には、キャッシュが切れたあとの最初の呼び出し、つまり必ず書き込みになる呼び出しが含まれます。最初の呼び出し以降のヒット率は、キャッシュが埋まったあとの会話のコストを示します。プロンプトの一部はどのユーザーでも同じ(後述する1つ目のブレークポイント)なので、本番では、ある人の最初の呼び出しが、別の人の会話で書き込まれた分を読み込むことがあります。それを示せるのは全呼び出しの数字だけです。私たちのテストは1人のユーザーとして実行したので、これは測れていません。
本番のベースラインは、クラウドエージェントに関するFoxlの課金記録から取りました。公開以来8つの会話で19ターン、キャッシュからの読み込みが35,054トークン、書き込みが47,173トークン、キャッシュなしが72トークンです。35,054 / (35,054 + 47,173 + 72)は42.6%になります。エージェントは読み込んだ量より多く書き込んでいました。少なくとも12,119トークン(47,173から35,054を引いた分)、つまりキャッシュのために支払った分の約4分の1は、一度も読み返されませんでした。
チャットエージェントが自分のキャッシュを外す理由
FoxlのアプリとBedrockのあいだにあるサーバー、Foxlのリレーは、もともとすべてのClaudeリクエストに3つのブレークポイントを付けていました。最後のツール、システムプロンプト、最後のユーザーメッセージの3か所で、いずれもデフォルトの5分のTTLです。呼び出しの間隔が短ければヒットしました。返答の15秒後に送った追加の質問は、3,616トークンを読み込み、44トークンを書き込みました。
大きな書き込みは、どれも5分を超える間隔のあとに起きていました。前の返答から26分後に送ったメッセージや、前回から2時間後に開いたチャットは、プレフィックス全体、つまりツールとシステムプロンプトの約3.6Kトークンを書き直しました。1時間のキャッシュについてのAWSのガイダンスは、ほぼこのケースそのものを挙げています。「ユーザーとの長いチャットの会話を保存していて、そのユーザーが通常、次の5分以内には応答しないと予想される場合」です。
ヒットすると、エントリーは無料で更新されます(「キャッシュされた内容が使われるたびに、追加費用なしでキャッシュが更新されます」)。そのため、ツールループをこなしているエージェントが5分の制限にぶつかることはめったにありません。1回の呼び出しやツールに5分以上かかる場合は別です。人とのチャットは、ターンとターンのあいだでこの制限にぶつかります。そして、そうしたチャットが時間の大半を過ごすのは、まさにそのあいだです。
4つのブレークポイントの置き場所
1つのClaudeリクエストに付けられるブレークポイントは最大4つです(Anthropicは「キャッシュブレークポイントは最大4つまで定義できます」と書き、Bedrockも表のすべてのClaudeモデルで4としています)。キャッシュされたプレフィックスはtools、system、messagesの順に読まれ、どこかが変わると、それより後ろはすべて無効になります。リレーのブレークポイントはすべて5分のものだったので、今ではクラウドエージェントが4つすべてを自分で、それぞれのTTL付きで設定します。リレーは、すでにブレークポイントを持つリクエストには手を加えず、自分のブレークポイントも追加しません。

- システムプロンプトの共有部分の終わり。システムプロンプトは、ユーザーごとのメモリが始まる
## Memory見出しの位置で分けています。ツールと、その見出しより上にあるもの(エージェントが何者か、クラウドで何ができて何ができないか)は、同じモデルを使うすべての人のすべての会話で同一なので、誰が始めた新しいチャットでも、まずそこを読み込むところから始まります。 - システムプロンプトの終わり。2つ目のブレークポイントには、その人自身のメモリと日付の行が加わります。日付の行には時刻ではなく日付だけが入るので、呼び出しのあいだでプレフィックスが変わることはありません。
- 直前のユーザーメッセージの、キャッシュ可能な最後のブロック。ここはちょうど前の呼び出しが書き込んだ位置なので、それを読み込めるかどうかはAPIのルックバックに左右されません。このブレークポイントがないと、APIは最新のブレークポイントから前のキャッシュエントリーをさかのぼって探しますが、その範囲には限りがあり(「ルックバックウィンドウは20ブロックです」)、ツール呼び出しの多いターンがあると、前回の書き込みが届かない位置に押し出されることがあります。このブロックの数え方は、Claudeをどこで呼び出すかによって違います。AnthropicのAPIは、連続する
tool_useブロックやtool_resultブロックの並びを1つの位置として数えますが、Bedrockのページには「約20のコンテンツブロック」としか書かれておらず、そうした例外はありません。 - 最新のメッセージの、キャッシュ可能な最後のブロック。これが、次の呼び出しが読み込む書き込みです。
4つとも同じTTLを使います。Bedrockは、1時間のエントリーがすべて先に来る限り、1つのリクエストで両方のTTLを受け付けます(「1時間のキャッシュエントリーは、どの5分のキャッシュエントリーよりも前に置く必要があります」)。4つすべてを同じTTLにしておけば、確認しなくてもこの条件は常に守られます。ブレークポイントを置くのはtext、image、document、tool_use、tool_resultのブロックだけで、これはAnthropicがキャッシュ可能なメッセージの内容として挙げているものです。
1時間のTTLを使うのは、Bedrockの表で「Supported TTL: 5 minutes, 1 hour」となっているClaudeモデルだけです。5分しかサポートしないモデルにこのフィールドを送ると「ValidationExceptionが発生する可能性がある」とBedrockは警告しているので、ほかのClaudeモデルにはttlなしで同じ4つのブレークポイントを付けます。Bedrock上のGPT-5.6以降には手を加えません。これらのモデルは独自のprompt_cache_breakpointフィールドを使い、同じページによれば「最小TTLは30分」だからです。
// the marker on each of the four blocks, on a model that takes it
"cache_control": { "type": "ephemeral", "ttl": "1h" }最小サイズもあります。Bedrockがチェックポイントをキャッシュするのは、それより前の全体がモデルの最小長に達している場合だけです。5.5のモデルでは512トークン、いくつかの古いモデルでは1,024または4,096トークンです。最小長に満たない場合、「推論自体は成功しますが、プレフィックスはキャッシュされません」。最小長が4,096トークンのモデルで3.6Kトークンのプロンプトを使うと、会話そのものがプレフィックスを最小長より大きくするまで、ツールとシステムのブレークポイントは何もキャッシュしません。提供するモデルごとに表を確認してください。
1時間のキャッシュは、節約分より高くつくのか
Anthropicの価格では、5分の書き込みは基本入力価格の1.25倍、1時間の書き込みは2倍、読み込みは0.1倍(Opus 5.5とSonnet 5.5では0.05倍)です。つまり1時間の書き込みは、5分の書き込みより基本価格の0.75倍分高くなります。この上乗せ分を、プレフィックスについては1時間に1回、各呼び出しの新しい内容については毎回支払います。その代わり、5分を超える間隔があっても、プレフィックス全体を1.25倍で書き直すことはなくなり、プレフィックスは読み込まれます。
これを測るため、クラウドエージェントの本番コードを、ローカルで動かした私たちのリレー経由で、実際のBedrockに対して実行しました。シナリオは、4ターンのチャット1つ(ツールで追跡する計画、追加の質問、ファイルの保存、そして6.5分後に最初のターンについての質問)と、そのあとの新しいチャット1つです。ツールを使うターンは複数の呼び出しになるので、Sonnet 5.5へのモデル呼び出しは合計9回になります。使用量はBedrockが返したストリームから読み取り、費用の列にはFoxl自身のSonnet 5.5のトークン単価を使っています。
| 設定 | キャッシュ読み込み | キャッシュ書き込み | ヒット率(全9回) | ヒット率(2-9回目) | 実行の費用 |
|---|---|---|---|---|---|
| 5分(リレーのブレークポイント) | 29,979 | 11,755 (5m) | 71.8% | 78.7% | $0.0538 |
| 1時間(4つのブレークポイント) | 35,859 | 5,974 (1h) | 85.7% | 93.9% | $0.0494 |
| 1時間+メモリファイル(後述) | 177,347 | 20,108 (1h) | 89.8% | 98.6% | $0.1219 |
差のほぼすべては、6.5分の間隔のあとの呼び出しによるものです。5分のエントリーでは、この呼び出しは5,873トークンのプレフィックスを書き直しました。1時間のエントリーでは5,750トークンを読み込み、133トークンを書き込みました。Bedrockはすべての書き込みをephemeral_1h_input_tokensとして報告したので、長いほうのTTLが黙って無視されたのではなく、実際に適用されたことがわかります。1時間の実行は5分の実行より5,880トークン多く読み込みました(35,859から29,979を引いた分)。そのうち5,750トークンはこの1回の呼び出しによるもので、残りの130トークンほどはブレークポイントを移したことによるものです。このシナリオには、3つ目のブレークポイントが想定している、ツール呼び出しの多いターンはありませんでした。キャッシュなしの入力は、どの実行でも28トークンでした。
5分の設定が本番では42.6%なのにこのテストでは71.8%になるのは、テストには6.5分の間隔が1回あるだけなのに対し、本番の19ターンは8つの会話に分かれ、26分や2時間の間隔があったからです。同じコードでもトラフィックが違えばスコアは大きく変わるので、ヒット率は同じシナリオの実行どうしでのみ比べてください。
間隔がなければ、結果は逆になります。同じ計測から間隔を取り除くと、5分の設定はプレフィックスを一度も書き直さないので、こちらのほうが安くなります。1時間の実行は全体で約23%、入力では約48%高くなります。書き込みのたびに基本価格の2倍を払ったのに、5分を過ぎてからそれを読み返すものが何もなかったからです。
Sonnet 5.5やOpus 5.5での自分のトラフィックについては、次の2つの数字を比べてください。
- 節約分:間隔のせいで書き直すことになっていたはずのプレフィックスのトークン1つにつき、基本価格の約1.2倍を節約できます(1.25の書き直しが0.05の読み込みに置き換わるため)。
- 上乗せ分:その1時間に書き込まれるトークンはすべて、基本価格の0.75倍分高くなります(1.25ではなく2)。
節約分のほうが大きければ、1時間のTTLが得になります。5分から60分の間隔があるチャットはたいていそうなり、1時間以内に始めた2つ目のチャットも同様です。5分以内に終わり、その1時間にほかに何もないチャットはそうなりません。スマートフォンから返信する人のために、私たちは1時間を選びました。呼び出しが数秒おきに届くエージェントなら、5分のほうがよい設定です。
呼び出しのたびに事前にクレジットを確保しているなら、TTLは確保額にも影響します。Foxlのリレーは最悪の場合を想定して確保するので、1時間のTTLを求めるリクエストは1時間の書き込み単価で確保されます。その単価(2倍)は5分の単価(1.25倍)の1.6倍なので、コンテキストが100KトークンのSonnet 5.5の呼び出しでは、入力分として$0.25ではなく約$0.40を確保します。実際の請求額は呼び出しで使った分のままですが、残高がほとんどない場合は、1回早く断られることがあります。
上限を決めるもの
上限を決めるのは新しい内容です。新しいユーザーメッセージも返答も、読み込まれる前に一度キャッシュに書き込まれるので、ブレークポイントをどう置いても、呼び出しが自分で追加するトークンを読み込むことはできません。1時間の実行では、前の実行の5分のエントリーが切れていたため最初の呼び出しは何も読み込まず、読み込んだ35,859トークンはすべて2回目から9回目の呼び出しによるものでした。これは、それらの呼び出しが受け取った約38,200の入力トークンの93.9%にあたります。残りの約2,300トークンはその実行での新しいメッセージと返答なので、このシナリオでは93.9%が上限ということになります。長いチャットほど、新しいトークン1つあたりの履歴が多いので、上限は高くなります。

変わらないプレフィックスが大きくなっても比率は上がりますが、それで何かが安くなるわけではありません。これがわかったのは、クラウドエージェントに、ユーザーのメモリファイルを含めるオプションが加わったときです。メモリファイルとは、Foxlのデスクトップアプリが自分のプロンプトに入れる4つのMarkdownファイル(SOUL.md、USER.md、MEMORY.md、AGENTS.md)のことです。スマートフォンやブラウザは、コンピューターが接続されているあいだにこれらをコピーし、デスクトップと同じコードで切り詰めて、クラウドへのメッセージごとに送ります。実際のファイル一式をこの方法で切り詰めると約41K文字、呼び出し1回あたりおよそ14K入力トークンになりました。3つ目の実行では、同じサイズのテスト用ファイルを使いました。
この実行では、最初の呼び出しがその人のプロンプトの17,619トークンを書き込み、以降の呼び出しはすべてそれを読み込み、最初の呼び出し以降のヒット率は98.6%に達しました。一方で、プロンプトが数倍大きいため、費用は$0.0494に対して$0.1219かかりました。キャッシュがなければ、同じ9回の呼び出しに$0.4187かかっていたはずです。3つの実行の中でヒット率は最も高く、請求額も最も大きかったのです。私たちが報告するすべてのヒット率の横に費用を添えるのは、このためです。
まだ測っていないこと
- 変更後の本番環境。2026年10月23日の時点で、変更をデプロイしてから本番で実行されたクラウドのターンはまだないので、本番の数字は42.6%だけです。変更後の数字は、実際のBedrockに対してローカルのリレーで測ったものです。
- ほかのモデル。実行したのはSonnet 5.5だけです。1時間のTTLを使うほかのClaudeモデルは、Bedrockの表と、Opus 5.5については以前に私たちのリレー経由で行った計測を根拠にしています。
- ほかのシナリオ。測ったのは1つのパターンの9回の呼び出しだけで、ツール呼び出しの多いターンは含まれていません。1時間以上放置されたチャットは最初の書き込みをもう一度支払うことになり、1時間は、どちらのプロバイダーでもClaude向けに提供されている最長のTTLです。
自分でClaudeのエージェントを動かしているなら、ヒット率はいちばん手軽に確かめられる指標です。呼び出しごとに3つのカウンターを合計し、読み込みを入力全体で割り、大きなプレフィックスを書き込んだ呼び出しそれぞれの直前に何があったかを見てください。5分を超える間隔のあとなら、1時間のTTLで解決します。このシナリオでの改善のほぼすべてがそうでした。数秒前の呼び出しのあとなら、2つの呼び出しのあいだでプレフィックスが変わったか、前回の書き込みがルックバックの届かない位置にあったかのどちらかで、後者は3つ目のブレークポイントで防げます。以前の記事では、サブエージェントに親のキャッシュを使わせ続ける方法を紹介しています。1つのターンの中で働く、同じプレフィックスのルールです。