AIDive

影片資料包

Spotify 宣稱省下 90% 的 Claude Code token,重建與實測:hook、subagent、測試流程

閱讀約 11 分鐘

TL;DR

  • Spotify 所說的「90%」,是在 Java monorepo 上以估算輸入 token 衡量大量讀取情境後得出的平均值。文中沒有給出金額數字,也沒有品質分數。
  • 用純 Claude Code 重建(一個 PreToolUse hook、兩個便宜的 subagent、一條三行的路由規則),在 Fastify 上跑四個情境、共 16 次執行,這個模式讓主模型的 context 減少 59.6%,總成本減少 33.1%。
  • 在實測的執行中,deny hook 一次都沒有觸發。節省來自 CLAUDE.md 裡的路由規則;hook 是模型哪天無視規則時的安全網。
  • 委派每次都比較慢,平均耗時增加 +65.3%。在規模較小的撰寫測試情境中,成本還多了 2.6%。
  • 兩個陷阱:hook 在 subagent 內也會觸發,所以要把你的 worker 排除在外;而 sed -n 範圍讀取會直接穿過只盯著 cat、head、tail 的 hook。
  • Haiku 讀取者的摘要,在八次委派執行中有兩次出現事實錯誤。請保留主模型的驗證回合。

測量結果說明了什麼

Spotify 的外掛 Shunt 透過兩種「模式」把大量工作從主模型分流出去:bulk-reader 和 code-writer,範例中兩者都跑 Gemini 2.5 Flash,model 欄位可以接受 Portal 實例中設定的任何模型 s1。路由分三層。check-file-size hook 在每次 Read 時觸發,攔下超過可設定行數門檻(預設 350)的檔案,並引導模型改用 bulk-reader skill;check-bash-read hook 則攔截對大檔案使用的 cat、head、tail、less 和 more,而串接管線的指令會放行 s1。hook 原始碼和兩個 skill 都在公開 repo 裡 s2,大小檢查也能單獨閱讀 s3。模式本身存在於 Portal,也就是 Spotify 的內部平台,因此這個外掛照原樣出貨時無法在公司外執行 s4。

基準測試的說法證據薄弱。Spotify 在 Java monorepo 上測了四個情境,「衡量 Claude 直接讀檔與讀取 bulk-reader 摘要各自會消耗的 token」,並回報大量讀取的平均節省約 90% s1。文章本身也承認:寫程式的情境更難用 token 衡量,worker 的摘要沒有可靠的行號,所以編輯無法委派,worker 漏掉了主模型抓到的一個細微的執行緒安全 bug,而且每次委派都會增加 10 到 30 秒,Portal 還把單次呼叫限制在 30 秒 s1。Hacker News 的討論串對 90% 到底在衡量什麼,也提出了同樣的疑問 s7。

重建版本把 Portal 模式換成兩個 Claude Code subagent,由定義檔固定模型:跑在 Haiku 上的 Explore 讀取者,以及跑在 Sonnet 上的 code-writer s6。deny 是一個 PreToolUse hook,以目前的 hook JSON 格式回傳 deny 決定 s5。受測 repo 是 fastify/fastify,commit ac28821d,294 個 .js/.ts 檔案、78270 行、63 個檔案超過 350 行。session JSON 回報的模型 id:主對話 claude-opus-5[1m],讀取者 claude-haiku-4-5-20251001,寫入者 claude-sonnet-5。每個情境每種設定跑兩次,共 16 次實測,皆為單回合 claude -p session,只套用專案設定,讓兩邊的 system prompt 完全相同 s5。

這個模式贏的地方:S2 是跨 lib/route.js(691 行)、lib/reply.js(1090 行)和 lib/request.js(398 行)三個檔案的呼叫圖問題,主 context 平均從 357165.5 token 降到 73440.0(-79.4%),成本從 0.5810500000000001 USD 降到 0.21823605000000001 USD(-62.4%)。沒贏的地方:S4 是參照一個 19 行的範例,為 45 行的原始碼寫測試,沒有委派時成本是 0.29465575 USD,委派後是 0.3022213 USD(+2.6%),原因是 Sonnet 等於第二份完整 context(cache 讀取 token 從 13004 增加到 18729),而主模型仍然重讀了產生的檔案並跑了測試 s6。

比百分比更重要的有三項發現。第一,在 16 次實測中 hook 一次都沒觸發:有了 CLAUDE.md 裡的路由規則,主模型自己跑 wc -l 然後委派。唯一觀察到的 deny 出現在沒有 CLAUDE.md 的驗證執行中,模型被拒絕 Read,接著被拒絕 cat -n,最後只靠 grep -n 回答,完全沒呼叫 Agent 工具 s5。第二,hook 會在 subagent 內執行:在兩次被捨棄的執行中,Haiku 讀取者自己被大小檢查擋下,退回用分段的 offset/limit 讀取。解法是在 hook 最上方加一段 case "$agent_type" in Explore|code-writer) exit 0 的豁免,欄位名稱以記錄下來的 stdin 為準 s5。第三,在基準設定中,主模型根本沒用過 Read 工具。它每個檔案都是透過 Bash 讀的(cat -n、sed -n '1,200p'、sed -n '200,560p'),所以只監看 Read 的 hook 什麼都抓不到,而只比對不帶管線的 cat、head、tail 的 Bash hook,仍然會放過 sed -n 範圍讀取 s3。

品質是用 grep 對照原始碼檢查的。基準版本在一次 S2 執行中產生了錯誤的行號(它用 sed -n 倒出沒有行號的檔案,再用手數)。委派版本在另一次 S2 執行中出現三個事實錯誤,在一次 S3 執行中出現兩個,全都追溯到照單全收 Haiku 的摘要:buildRequest/buildReply 的呼叫者錯誤、一個未匯出的常數被列為 export、一個已涵蓋的 iterator 被標成未涵蓋。主模型花輸出 token 用 grep 重新驗證的地方(S3,輸出 token 從 4534 到 4738),答案是正確的 s6。在撰寫測試的情境中,所有產生的檔案都通過:12/12、6/6、7/7 和 10/10 個測試。一則 Reddit 回報展示了相關的失敗模式:沒有東西固定模型時,主模型會用錯誤的模型去啟動 worker s8,而 require-model hook 和 agent 檔案裡的 model: 欄位正是用來防範這一點。

測量數據

每個格子是 2 次執行的平均。「Main context」是整個 session 中計費給主模型的 input + cache_creation + cache_read token,也就是可以和 Spotify 所說「主 context 的 token」相比的數字。A = 純 Claude Code,B = hook + subagent + CLAUDE.md 規則。

情境 main context A main context B 變化 main output A main output B 變化 total cost A total cost B 變化 duration A s duration 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%
四項合計 223274.375 90236.25 -59.6% 4181.75 2898.0 -30.7% 0.366462375 0.2452119 -33.1% 58.122 96.08337499999999 +65.3%

測試流程:兩份位元組完全相同的 fastify/fastify ac28821d 淺層 clone;repo-shunt 只加了 .claude/(設定、兩個 agent 檔案、三個 hook)和一條 CLAUDE.md 路由規則,其他什麼都沒動。每個 session:claude -p "<prompt>" --output-format json --setting-sources project --strict-mcp-config,搭配空的 MCP 設定、不帶 --model、逾時 600 秒。四個 prompt 在兩邊完全相同:S1 是 lib/reply.js 的 export,S2 是跨三個 lib 檔案的呼叫圖,S3 是 lib/hooks.js 的方法與 test/hooks.test.js 的涵蓋範圍對照,S4 是仿照 test/noop-set.test.js 撰寫 test/head-route.test.js。數字取自 session JSON 的 modelUsage 和 total_cost_usd,未經四捨五入。答案用 grep 對照原始碼檢查,產生的測試以 node --test 執行。

週一就動手

  • 在你的 repo 上跑 wc -l,數一數超過 350 行的檔案有幾個。如果數量接近零,就在這裡停下:設這個門檻是因為在小檔案上,委派的成本比省下的還多。
  • 在 CLAUDE.md 加一條三行的路由規則:超過門檻的檔案交給讀取者 subagent,照著既有模式寫的程式碼交給寫入者 subagent,除錯和架構留給主模型。在測量中,這條規則完成了所有工作。
  • 建立 .claude/agents/Explore.md,frontmatter 寫 model: haiku,以及 .claude/agents/code-writer.md,frontmatter 寫 model: sonnet,讓 worker 的模型固定在檔案裡,而不是交給 orchestrator 決定。
  • 把 Read 上的 PreToolUse hook 當作安全網寫起來,以目前的 hook JSON 格式回傳 deny 決定,並讓它的開頭幾行在 agent_type 是你的 worker 之一時直接 exit 0。
  • 把 Bash hook 擴大到 cat、head、tail 之外:比對大檔案上的 sed -n 範圍和 cat -n,讓帶管線和 grep 的指令通過。
  • 用 claude -p --output-format json,針對一個真實問題分別在有和沒有 .claude/ 資料夾的情況下各跑一次,比較 total_cost_usd 和 duration_ms,不要只看輸入那一欄。
  • 在信任讀取者的摘要之前,先用 grep 對照原始碼檢查兩個委派的答案;把主模型的驗證回合當成成本的一部分來編列預算。
  • 也測一次多回合的 session:單回合的結果顯示,主 context 在委派時是 50k 到 119k token,沒有委派時是 84k 到 414k,所以第二個問題應該會以更低的成本起步,但這一點沒有實測。

延伸閱讀

  • 在從部落格文章複製 hook 之前,先讀官方參考文件裡的 hook 格式和 agent_type 欄位;deny 的形式和 stdin 上的欄位,正是讓 subagent 豁免得以成立的關鍵 s5。
  • subagent 文件涵蓋了 model frontmatter 欄位和工具限制,這就是讓讀取者保持唯讀又便宜的方法 s6。
  • Spotify 自己的「What doesn't work」一節是整篇文章最有用的部分:不能委派編輯(摘要裡沒有可靠的行號)、不能委派推理(漏掉的執行緒安全 bug)、一趟來回要 10 到 30 秒 s1。
  • Shunt README 展示了三層結構(hook、腳本、skill),以及告訴模型何時該委派的 skill 文字;值得改編的是 skill 的文字,而不是 hook s2。
  • Portal 模式是疊在模型加 system prompt 之上的設定層;同樣的概念可以對應到帶有 model 欄位的 Claude Code agent 檔案 s4。
  • Hacker News 討論串是測量方式的疑問最早被提出的地方,也是檢驗任何省 token 說法時該問哪些問題的好清單 s7。
  • 一個 Reddit 討論串記錄了 orchestrator 用自己昂貴的模型啟動了五個 worker;請把模型固定在 agent 檔案裡,如果想要硬性保證,就 deny 沒有 model 欄位的 Agent 呼叫 s8。

來源

FAQ

90% 這個數字是錯的嗎?

它只衡量一件事:在大型 Java 檔案的大量讀取情境中,主 context 的估算輸入 token。以同類指標來看,重建版本在讀取情境中得到 41.9% 到 79.4%。它對成本、時間或答案品質都沒有說任何話,文章也沒有這樣宣稱。

我需要 Portal 才能做到嗎?

不需要。路由存在於一條 CLAUDE.md 規則、兩個固定模型的 agent 檔案,以及一個 PreToolUse hook 裡。在 Spotify,Portal 負責提供 worker 模型;在純 Claude Code 裡,一行 model: haiku 就能做到同樣的事。

委派什麼時候反而更貴?

檔案很小的時候。45 行的撰寫測試情境在委派後成本多了 2.6%,因為寫入者是第二份完整 context,而主模型仍然重讀並測試了結果。每一次委派的執行也都比較慢,平均 +65.3%。

為什麼 hook 從來沒有觸發?

因為 CLAUDE.md 裡的路由規則讓主模型在嘗試讀取之前,就先檢查 wc -l 並委派。hook 只在模型無視規則時才有用,而這發生在沒有 CLAUDE.md 的驗證執行中。