AIDive

Spotifyの「Claude Codeトークン90%減」を再現したら、60%と穴が1つ

AIDive · 公開

コーディングエージェントAI モデル

90%、そしてそれを売った一文

SpotifyのClaude Code設定は、SpotifyのプロダクトマネージャーであるDimitri Mazmanov氏のブログ記事で、コードはGitHubに公開されています。彼によれば、チームで使っている設定によってClaude Codeのトークン使用量が90%減ったとのことです。記事の最初の一文が主張のすべてを担っています。AIコーディングagentがやっていることの大半は思考ではなく、I/Oである、という一文です。1つのメソッドについての質問に答えるために5つのファイルを読む、隣にある20個のテストファイルをコピーした21個目のテストファイルを書く。こうした作業は、ほとんど推論を伴わないまま数千トークンを消費します。

あるツイートが、たった一文でこの記事を150万ビューまで運びました。書かれたルールは提案にすぎない、ブロックは違う、という一文です。Hacker Newsではフロントページに載り、271ポイントと173件のコメントが付きましたが、コメントの半分は同じ質問をしていました。何の90%なのか、という質問です。Spotify自身が付けた限定語は「bulk read」です。この記事では、この設定を素のClaude Codeの中で再現し、それを計測します。その限定語が実際に何をもたらすのかを正確に知るためです。

Portalの正体(そしてなぜ動かせないのか)

Portalはルーターではありません。Spotify社内の開発者ポータルであり、Spotifyがオープンソース化した開発者プラットフォームであるBackstageの上に構築されています。ここで関係する機能はModesと呼ばれています。Spotifyの定義によれば、modeとはエフェメラルなランタイム上で動く宣言的なagentで、おおまかに言えばagent版のAWS Lambdaです。指示を書き、モデルを選び、temperatureを設定し、ツールを付ける。Mazmanov氏はこれを2つ作りました。bulk readerとcode writerで、どちらもtemperature 0.2のGemini Flashで動きます。つまり、どちらも意図的に安価で退屈な作りです。

ルーティングはShuntというClaude Code pluginの中にあります。GitHubで公開されており、2つのコマンドでインストールできます。しかし2つ目のステップは、Portalのコマンドラインをあなた自身のPortalインスタンスに対して認証するもので、あなたはそれを持っていません。pluginは公開されていますが、pluginが委譲する先は公開されていないのです。

したがって有用な動きは、pluginのことは忘れてパターンだけを残すことです。パターンは3層からなり、彼自身の言葉で言えばhook、スクリプト、skillです。そのどれにも、素のClaude Codeでの等価物があります。この記事の残りは、それを構築し、計測します。

第1層: 尋ねる代わりにブロックするhook

この設定のバージョン1は、プロジェクトの指示ファイルに書かれたルーティングルールの塊でした。Mazmanov氏の言葉では「まあまあ機能した」程度です。ルールは助言であって強制ではなく、Claudeは無視できましたし、プロジェクトごとにコピーが必要でした。バージョン2は、判断をpromptからツール層へ移します。2つのhookを使い、どちらもツール呼び出しの前に発火します。1つはすべてのファイル読み取りを監視し、もう1つはシェルを監視します。

読み取りhookは33行のbashです。環境変数からしきい値(デフォルトで350行)を読み、次の3つを通します。

  • offsetかlimitの付いた読み取り。Claudeが必要なものをすでに分かっているからです。
  • 存在しないファイル。
  • しきい値以下のファイル。小さなものを委譲するのは、読むより高くつくからです。

それ以外はすべてブロックされ、Claudeはファイルの代わりにメッセージを読みます。このファイルは何行ある、bulk reader skillを使え、編集のために正確な内容が必要なら、その部分だけを読み直せ、という内容です。シェルhookは、大きなファイルに対するcat、head、tail、less、moreを捕まえます。パイプ付きのコマンドは通ります。grepへのパイプは絞り込まれた読み取りだからです。

Mazmanov氏の層構造についての指摘が重要です。Claudeがskillの説明を一度も読まなかったとしても、hookは高価な読み取りをブロックします。skillはリダイレクトを滑らかにし、ブロックはそれを現実のものにします。後で効いてくる細部が1つあります。このスクリプトは「block」という名前のトップレベルのdecisionで応答します。この単語を覚えておいてください。

第2層と第3層: ワーカーとその数字

ワーカーは2つのpromptです。readerは「あなたは精密なコード分析者である。構造化された箇条書きだけを出力せよ。挨拶も散文も不要。各箇条書きは正確な名前、型、行番号で始めよ」。writerは「既存のパターン、命名、スタイルに正確に合わせよ。コードだけを出力せよ。フェンスも説明も不要」。最後の一文がないと、モデルはすべてをMarkdownで包み、Claudeがそれをパースする羽目になります。

2つのスクリプトがこれらを包みます。bulk-readは質問とファイルパスを受け取って送り出します。code-writeは仕様と参照ファイルを受け取り、結果をディスクに直接書き込むので、Claudeは生成されたコードを一切見ません。すべての委譲はワンショットで、追加の質問はファイルを再送します。それは重要なところでは無料です。コーパスはワーカーに送られ、Claudeのコンテキストには決して入らないからです。

第3層は、いつ委譲すべきかをClaudeに教えるskillファイルです。350行超のファイル、3つ以上のファイルにまたがる質問、大きなdiff。最後の行は「編集前に行番号を検証せよ」です。

Spotifyの表は、1つのJava monorepoと3つの読み取りシナリオを扱っています。単一ファイルのケースは約34,000トークンから6,000未満に下がり、3行の平均節約率は90%です。

Spotifyのベンチマーク
リポジトリ 1 Java monorepo
シナリオ 3、すべてbulk read
単一ファイルのケース、前 ~34,000 tokens
単一ファイルのケース、後 < 6,000 tokens
平均節約率 90%
トークン推定 1トークンあたり4文字
writerの強制 なし(hookがあるのはreaderのみ)

Spotify自身が2つの注意書きを明記しています。トークンは4文字を1トークンとして推定されていること、そしてwriterには強制がまったくないことです。つまり90%とは、推定入力トークンで測った3つのbulk read行の平均であり、品質スコアもドルの数字もどこにもありません。それがテストすべき数字です。

再現その1: modelフィールド付きのsubagent

Claude Codeには組み込みのExplore subagentが付属していますが、最近のリリース以降はメインモデルを継承するようになり(上限はOpus)、「安価なreader」はもう安価ではありません。ドキュメントは修正方法を一文で示しています。Exploreという名前のプロジェクトsubagentは組み込みのものを上書きし、独自のmodelフィールドを保持する、というものです。Markdownファイル1つ、front matter、そしてmodelの行にHaiku。これがbulk readerです。writerは2つ目のファイルで、modelはSonnet、toolsはReadとWriteのみ、本文にはSpotify自身の指示をそのまま貼り付けます。

これが機能するのは、すべてのsubagentが新しい隔離されたコンテキストウィンドウで始まるからです。subagentが読んだものはそこに入り、メインの会話には入りません。これはネットワークの往復を除いたSpotifyのワンショット委譲そのものです。

次に、誰も計画に入れていない部分が来ます。今週Redditで、FableがOpusのagentを起動するよう指示されたのに、代わりにFableのagentを5つ起動した件がありました。30分で週次上限の73%が消えたのです。最も支持された回答は、モデルがsubagentをディスパッチする時に実行されるhookでした。モデルを明示的に選ばせ、タスクをこなせる最も安価なものを選ぶよう指示します。これが3つ目のhookです。Agentツールを監視し、modelのない呼び出しは「モデルを明示的に選べ」という一文で拒否されます。

Spotifyのskillは、プロジェクトの指示ファイルの3行になります。350行超のファイルはexplorerへ、ボイラープレートはwriterへ、すべてのagent呼び出しはmodelを設定する。もっと乱暴な選択肢もあります。すべてのsubagentに1つのモデルを強制する2つの環境変数です。正直な限界は、readerがより安価なモデルである以上、readerが返すものがメインモデルの知るすべてだということです。計測のセクションでこれを扱います。

再現その2: 今のhook形式でのdeny

「block」という単語を思い出してください。Spotifyのスクリプトはトップレベルのdecisionを返しますが、現在のClaude Codeドキュメントは違うことを言っています。PreToolUse hookはhook固有の出力オブジェクトの中でdecisionを返し、そのフィールドはpermissionDecisionという名前です。結果はallow、deny、ask、deferの4つで、ここで欲しいのはdenyです。hookがreasonとして書いたものはClaudeに表示され、複数のhookが応答した場合はdenyが勝ちます。

再現した読み取りhookは、同じ350のしきい値と同じ3つの例外を保ち、「block」の代わりに、Explore subagentと使うべきモデルを名指しするreason付きのdenyを返します。ドキュメントが明記している罠が1つあります。settingsのhookはsubagentの中でも実行される、というものです。逃げ道がなければ、Haikuのreaderは自分自身の読み取りを拒否され、仕事ができなくなります。そのためスクリプトは誰が呼んでいるかを確認し、2つのワーカーを通します。

配線は、Read、Bash、Agentの3つのmatcherを持つ1つのsettingsファイルで、それぞれがスクリプトを指し、しきい値は環境変数として設定します。実際に1,090行のファイルを読み取ると、書いておいた文言のエラーが返ってきます。この読み取りはexplorerに委譲せよ、モデルはHaiku、というものです。続いて委譲が起こります。メインモデルはまず行数を数え、modelをHaikuに設定してexplorerを呼び、行番号付きの箇条書きが返ってきます。3ターン、44秒です。

Spotifyの言葉は成り立ちます。層構造のおかげで、システムは緩やかに劣化するのです。指示がルーティングを行い、hookが安全網になります。ただし安全網には穴があります。ファイル全体を欲しがるモデルは、offsetとlimitで分割して読むことができ、これは通ります。あるいはsedの範囲指定でシェルから吐き出すこともでき、このhookはそれを捕まえません。計測はその両方を数えます。

計測

テストリポジトリはNodeのWebフレームワークであるFastifyです。294ファイル、うち63がしきい値超えです。2つの同一のクローンを用意し、違いは.claudeフォルダとルールファイルだけです。メインモデルはCLIデフォルトのOpus、readerはHaiku、writerはSonnet。単一promptのセッションで追加の質問なし、各シナリオを設定ごとに2回ずつ、合計16回の実行です。4つのシナリオはSpotifyと同じで、1つの大きなファイルのexport、3つのファイルとその呼び出し関係、ソースファイルとそのテストの照合、既存のテストからディスクに書き出す新しいテストファイルです。

シナリオ メインコンテキスト、なし メインコンテキスト、あり 変化 総コスト、なし 総コスト、あり 変化 所要時間、なし 所要時間、あり 変化
大きなファイル1つ 88,693 51,552 -41.9% $0.139 $0.087 -37.8% 22 s 44 s +100.8%
3ファイル 357,166 73,440 -79.4% $0.581 $0.218 -62.4% 52 s 129 s +149.9%
ソースとテスト 303,808 114,136 -62.4% $0.451 $0.374 -17.1% 93 s 125 s +33.6%
新しいテストファイル 143,432 121,818 -15.1% $0.295 $0.302 +2.6% 66 s 87 s +32.2%
4つ全体 223,274 90,236 -59.6% $0.366 $0.245 -33.1% 58 s 96 s +65.3%

まず重要なのはメインコンテキスト、つまり高価なモデルが実際に見たトークンの列です。3ファイルの質問では79%減り、4シナリオ全体では59.6%減ります。請求額の減り幅はそれより小さく、全体で3分の1です。readerのトークンも無料ではないからで、小さなテスト作成タスクでは請求額が2.6%増えました。時間は逆方向に動きます。設定なしで平均58秒、ありで96秒。委譲は毎回遅くなります。

2つの設定が最も違うのは品質です。設定なしでは、メインモデルは行番号なしでファイルをシェルから吐き出し、手で数えたため、行番号が全体的に間違っていました。149行目にあると報告された関数は、実際には156行目にありました。設定ありでは、4回に1回の実行でreaderの要約を鵜呑みにし、3つの誤った主張を含んでいました。そのうち1つは、readerがルートファイルからは決して呼ばれないと言った関数で、実際には553行目で呼ばれています。生成された4つのテストファイルはすべてパスし、deny hookは16回の実行で一度も発火しませんでした。ルールファイルがあれば、メインモデルは毎回自分で行数を確認して委譲したのです。

トレースからもう1つ。ルールなしでは、メインモデルはReadツールを一度も使いませんでした。すべてをシェル経由で読んでおり、シェルの範囲読み取りは同じトークンを消費し、しかもhookを通過します。つまりSpotifyの表は90と言い、こちらの表はコンテキストで60、請求額で3分の1と言っているのです。

ブロックは残せ。請求額が90%減るとは期待するな。

残す価値があるものは3つです。Haikuで動くプロジェクトのExplore subagent、指示ファイルの3行のルール、そして安全網としての読み取りhook。計測された結果は、メインコンテキスト60%減、請求額3分の1減、実行時間3分の2増です。

hookを信頼する前に、2つ直してください。hookはsubagentの中でも実行されるので、ワーカーを除外すること。そしてシェルの穴です。bash hookはcat、head、tailを捕まえますが、範囲読み取りは通り、ルールがない時のメインモデルはまさにそれを使いました。

Spotify自身が挙げる限界も変わりません。編集は委譲できず、推論も委譲できません。ワーカーはClaudeが数秒で見つけたスレッド安全性のバグを見逃し、委譲のたびに往復が発生します。Hacker Newsの懐疑派も1つの点では正しかった。入力トークンは請求額の本体ではありません。出力トークンの方が高く、この設定は出力トークンには何もしません。

誰が得をするかは支払い方によります。APIなら3分の1減。ProやMaxプランでは、同じ設定が動かすのは5時間枠と週次枠であって、ドルではありません。しきい値にも注意してください。しきい値未満では、委譲は節約分より高くつきます。45行のテストケースが+2.6%でその証拠です。最後に、8回中2回の実行でreaderの要約は誤りを含み、メインモデルの検証ターンがそれを捕まえました。そのターンを省けば、その誤りがあなたの編集に届きます。

出典

よくある質問

SpotifyはClaude Codeのトークン使用量を本当に90%減らしたのですか?
Spotifyの90%は、Java monorepoでの3つのbulk readシナリオの平均で、4文字を1トークンとする推定入力トークンで数えたものです。品質スコアもドルの数字もありません。同じ考え方を素のClaude Codeで再現しFastifyで計測すると、メインモデルのコンテキストは59.6%減、総コストは33%減でした。
SpotifyのPortalとは何ですか?Claude Codeで使えますか?
PortalはBackstageの上に構築されたSpotify社内の開発者ポータルで、そのModes機能はエフェメラルなランタイム上で宣言的なagentを動かします。Claude CodeをそれらのagentにルーティングするShunt pluginはGitHubで公開されていますが、あなたが持っていないPortalインスタンスに対して認証するため、pluginを動かすことはできず、パターンをコピーすることしかできません。
Claude Codeに大きなファイルの読み取りを安価なモデルへ委譲させるにはどうすればいいですか?
modelフィールドをHaikuに設定したExploreという名前のプロジェクトsubagentを作ります。これは、今ではメインモデルを継承する組み込みのExplore agentを上書きします。次に、プロジェクトの指示ファイルに3行のルール(350行超のファイルはexplorerへ、ボイラープレートはwriterへ、すべてのagent呼び出しはmodelを設定する)を追加し、そのsubagentを名指しするreason付きで大きな読み取りを拒否するPreToolUse hookをReadに設定します。
今のClaude Codeでは、PreToolUse hookはどうやってツール呼び出しをブロックするのですか?
hookはhookSpecificOutputオブジェクトの中のpermissionDecisionというフィールドでdecisionを返します。値はallow、deny、ask、deferの4つです。reasonの文字列はClaudeに表示され、複数のhookが応答した場合はdenyが勝ちます。Spotifyのスクリプトは、blockという名前の古いトップレベルのdecisionを使っています。
読み取りをHaikuのsubagentに委譲すると、Claude Codeは安くなりますか?
APIでは4シナリオ全体で請求額が3分の1減りましたが、readerのトークンも無料ではありません。45行のテスト作成タスクでは総コストが2.6%増えました。ProやMaxプランでは、節約は5時間枠と週次枠の利用枠として現れ、ドルとしては現れません。出力トークンは手つかずです。
Claude Codeのhookはなぜsubagentの中でも発火するのですか?
settingsで定義したhookは、あなたがディスパッチするsubagentを含むすべてのagentで実行されます。そのため読み取りをブロックするhookは、スクリプトが誰が呼んでいるかを確認してワーカーagentを通さない限り、Haikuのreader自身の読み取りも拒否してしまいます。

関連動画