AIDive

動画パック

Spotifyの「Claude Codeトークン90%削減」を再構築して計測:フック、サブエージェント、手順

11 分で読めます

TL;DR

  • Spotifyの「90%」は、Javaモノレポで推定入力トークン数として計測した一括読み込みシナリオの平均値です。記事にはドル建てのコスト値も品質スコアもありません。
  • 素のClaude Code(PreToolUseフック1つ、安価なサブエージェント2つ、3行のルーティングルール)で再構築し、Fastifyで4シナリオ、16回の実行で計測したところ、このパターンでメインモデルのコンテキストは59.6%、総コストは33.1%減りました。
  • 計測した実行では、denyフックは一度も発動しませんでした。節約を生んだのはCLAUDE.mdのルーティングルールです。フックは、モデルがルールを無視した日のためのセーフティネットです。
  • 委任は毎回遅くなり、実時間は平均で+65.3%でした。小さなテスト作成シナリオでは、コストが2.6%増えました。
  • 落とし穴は2つ。フックはサブエージェントの内部でも発動するので、ワーカーは除外してください。そしてcat、head、tailだけを監視するフックは、sed -nの範囲読み込みをそのまま通します。
  • Haikuリーダーの要約には、委任した8回のうち2回で事実の誤りがありました。メインモデルの検証ターンは残してください。

計測が示すこと

SpotifyのプラグインShuntは、一括作業をメインモデルから2つの「モード」に振り分けます。bulk-readerとcode-writerで、例ではどちらもGemini 2.5 Flashで動き、modelフィールドにはPortalインスタンスで設定された任意のモデルを指定できます s1。ルーティングは3層です。check-file-sizeフックはReadのたびに発動し、設定可能な行数しきい値(デフォルト350)を超えるファイルをブロックして、代わりにbulk-readerスキルを案内します。check-bash-readフックは大きなファイルに対するcat、head、tail、less、moreを捕まえ、パイプ付きのコマンドは通します s1。フックのソースと2つのスキルは公開リポジトリにあり s2、サイズチェックは単独で読めます s3。モード自体はSpotifyの社内プラットフォームPortalにあるため、プラグインは配布された形のまま社外では動かせません s4。

ベンチマークの主張は薄いものです。SpotifyはJavaモノレポで4つのシナリオを試し、「measuring tokens Claude would consume reading files directly vs. consuming the bulk-reader's summary」と述べ、一括読み込みの平均削減を約90%と報告しています s1。記事自体が、コード書き込みシナリオはトークンでは測りにくいこと、ワーカーの要約には信頼できる行番号がないため編集は委任できないこと、メインモデルが見つけた微妙なスレッドセーフティのバグをワーカーが見逃したこと、委任ごとに10〜30秒が加わりPortalは1回の呼び出しを30秒に制限していることを認めています s1。Hacker Newsのスレッドでも、90%が何を測っているのかという同じ疑問が出ました s7。

再構築では、Portalのモードを、定義ファイルでモデルを固定した2つのClaude Codeサブエージェントに置き換えました。HaikuのExploreリーダーと、Sonnetのcode-writerです s6。denyは、現行のフックJSON形式でdenyの判定を返すPreToolUseフックです s5。テスト対象のリポジトリはfastify/fastifyのコミットac28821d、.js/.tsファイル294個、78270行、350行を超えるファイルが63個です。セッションJSONが報告したモデルIDは、メイン会話がclaude-opus-5[1m]、リーダーがclaude-haiku-4-5-20251001、ライターがclaude-sonnet-5です。各シナリオを構成ごとに2回実行し、計測は16回。プロジェクト設定のみの単一ターンclaude -pセッションで、両側のシステムプロンプトを同一にしました s5。

パターンが勝った場面。S2は、lib/route.js(691行)、lib/reply.js(1090)、lib/request.js(398)にまたがるコールグラフの質問で、メインコンテキストの平均が357165.5トークンから73440.0(-79.4%)に、コストが0.5810500000000001 USDから0.21823605000000001 USD(-62.4%)になりました。勝てなかった場面。S4は、19行の参照テストに倣って45行のソースのテストを書くシナリオで、委任なしは0.29465575 USD、委任ありは0.3022213 USD(+2.6%)でした。Sonnetが2つ目のフルコンテキストになり(キャッシュ読み取りトークンは13004から18729)、メインモデルが生成ファイルを読み直してテストも実行したためです s6。

割合よりも重要な発見が3つあります。第一に、フックは計測した16回の実行で一度も発動しませんでした。CLAUDE.mdのルーティングルールがあると、メインモデルは自分でwc -lを実行して委任しました。観測された唯一のdenyは、CLAUDE.mdなしの検証実行で、Readを拒否され、次にcat -nも拒否され、Agentツールを一度も呼ばずにgrep -nだけで答えたときのものです s5。第二に、フックはサブエージェントの内部でも動きます。破棄した2回の実行では、Haikuリーダー自身がサイズチェックにdenyされ、offset/limitによる分割読み込みに切り替えました。対処は、フック冒頭にcase "$agent_type" in Explore|code-writer) exit 0という脱出を置くことで、フィールド名は記録したstdinで確認します s5。第三に、ベースライン構成のメインモデルはReadツールを一度も使いませんでした。すべてのファイルをBash(cat -n、sed -n '1,200p'、sed -n '200,560p')で読んだため、Readだけを見るフックは何も捕まえず、パイプなしのcat、head、tailだけに一致するBashフックでもsed -nの範囲読みは通ってしまいます s3。

品質はソースに対してgrepで確認しました。ベースラインは、S2の1回で誤った行番号を出しました(sed -nで行番号なしにファイルをダンプし、手で数えたため)。委任構成は、もう一方のS2の実行で事実の誤りを3件、S3の実行で2件出し、すべてHaikuの要約をそのまま信じたことに起因します。buildRequest/buildReplyの呼び出し元の誤り、エクスポートされていない定数をエクスポートとして列挙、カバー済みのイテレータを未カバーと判定、などです。メインモデルが出力トークンを使ってgrepで再検証した場面(S3、4534対4738出力トークン)では、答えは正しいままでした s6。テスト作成シナリオでは、生成されたファイルはすべて通りました。12/12、6/6、7/7、10/10のテストです。Redditの報告には、何も固定していないとメインモデルが間違ったモデルでワーカーを起動するという関連の失敗例があり s8、require-modelフックとエージェントファイルのmodel:フィールドはそれを防ぐためのものです。

計測結果

各セルは2回の実行の平均です。「Main context」は、セッション全体でメインモデルに課金されたinput + cache_creation + cache_readトークンで、Spotifyの「tokens in the main context」と比較できる値です。A = 素のClaude Code、B = フック + サブエージェント + CLAUDE.mdのルール。

シナリオ main context A main context B 変化 main output A main output B 変化 総コスト A 総コスト B 変化 所要時間 A s 所要時間 B s 変化
S1 88693.0 51551.5 -41.9% 1424.5 1060.5 -25.6% 0.13910675 0.08653685 -37.8% 21.817500000000003 43.799499999999995 +100.8%
S2 357165.5 73440.0 -79.4% 3835.0 2555.5 -33.4% 0.5810500000000001 0.21823605000000001 -62.4% 51.637 129.036 +149.9%
S3 303807.5 114135.5 -62.4% 6192.0 4636.0 -25.1% 0.451037 0.3738534 -17.1% 93.321 124.64099999999999 +33.6%
S4 143431.5 121818.0 -15.1% 5275.5 3340.0 -36.7% 0.29465575 0.3022213 +2.6% 65.7125 86.857 +32.2%
4つ合計 223274.375 90236.25 -59.6% 4181.75 2898.0 -30.7% 0.366462375 0.2452119 -33.1% 58.122 96.08337499999999 +65.3%

手順:ac28821dのfastify/fastifyをバイト単位で同一にshallow cloneを2つ用意。repo-shuntには.claude/(設定、エージェントファイル2つ、フック3つ)とCLAUDE.mdのルーティングルールだけを追加し、ほかは何も変えません。各セッションはclaude -p "<prompt>" --output-format json --setting-sources project --strict-mcp-configで、空のMCP設定、--modelなし、タイムアウト600秒。プロンプトは4つで、両側で同一です。S1はlib/reply.jsのエクスポート、S2は3つのlibファイルにまたがるコールグラフ、S3はlib/hooks.jsのメソッドとtest/hooks.test.jsのカバレッジの比較、S4はtest/noop-set.test.jsに倣ったtest/head-route.test.jsの作成。数値はセッションJSONのmodelUsageとtotal_cost_usdから、丸めずに読み取りました。回答はソースに対してgrepで確認し、生成されたテストはnode --testで実行しました。

月曜にやること

  • リポジトリでwc -lを実行し、350行を超えるファイルを数える。数がゼロに近いなら、ここで終了です。小さなファイルでは委任のコストが節約を上回るため、このしきい値があります。
  • CLAUDE.mdに3行のルーティングルールを追加する。しきい値を超えるファイルはリーダーのサブエージェントへ、既存パターンに沿うコードはライターのサブエージェントへ、デバッグとアーキテクチャはメインモデルに残す。計測では、このルールがすべての仕事をしました。
  • .claude/agents/Explore.mdにfrontmatterでmodel: haiku、.claude/agents/code-writer.mdにmodel: sonnetを入れ、ワーカーのモデルをオーケストレーター任せにせずファイルで固定する。
  • セーフティネットとして、現行のフックJSON形式でdenyの判定を返すReadのPreToolUseフックを書き、先頭の数行でagent_typeが自分のワーカーのどれかなら0で終了するようにする。
  • Bashフックをcat、head、tailの外へ広げる。大きなファイルへのsed -n範囲読みとcat -nに一致させ、パイプ付きとgrepのコマンドは通す。
  • 実際の質問を1つ、.claude/フォルダありとなしでclaude -p --output-format jsonから実行し、入力の列だけでなくtotal_cost_usdとduration_msを比較する。
  • リーダーの要約を信じる前に、委任された回答を2つ、ソースに対してgrepで確認する。メインモデルの検証ターンもコストとして見込む。
  • マルチターンのセッションも計測する。単一ターンの結果では、メインコンテキストは委任なしの84kから414kトークンに対して50kから119kトークンに収まったので、2つ目の質問はより安く始まるはずですが、それは計測していません。

さらに読む

  • ブログからフックをコピーする前に、公式リファレンスでフックの形式とagent_typeフィールドを読んでください。denyの形とstdinのフィールドが、サブエージェントの除外を可能にします s5。
  • サブエージェントのドキュメントには、modelのfrontmatterフィールドとツール制限が載っています。リーダーを読み取り専用で安価に保つ方法です s6。
  • Spotify自身の「What doesn't work」セクションが記事でいちばん役に立ちます。編集の委任なし(要約に信頼できる行番号がない)、推論の委任なし(スレッドセーフティのバグの見逃し)、往復10〜30秒 s1。
  • ShuntのREADMEは、3層構造(フック、スクリプト、スキル)と、いつ委任するかをモデルに伝えるスキル本文を示します。応用する価値があるのはスキルの文章で、フックではありません s2。
  • Portalのモードは、モデルとシステムプロンプトの上にある設定レイヤーです。同じ考え方は、modelフィールドを持つClaude Codeのエージェントファイルに対応します s4。
  • Hacker Newsのスレッドは、計測への疑問が最初に出された場所で、トークン節約の主張に対して何を問うべきかのよいチェックリストになります s7。
  • あるRedditのスレッドには、オーケストレーターが自分の高価なモデルで5つのワーカーを起動した例があります。エージェントファイルでモデルを固定し、確実に防ぎたいならmodelフィールドのないAgent呼び出しをdenyしてください s8。

出典

FAQ

90%という数字は間違っていますか?

測っているのは1つだけです。大きなJavaファイルの一括読み込みシナリオで、メインコンテキストの推定入力トークン数です。同種の指標で、再構築は読み込みシナリオで41.9%から79.4%でした。コスト、時間、回答の品質については何も語っておらず、記事もそうは主張していません。

これを使うのにPortalは必要ですか?

いいえ。ルーティングはCLAUDE.mdのルール、モデルを固定した2つのエージェントファイル、PreToolUseフックにあります。SpotifyではPortalがワーカーのモデルを供給しますが、素のClaude Codeではmodel: haikuの1行が同じ役目を果たします。

委任のほうが高くつくのはいつですか?

ファイルが小さいときです。45行のテスト作成シナリオは、ライターが2つ目のフルコンテキストで、メインモデルが結果を読み直してテストもしたため、委任ありで2.6%高くなりました。委任した実行はどれも遅く、平均で+65.3%でした。

フックが一度も発動しなかったのはなぜですか?

CLAUDE.mdのルーティングルールのおかげで、メインモデルが読み込みを試す前にwc -lで確認して委任したからです。フックが意味を持つのは、モデルがルールを無視したときだけで、それはCLAUDE.mdなしの検証実行で起きました。