背景 agent 的狀態摘要花多少:按 query_source 歸因模型呼叫的 token 與費用
十月底一份 gist 在 HN 上引起討論,作者回報 Claude Code 約每 30 秒把互動模式下正在工作的背景 agent transcript 送回模型,產生由 3 到 5 個詞組成的狀態文字。在他的一週資料裡,這類呼叫佔估算費用 16.4%、API request 數 19.7%,停用方法的測試版本涵蓋 2.1.257 到 2.1.312。數字來自該作者的 OpenTelemetry 匯出,適用於其他環境與版本之前,仍需驗證。
要查自己的工作流有多少摘要開銷,得先按用途分類模型 request,再分別計算 request 數、token 與估算費用,帳單也要另外對照。若量測期間或工作量不同,佔比就不能直接比較。後面的合成資料會示範計算方式,以及換時間窗後數字會怎麼變。
本站〈估 AI prototype 的時程要拆兩段〉和〈AI code review 的帳怎麼算〉列為 AI 工作流總成本的延伸閱讀,頁面目前無法讀取,內容仍需驗證;這篇將總額按用途拆開。
一、公開量測說了什麼
gist 的標題是「Claude Code burns ~15–20% of your tokens on a 3-word status line」,作者 flamerix-ai,在 Linux 上測,版本 2.1.257 到 2.1.312。他描述的行為是:互動模式下正在工作的背景 agent 約每 30 秒被要求「describe your most recent action in 3-5 words」,每次都把該 agent 的完整 transcript 送給它自己用的模型。他將結果描述為只用來更新 agents 面板上的狀態列;是否也供其他流程使用,仍需驗證。他說這個行為從 2.1.232 起對互動模式下正在工作的背景 agent 預設開啟,間隔由伺服器端調整,範圍 30 到 240 秒,預設就是最小值。
他的數字是一張前後對照表,「裝 hook 前一週」與「裝 hook 後兩天」:
- 佔 Claude Code 總估算費用:前 16.4%(主對話是 15.7%),後 0%。
- 佔全部 API request:前 19.7%,後 0%。
- 這些摘要 request 的 input 對 output 比約 9,000 比 1,96% 的費用花在 Opus。
- 週佔比的歷史:七月 3.5% 到 7%,升到 2.1.233 後 7.3% 再到 11.8%,十月中約 16%。
這份量測有幾個限制。費用是 Claude Code 每個 request 自己估的 cost_usd,不是帳單;它對訂閱方案的額度與實際支出有何影響,還得依方案另行確認。作者也回報這些 request 未出現在磁碟上的 session log,是靠 OpenTelemetry 匯出才看見的,所以他是把 claude_code.api_request 事件按 query_source 分組後,才發現 agent_summary 這一類比主對話還貴。
gist 提出的停用方法是一支 Bun --preload 腳本,只包住全域的 setTimeout,callback 原始碼裡含有「[AgentSummary] Timer fired」這類錨點字串就回一個 2³¹−1 毫秒的閒置計時器,其他計時器照常。Claude Code 的執行檔沒有被改,裝法是設 BUN_OPTIONS=--preload=<path> 再 claude daemon stop --any。作者自己列的風險是 fail-open:比對靠的是原始碼文字,改個 log 字串、搬個函式、或計時器不再走全域 setTimeout,摘要就會無聲地回來,所以他要求裝完要驗證。這是作者提出的非官方攔截方式;他回報當時沒有專用的關閉設定,現行版本需驗證。作者另稱 CLAUDE_CODE_FORK_SUBAGENT=0 和 CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1 也能停掉摘要,代價是 fork subagent 或背景 agent 一起沒了。
gist 另外描述 SDK 的行為:摘要可用 agentProgressSummaries: true 或環境變數 CLAUDE_CODE_AGENT_PROGRESS_SUMMARIES 開啟,開了之後摘要以 task_progress 的 stream-json 事件送出,所以你要是有程式在吃這個事件,關摘要之前得先查清楚誰在用。作者未測 ACP adapter,SDK 的現行行為也需驗證。
gist 裡引用了 GitHub issue #86879,說那裡集了四份獨立量測,落在 18% 到 26%。打開 issue 看,本文是一個 4 小時的 session,2.1.233、macOS、27 個 subagent、1,496 個 request,agent_summary 佔 request 數 22.7%、原始 token 22.2%、按費率加權後的 token 18.0%。底下一則留言是 2.1.276、Linux、1 小時 45 分的互動 session,報的是「佔 session cache-read token 的 26%」。所以那個 18 到 26 的範圍,兩端用的是不同分母,一端是加權 token,另一端是 cache-read token,中間還夾著 request 佔比,版本和時長也都不同。這些量測的分母不同,不能合併成一個通用的成本區間。
本文沿用的 HN 討論串快照記錄了 562 點、227 則留言,擷取時間需驗證。有人依程式碼判讀,稱摘要呼叫是對話的暫時分支,可以使用 prompt cache,約每 30 秒一次;另一位 20x Max 使用者估算的佔比約為 16%,但註明是依 30 秒計時器推估。這些數字只能當使用者回報,能否對上帳單仍需驗證。
二、統計單位先定好
要自己量,第一件事是決定什麼算一個單位。合理的單位是一個模型 request,也就是一筆 claude_code.api_request 事件,按它的用途歸類。工具執行和 agent 之間傳訊息不一定各自對應一次 request,若混用 tool call、訊息與 request 數,統計單位就不一致,也可能重複計算。
第二件事是四個量分開報,不互相代換:
- request 數:看的是頻率。摘要若每 30 秒呼叫一次,可能佔不少 request,但仍要看其他工作的呼叫頻率。
- token 數:還要再分
input_tokens、output_tokens、cache_read_tokens、cache_creation_tokens。若摘要送出整份 transcript 並大量命中 cache read,原始 token 佔比可能較高,費用佔比則受 cache read 費率影響,不能只看 token 總數。 - 估算費用:
cost_usd是工具按模型費率算的估計,模型不同、cache 命中與否都會讓它和 token 不同步。 - 帳單:實際支出要依付費方式確認,API 用量計價與訂閱額度的意義不同,不能直接拿估算費用當作扣款。
上面 issue 裡那組數字正好是例子,同一個 session 裡 request 佔比 22.7%、原始 token 22.2%、加權 token 18.0%,這是該作者回報的不同指標,不能互換。
失敗與重試的計法也要先定。重試算一個 request 還是兩個,api_error 事件要不要併進去,先定好再分組,不然前後兩次量測會對不起來。
三、怎麼量
Claude Code 的 Monitoring usage 文件寫了怎麼開 OpenTelemetry,設 CLAUDE_CODE_ENABLE_TELEMETRY=1,再用 OTEL_LOGS_EXPORTER 和 OTEL_METRICS_EXPORTER 選 exporter。事件名稱是 claude_code.<event>,和這篇有關的是 api_request,文件列的屬性包括 model、cost_usd、cost_usd_micros、duration_ms、input_tokens、output_tokens、cache_read_tokens、cache_creation_tokens、request_id、speed、effort,以及 query_source,文件給的例子是 "repl_main_thread"、"compact",或者一個 subagent 的名字。另外有彙總的 metrics,claude_code.token.usage 帶 type 屬性(input、output、cacheRead、cacheCreation)和 model,claude_code.cost.usage 帶 model,兩者也都帶 query_source,但目前文件中的 metrics 分類較粗,不能直接拿來區分摘要呼叫。本文按逐筆事件歸因,使用 metrics 前仍須確認分類粒度。
input_tokens 在文件裡不含 cache read 與 cache creation。若要算整筆 request 的輸入與輸出 token 總量,才把四個欄位相加;這個總和包含輸出,不能稱作送入模型的 token。只看 input_tokens,則會漏掉快取相關的輸入。
沒有 query_source 可用時(舊版本,或別的工具),用間隔固定、prompt 越來越長這種特徵去找候選 request,只能當線索。gist 作者是靠 query_source 分組才確定的,用特徵猜出來的那一堆,裡面混了什麼沒人知道,費用算出來也只能標成「疑似」。找不到歸類的 request 另外列一行「未知」,別硬塞進某一類。
下面是用合成資料做的示範,欄位名沿用文件的 api_request 事件,記錄格式經過簡化,數字與費率都是示範用的假設值,只用來示範計算方式,不能把真實匯出檔直接套入。先產生七天的紀錄:主對話每天 4 小時,約每 60 秒一個 request;每天開 0 到 6 個背景 agent,各跑 1 小時,約每 45 秒一個 request;每個 agent 另外每 30 秒一筆 agent_summary,input 很小、output 很小、cache_read_tokens 隨 transcript 長大。seed 固定為 42。
# 產生合成的 api_request 紀錄(欄位名照 Claude Code OTel 文件),寫成 JSONL
import json, random
random.seed(42)
RATE = {"in": 5.0, "out": 25.0, "cr": 0.5, "cc": 6.25} # USD / 1M tokens,假設值,不是牌價
DAY = 86400
PLAN = [3, 1, 1, 0, 2, 6, 5] # 七天,每天開幾個背景 agent(各跑 1 小時)
def req(t, source, inp, out, cr, cc):
cost = (inp*RATE["in"] + out*RATE["out"] + cr*RATE["cr"] + cc*RATE["cc"]) / 1e6
return {"event.name": "api_request", "event.timestamp": t, "model": "synthetic-model",
"query_source": source, "input_tokens": inp, "output_tokens": out,
"cache_read_tokens": cr, "cache_creation_tokens": cc, "cost_usd": round(cost, 6)}
rows = []
for day, n_agents in enumerate(PLAN):
t0 = day * DAY + 9 * 3600
for k in range(240): # 主對話:約每 60 秒一次,4 小時
rows.append(req(t0 + k*60 + random.randint(0, 20), "repl_main_thread",
random.randint(500, 3000), random.randint(100, 800),
100000 + 300*k, random.randint(0, 4000)))
for a in range(n_agents):
ta = t0 + 600 + a * 300
ctx = 20000
for k in range(80): # agent 本身:約每 45 秒一次,1 小時
ctx += random.randint(1500, 4000)
rows.append(req(ta + k*45, f"agent-{a}", random.randint(300, 2000),
random.randint(100, 600), ctx, random.randint(0, 3000)))
for k in range(120): # 狀態摘要:每 30 秒一次,讀整份 transcript
rows.append(req(ta + k*30 + 5, "agent_summary", 60, random.randint(5, 20),
15000 + k * 1000, 0))
rows.sort(key=lambda r: r["event.timestamp"])
with open("requests.jsonl", "w") as f:
for r in rows:
f.write(json.dumps(r) + "\n")
print(len(rows), "requests written")
5280 requests written
接著按 query_source 分組,subagent 本身的 request(agent-0、agent-1⋯)併成一類,算 request 數、各類 token 和估算費用,最後把三種佔比並排:
# 按 query_source 分組:request 數、各類 token、估算費用,三種佔比並排
import json, collections
agg = collections.defaultdict(lambda: collections.Counter())
for line in open("requests.jsonl"):
r = json.loads(line)
src = "subagent" if r["query_source"].startswith("agent-") else r["query_source"]
c = agg[src]
c["requests"] += 1
c["input"] += r["input_tokens"]; c["output"] += r["output_tokens"]
c["cache_read"] += r["cache_read_tokens"]; c["cache_creation"] += r["cache_creation_tokens"]
c["cost_usd"] += r["cost_usd"]
total = collections.Counter()
for c in agg.values():
total.update(c)
total["tokens"] = total["input"] + total["output"] + total["cache_read"] + total["cache_creation"]
print(f"{'query_source':17}{'requests':>9}{'input':>10}{'output':>9}{'cache_read':>13}{'cost_usd':>10}")
for src, c in sorted(agg.items(), key=lambda kv: -kv[1]["cost_usd"]):
print(f"{src:17}{c['requests']:9}{c['input']:10}{c['output']:9}{c['cache_read']:13}{c['cost_usd']:10.2f}")
print()
for src, c in agg.items():
tokens = c["input"] + c["output"] + c["cache_read"] + c["cache_creation"]
print(f"{src:17} requests {c['requests']/total['requests']:6.1%} "
f"tokens {tokens/total['tokens']:6.1%} cost_usd {c['cost_usd']/total['cost_usd']:6.1%}")
query_source requests input output cache_read cost_usd
repl_main_thread 1680 2973883 748500 228228000 168.62
subagent 1440 1629914 506539 188531520 129.02
agent_summary 2160 129600 26923 160920000 81.78
repl_main_thread requests 31.8% tokens 39.9% cost_usd 44.4%
subagent requests 27.3% tokens 32.7% cost_usd 34.0%
agent_summary requests 40.9% tokens 27.3% cost_usd 21.6%
同一類 request,三種佔比是 40.9%、27.3%、21.6%。在這份資料裡,摘要呼叫較頻繁,但每筆 token 量較少,token 又以 cache read 為主,因此費用佔比更低。這是合成資料的結果,只用來示範不同分母會得到不同數字,不能據此預測其他工作流的開銷。
四、分母不同,佔比就不能比
gist 比較「一週」與「兩天」的資料,作者回報停用後摘要佔比為 0%。這個 0 代表那段量測中沒有記到摘要呼叫,還不能直接推出「總費用省了 16.4%」。兩段期間的工作量、背景 agent 用量與 cache 情況是否可比,都需驗證。
用同一份合成資料換三個時間窗算一次:
# 同一份紀錄,量測期間不同,agent_summary 的佔比會差很多
import json, collections
DAY = 86400
rows = [json.loads(l) for l in open("requests.jsonl")]
def share(lo_day, hi_day):
win = [r for r in rows if lo_day*DAY <= r["event.timestamp"] < hi_day*DAY]
cost = sum(r["cost_usd"] for r in win)
summ = sum(r["cost_usd"] for r in win if r["query_source"] == "agent_summary")
agent_hours = sum(1 for r in win if r["query_source"].startswith("agent-")) * 45 / 3600
return cost, summ, agent_hours
for name, lo, hi in [("第 1 到 7 天", 0, 7), ("第 3 到 4 天", 2, 4), ("第 6 到 7 天", 5, 7)]:
cost, summ, ah = share(lo, hi)
per_day = cost / (hi - lo)
per_ah = summ / ah if ah else float("nan")
print(f"{name}: 總費用 {cost:7.2f} 每天 {per_day:6.2f} 摘要佔比 {summ/cost:6.1%}"
f" agent 小時 {ah:5.1f} 摘要費用/agent 小時 {per_ah:5.2f}")
第 1 到 7 天: 總費用 379.43 每天 54.20 摘要佔比 21.6% agent 小時 18.0 摘要費用/agent 小時 4.54
第 3 到 4 天: 總費用 59.55 每天 29.77 摘要佔比 7.6% agent 小時 1.0 摘要費用/agent 小時 4.54
第 6 到 7 天: 總費用 176.83 每天 88.41 摘要佔比 28.3% agent 小時 11.0 摘要費用/agent 小時 4.54
這份合成資料裡,摘要固定每個 agent 每 30 秒一筆,但第 3 到 4 天的佔比是 7.6%,第 6 到 7 天是 28.3%,整週則為 21.6%,差別來自各天的 agent 用量。即使假設第 3 天停掉摘要,再拿第 6 到 7 天的總費用比較,也不能只憑總額判斷停用是否有效;這些時間窗原本就有不同工作量。
最右邊是摘要費用除以背景 agent 執行小時,在這份合成資料的三個時間窗都約為 4.54,因為每個 agent 套用相同的摘要頻率與 token 模型。實際工作流是否也能用這個比率估算,需驗證,不能直接套用別人的 16.4%。量測時可以記錄同時存活的 agent 數與各自執行時間,並分清楚累計建立數和同時存活數;計時器的觸發條件也要依工具版本確認。
五、關掉之前要比對什麼
就算量到了,決定關不關之前還有幾件事:
- 固定條件多跑幾次。同一份任務、同一個模型、同一版工具,各跑幾輪再比,單次重跑會被 prompt cache 命中率和執行路徑的差異蓋過去。
- 除了費用,也記完成品質和執行時間。摘要是否只用來顯示狀態需驗證;若確實不供其他流程使用,可能不影響結果,但 gist 的關法改的是計時器,有沒有副作用要自己驗。
- 先查狀態顯示有沒有其他消費端。終端面板只是其中一個,SDK 的
task_progress事件是另一個,若有自動化流程依賴它,停用就可能影響其他工作。 - agent 讀過哪些檔案,不等於那些檔案整份都送出去了。摘要 request 送的是 transcript,可先檢查
api_request_body事件(要開OTEL_LOG_RAW_API_BODIES),但匯出內容本身也可能截斷或遮蔽,不能一律當成完整 payload;issue 作者回報使用這項功能擷取資料。
收件端也要依實際設定確認。按公開量測的描述,摘要送往處理該 agent 的模型 API;即使服務提供者相同,也不能據此排除資料處理上的影響。實際送出的 transcript、截斷方式與接收端是否符合預期,仍需驗證。
補充筆記
- 公開量測來自單一作者,其停用方法測試涵蓋版本 2.1.257 到 2.1.312:
agent_summary佔估算費用 16.4%、request 數 19.7%,費用是工具自估的cost_usd,不是帳單。 - GitHub issue 裡的 18% 到 26% 分別是加權 token 佔比與 cache-read token 佔比,分母不同,版本與時長也不同,不是同一個量。
- 以
claude_code.api_request事件為單位、按query_source歸因;request 數、token、估算費用、帳單四個分開報。 - 合成資料裡同一類 request 的 request、token、費用佔比是 40.9%、27.3%、21.6%,差距來自頻率高與 cache read 便宜。
- 量測期間不同,佔比從 7.6% 到 28.3% 都算得出來;每 agent 執行小時的摘要費用只在這個合成模型裡近似固定,實際是否適用仍需驗證。
- 關掉前固定條件多跑幾次,也記品質與時間,並先查 SDK 的
task_progress有沒有人在用。
延伸閱讀
- flamerix-ai〈Claude Code burns ~15–20% of your tokens on a 3-word status line. Here’s how to turn it off〉:該作者量測數字、測試版本範圍、量測期間與 Bun preload 關法的出處;issue 回報與本文合成資料另行區分。
- Claude Code 文件〈Monitoring usage〉:OpenTelemetry 的啟用方式、
api_request事件的屬性名稱、token.usage與cost.usagemetrics,本文欄位名都照這份。 - GitHub issue〈Feature request: a supported way to disable the agent_summary spinner〉:另一份 4 小時 session 的量測(22.7% request、18.0% 加權 token)與留言裡的 26% cache-read token。
- HN 討論串〈Claude Code regularly sends each background agent’s transcript to its model〉:其他使用者的回報與對 prompt cache 的討論,參考用,沒有帳單層級的證實。
- 本站〈估 AI prototype 的時程要拆兩段:從零到可展示,從可展示到上線〉:原稿列為 AI 工作流總成本估算的參考,頁面目前無法讀取,內容仍需驗證。
- 本站〈AI code review 的帳怎麼算:找到的 bug、誤報和人的時間〉:原稿列為多 agent review 成本估算的參考,頁面目前無法讀取,內容仍需驗證。
想法與技術判斷出自 Sheng,和 Claude 一起起草 · 題目由 Sheng 選定,內文、案例整理與範例由 Claude 撰寫,範例在 Linux 上以 Python 3.13.18 實測。