2026年9月4日 金曜日
AI時短ラボ
検証· 約13

プロンプトキャッシュを壊さない会話組み立て──Gooseが「バイトを後から動かさない」設計に変えた理由

オープンソースAIエージェントGoose(Block)はv1.46.0で、リクエスト組み立てをappend-only(追記専用)に変更した。すでにプロバイダに送ったバイトは二度と動かさず編集しないことで、Anthropic・OpenAI等のプロンプトキャッシュのprefixを常に維持する。実装したPR本文には、検証用に新設した「prefix invariance」テストハーネスの中身まで書かれている。

プロンプトキャッシュを壊さない会話組み立て──Gooseが「バイトを後から動かさない」設計に変えた理由
執筆・編集:
目次

Block社(旧Square)発のオープンソースAIコーディングエージェント「Goose」が、v1.46.0(2026年8月12日公開)でリクエストの組み立て方そのものを作り替えた。変更点を一言でいうと、「一度プロバイダに送ったバイト列は、二度と動かさない」。地味な変更に見えるが、実装したPR #11022(2026年8月7日マージ)の本文を読むと、プロンプトキャッシュのヒット率という、エージェント運用のコストと速度に直結する問題への対処だと分かる。

なお、GitHubのblock/gooseは現在aaif-goose/gooseへのリダイレクトになっており(本記事執筆時点で確認)、リポジトリのAboutには「gooseはLinux Foundation傘下のAgentic AI Foundation(AAIF)の一部」と明記されている。Blockが開発を始めたプロジェクトが、その後Linux Foundation配下の団体に移管されたことがうかがえるが、移管の時期や経緯そのものについては公式アナウンスを見つけられておらず、リポジトリの現状表記から確認できた範囲にとどまる。

プロンプトキャッシュは「先頭が同じ」でないと効かない

AnthropicのClaude APIをはじめ、多くのLLM APIはプロンプトキャッシュ機能を持つ。同じ内容を繰り返し送るリクエストの「先頭部分(prefix)」が前回と一致していれば、そこはキャッシュから読み込まれ、トークン処理コストと応答時間が下がる——という仕組みだ。逆に言えば、リクエストの途中に少しでも差し込みや書き換えがあると、その地点から後ろのキャッシュはすべて無効になる。

エージェントは1つの会話の中で何度もLLMを呼び出す。会話が長くなるほど、この「先頭が毎回一致しているか」がコストに効いてくる。

何が壊れていたのか

PR本文の説明はこうだ。

"Implements #11019: request assembly is now append-only, so bytes already sent to a provider are never moved or edited and every provider's prompt cache keeps its prefix."

(実装内容:リクエストの組み立てをappend-only(追記専用)にした。これにより、すでにプロバイダに送ったバイト列は二度と動かされたり編集されたりせず、各プロバイダのプロンプトキャッシュはprefixを保ち続ける)

これまでのGooseは、ターンごとのコンテキスト情報(turn context)を「直近のユーザーメッセージに後から差し込む(splice)」という組み立て方をしていたようだ。差し込み先が最新のユーザーメッセージである以上、会話が進むたびにその差し込み位置・内容が変わり得る。結果として、前回送ったリクエストと今回送るリクエストの「先頭が完全に一致する」保証がなくなり、プロバイダ側のキャッシュがそのたびに切れていた可能性がある。

発端のIssue #11019が示す数字:キャッシュ読み込みは非キャッシュの約1/10の価格

今回のPRのもとになったIssue #11019(2026年8月6日起票)の本文には、この問題がなぜ「コストレバー」と呼べるのかという背景と、過去に個別対応した3件の修正の効果が具体的な数字つきで書かれている。

対象 対応PR キャッシュ再利用率の変化 コストへの影響
Anthropic向けprefix #10030 77% → 91% 入力コストが約42%減
OpenAI互換パス #10937 75.6% → 95.0% 記載なし
OpenAI Responsesスタック(gpt-5.x系) #10993 ―(append-onlyのキャッシュ仕様のため、turn-contextを動かさない対応) 記載なし

Issue本文はキャッシュの基本コストにも触れている。「プロバイダはキャッシュ読み込みを非キャッシュ時の約10%の料金で課金する」ため、キャッシュに優しいリクエスト形状かどうかが、セッションあたりのコストを何倍も左右するという。さらに、OpenRouter・LiteLLM向けの実装には「再配置した揮発性テール部分にcache_controlのbreakpointを置いてしまい、ツールループのたびに一度も読み戻されないキャッシュ書き込み(入力単価の1.25倍)を払い続けている」というバグも見つかっていたと記載されている。この問題は、リクエスト組み立て経路が約11箇所に分散し、そのうちテストがあったのは2箇所だけだったために、プロバイダごとに個別対応するしかなかった、とIssueは説明している。

解決策:ターンコンテキストを「専用のマーカー付きメッセージ」として永続化する

PR本文が説明する新しい設計は次の通りだ。

"The per-turn context block is persisted as an agent-only user message with a turnContext marker instead of being spliced into the latest user message, replacing the per-format relocation machinery."

(ターンごとのコンテキストブロックは、最新のユーザーメッセージに差し込まれるのではなく、turnContextマーカーを持つ「エージェント専用のユーザーメッセージ」として永続化される。これにより、フォーマットごとに存在していた再配置の仕組みを置き換える)

つまり、ターンコンテキストは会話履歴の中に独立した1つのメッセージとして固定される。以後、その内容が動かされることはない。会話を読む側(要約・検索・件数カウント・セキュリティスキャナー・プランナー・goose term runなど)は、このマーカー付きメッセージを「対話ではなく運用上のコンテキスト」として無視するようフィルタされている。ユーザーから見た会話ログには表示されない、という設計だ。

キャッシュの挙動自体も、プロバイダ・モデルの組み合わせごとにCacheSemanticsという宣言型のテーブルにまとめられた。従来はOpenRouter・LiteLLM・Databricksがそれぞれ似たようなキャッシュ処理コードを個別に持っていたが、これを「共有のbreakpoint writer」1つに統合したという。

検証:prefix invarianceを機械的にテストするハーネスを新設

PR本文には、この変更の検証方法まで具体的に書かれている。

"a new tests/prefix_invariance.rs harness asserts request N is a cache-valid prefix of request N+1 for every format under its declared semantics, with seeded regressions for the #10030 and #10993 bug classes failing CI."

(新設したtests/prefix_invariance.rsハーネスは、宣言されたセマンティクスの下で、リクエストNがリクエストN+1のキャッシュ有効なprefixになっていることを、すべてのフォーマットについて検証する。過去のバグ#10030・#10993のクラスを再現する回帰テストも仕込まれ、CIで失敗するようになっている)

つまり、「今回のリクエストは前回のリクエストの先頭と完全に一致しているか」を、フォーマットごとに機械的にアサートするテストを新設したということだ。過去に実際に起きた2件のキャッシュ破壊バグ(#10030、#10993)をテストケースとして固定化している点は、単なる思いつきの改善ではなく、実際に問題が報告された箇所への対処だったことを裏付けている。

差分の規模についても本文に記載がある。

"Diff shape: production code is net -153 lines (+861/-1014); all of the growth is test code, dominated by the prefix-invariance harness per the issue's plan."

(差分の内訳:プロダクションコードは正味-153行(+861/-1014)。増加分はすべてテストコードで、その大半をprefix-invarianceハーネスが占める)

プロダクションコード自体はむしろ削減されており、追加された量のほとんどが検証用のテストコードだった、という点は、この変更が「新機能追加」より「既存の設計をより堅牢な形に置き換える」性格の強い改修だったことを示している。

実際に手元で確認したこと

筆者はGoose自体を日常的に使っているわけではないが、この変更の前提となっている「プロンプトキャッシュはリクエストの先頭が変わると無効になる」という挙動自体は、Claude Codeなど自分が普段使っているツールでも意識せざるを得ない制約だ。長い会話の途中でシステムプロンプトやツール定義を書き換えると、そこから後ろのキャッシュが効かなくなり、応答が体感で遅くなる場面には何度も遭遇している。Gooseのこの変更が扱っているのは、個々のユーザーが意識する場面ではなく、エージェントの内部実装が「気づかないうちに」同じ問題を起こしていた、という一段深い層の話だ。

実測:Anthropic・OpenAI・Moonshotでキャッシュヒットを確認

PR本文の末尾には、ログ収集用のリバースプロキシを介して実際のAPIに対して行ったE2Eテストの結果が書かれている。

"Live E2E via a logging reverse proxy against real APIs (multi-turn goose run sessions with tool calls, plus resume): byte-level prefix invariance holds and caches hit across Anthropic on both agent loops (resume served ~8k input tokens from cache), OpenAI gpt-5-mini on the Responses stack (93-98% of input cached), and Moonshot's chat dialect (96% cached on resume); OpenRouter anthropic/... request bodies show the shared writer's explicit breakpoints in place."

(ログ収集用のリバースプロキシを介して、実際のAPIに対する複数ターンのgoose runセッション(ツール呼び出し・resume含む)でE2Eテストを実施した。バイトレベルのprefix不変性が保たれ、両方のエージェントループでAnthropicのキャッシュがヒットした(resume時は約8,000入力トークンがキャッシュから供給された)。Responsesスタック上のOpenAI gpt-5-miniでは入力の93〜98%がキャッシュされ、Moonshotのchat dialectではresume時に96%がキャッシュされた。OpenRouterのanthropic/...向けリクエストボディでも、共有writerによる明示的なbreakpointが確認できた)

数値としてキャッシュヒット率が明記されているのはAnthropic・OpenAI・Moonshotの3プロバイダで、Google(Gemini)については本文中に言及がない。コスト削減額そのもの(金額換算)はPR本文には書かれていない。

関連記事

シェア: ポスト はてブ

出典・参照資料

AIニュースの解説を動画でも

YouTubeでは注目ニュースの背景を解説し、Xでは新着記事をお知らせしています。

コメント

まだコメントはありません。最初のコメントを書いてみませんか?

AIについて聞きたいことはありますか?

質問箱で無料で受け付けています。回答は公開され、他の方の参考にもなります。

質問箱を見る →

新しい記事をメールで受け取る

AIの新しい発表を、出典付きで整理して届けます。

関連記事