Claude Code 的週額度延長引發關注,文章分析 Agent 程式的 Token 消耗機制。Agent 長時間運行會導致 working set 不斷膨脹,每一步都在累積歷史狀態,加上快取命中後仍佔用 context 的特點,使得計算成本呈累積增長。清理過多會引發「語義缺頁」,Agent 需重新獲取資訊。文章指出這種程式碼狀態與設計狀態保存精度的不一致,可能導致 AI 生成的「祖傳程式碼」:後續 Agent 無法理解早期程式碼的設計因果,最終形成 queue、bypass、retry 相互補償的複雜程式碼。文章作者、來源:雷峰網

代碼確實是 Agent 寫的,只是後續的 Agent 已經不知道前面的 Agent 為什麼這樣寫。
Claude Code 原定於 8 月 19 日結束的 +50% 周額度加成,又被 Anthropic 延長至 8 月 31 日。也就在原定截止日前後,Hacker News 上出現了一輪關於 Claude Code 使用成本的討論:不少人發現,一個並不複雜的任務,Agent 跑上幾輪,額度就會掉得很快。

問題在於,Claude Code 消耗的並不只是最後生成的那幾行代碼。讀文件、搜調用鏈、跑測試、處理日誌,每一步都會繼續進入後面的上下文。任務越長,Agent 背著的歷史越重,系統也越依賴清理和壓縮。
代碼可以完整留在倉庫裡,早期的設計理由卻可能在壓縮中逐漸變薄。於是 Token 消耗和代碼屎山開始在同一個地方匯合。

01 修個小 Bug,為何需要幾十次推理?
普通 Chat coding 的計算邊界很清楚。輸入一段代碼,模型讀完以後給出解釋或者修改方案,這一輪基本結束。
而 Claude Code 的基本單元已更換為 agent loop。模型先觀察當前狀態,決定下一步要讀取哪個檔案或執行什麼命令;工具返回結果後,模型再進行下一轮判斷。
讀取原始碼、搜尋引用、執行測試、查看 Git diff、修改檔案,看似是一個連續的動作,但在模型端實際上是一連串獨立的推理請求。Claude Code 官方文件也將這種「模型判斷—呼叫工具—根據結果繼續判斷」的循環作為 Agent 工作方式的核心。
例如,一個登入狀態偶發失效的問題。Agent 先找到入口,發現狀態來自 service,於是繼續讀取 service;看到快取後搜尋誰在寫入它;接著執行測試,測試出現另一個異常,於是查看 fixture;修復後再次驗證,舊測試又暴露出相容性問題。
可能直到這時,它才真正開始寫那幾行代碼。因此,diff 大小和計算量之間幾乎不存在穩定比例。5 行補丁背後可能只有 3 次推理,也可能已經經過 30 次工具交互。

如果將一次 Agent 任務拆開,可以先得到兩個變數:一個是 step count,Agent 為了完成任務走了多少步;另一個是 working set,走到當前這一步時,模型還需要掌握多少項目狀態。
只增加 step count 已經會提高消耗。如果 working set 還在同步變大,情況就完全不同了。第 3 步也許只需要處理幾千 Token,第 30 步卻可能已經背著項目規則、相關源碼、測試結果、修改歷史和工具返回繼續推理。
這也是 Coding Agent 成本結構發生變化 的起點:計算量開始取決於「走多少步 × 每一步背多重」,而不再取決於寫了多少行代碼。
02 Token 到底燒在哪裡?
將 Agent 的一次模型請求拆開,可以粗略看成三塊。相對穩定的部分包括 system prompt、CLAUDE.md、工具定義和項目規則;不斷變化的一部分包括代碼文件、搜索結果、測試日誌、Git diff 和此前的任務軌跡;最後還有這一輪模型生成的 reasoning、文字與代碼。
這裡容易產生一個誤區:只要前面的內容已經讀過,就不應該重複產生太多成本。問題在於,LLM 在兩次請求之間不存在像傳統程式那樣可以隨時訪問的內部記憶體。上一輪知道的資訊,如果下一輪判斷仍然依賴它,相關狀態還得繼續出現在可用 context 中。

Prompt cache 能緩解這個問題。Claude Code 官方文檔明確說明,如果沒有 prompt caching,每輪請求需要重新處理整段歷史;緩存命中後,已經處理過的穩定前綴可以複用,從而降低重複計算和成本。
但快取解決的是「同樣的歷史能否以較低成本重新使用」,並未解決「這段歷史是否還應繼續存在」。100K Token 的舊狀態命中快取後雖降低成本,但它仍佔用 context,且仍是當前推理所依賴的狀態。
於是可以把一個長任務粗略寫成:第 t 步的輸入規模,大約等於穩定前綴 S,加上當前有效工作集 W_t,再加這一輪剛產生的新資訊 Δ_t。
真正麻煩的是 W_t。如果每走一步,Agent 又多讀一點源碼、多得到一點日誌、多留下一個決策,而舊資訊沒有及時退出,那麼 W_t 會隨著任務推進不斷增加。
在一個極端簡化、完全沒有快取和清理的模型裡,如果每輪新增的有效狀態大致相同,總處理量會出現接近 1 + 2 + 3 + … + n 的累積結構。也就是說,step count 只增加了一倍,整個任務處理過的歷史狀態可能增加得更快。

實際系統有 cache、context editing 和 compaction,不會機械遵循這個增長曲線,但問題的形狀沒有改變:Agent 運行時間越長,每一個新動作越可能建立在一块更重的歷史之上。
因此,在長任務中,那個非常短的使用者提示很快就會失去存在感。真正開始主導成本的,是模型為保持任務連續性而不斷攜帶的工作集。
03 刪得太狠會出現語義缺頁
為什麼 working set 膨脹得這麼快?工具輸出是一個很大的來源。原始碼至少有結構,日誌卻經常沒有。
一次 grep 可以返回幾百處引用,一次構建可能吐出大片 warning,一次測試失敗可能帶著完整 stack trace,Docker、編譯器、包管理器也會製造大量對任務沒有長期價值的文本。

假設第 10 步測試產生了 8K Token 日誌。它第一次進入 context 時,僅為 8K Token。但 Agent 還需繼續檢查原始碼、修改並重新測試,只要這段日誌仍處於有效歷史中,它就會提高後續多輪請求的基礎權重。
這很像存儲系統裡的 write amplification:一次邏輯寫入,造成了後續更多底層處理。Agent 裡的情況是,一次 tool output 被寫進執行歷史,隨後跟著後面的推理一起移動。
因此,同樣是 8K Token,放在任務結束前一輪和放在任務剛開始時,帶來的整體影響完全不同。Claude Code 現在也正在主動減少這種污染。官方建議使用 sub-agent 隔離高輸出任務,並明確提到搜尋結果、日誌和大量檔案內容會消耗主會話 context;工具定義本身也會佔用空間,因此工具集過大同樣會增加狀態負擔。
但這裡又出現了一個相反的問題:不能因為日誌很貴,就把它們全部刪掉。一段 3000 行日誌裡,可能只有 20 行與根本原因有關。系統事先並不知道是哪 20 行。如果過早清理,Agent 後來突然需要其中一個細節時,只能重新運行測試或重新打開文件。

這可以稱為 semantic page fault,語義缺頁。在傳統虛擬內存中,程式訪問一個已不在記憶體中的頁面時,系統會從磁碟重新載入;Coding Agent 在丟棄某段早期證據後,也會發生類似現象,只不過表現為重新搜尋倉庫、重複讀取檔案、再次執行命令,甚至重新推導一個早已分析過的問題。
因此,長任務陷入兩難:保留過多歷史,後續每一步都會越來越沉重;清理過於激進,Agent 又會不斷重新獲取以前見過的資訊。
這也解釋了為何 context 管理不能簡化成「少塞一點 Token」。真正需要解決的是 working set selection:此刻有哪些資訊必須留在工作區,哪些只是已經完成使命的中間產物。
到了這裡,compaction、memory 和 sub-agent 才真正有了存在的理由。

04 什麼資訊可以被忘記?
當 Claude Code 接近 context 邊界時,會自動壓縮會話,同時也會清理部分較舊的工具結果。官方亦提醒,長 session 中的無關對話、檔案內容和命令結果可能佔滿視窗並干擾模型表現。
從系統角度看,compaction 很像一次語義垃圾回收。麻煩在於,普通垃圾回收判斷的是「這個對象還有沒有引用」,而 Agent 必須判斷「這段資訊以後還有沒有意義」。
後者困難得多。例如早期有這樣一段設計結論:某個模組不能自己快取使用者狀態,因為系統要求狀態只有一個 owner,所有修改必須經過 service。
幾十步之後,如果這段資訊被壓縮為:「之前通過 service 調整解決了狀態問題。」事實並未出錯,但資訊已經發生變化。原始內容包含的是 constraint,後面的摘要僅保存了 event。
當下一次 Agent 遇到效能問題,發現 service 調用很慢時,很可能又會在模組中增加快取。它並未違反其當前掌握的資訊;當初禁止快取的因果關係已不在有效狀態中。
Claude Code 的 context 文件甚至明確指出,部分 path-scoped 規則和嵌套的 CLAUDE.md 會隨著會話一起被 compaction 摘要,需再次讀取匹配檔案才會重新載入。
Memory 試圖解決長期知識保存的問題。項目根目錄的 CLAUDE.md 和 auto memory 可以將構建命令、項目規範、調試經驗等內容從短期對話中抽離,並在會話開始時重新載入。但 Anthropic 對其定位也寫得很清楚:這些 memory 仍然是 context,不屬於強制配置。

這個區別非常關鍵。「這裡不能直接訪問資料庫」如果僅寫在記憶體中,它仍只是一句模型需要理解並遵循的自然語言;如果同一條規則以 dependency lint、類型約束或 CI 檢查的形式呈現,它才會變成一個無法輕易繞過的軟體不變量。
Sub-agent 解決的是另一塊:隔離工作集。讓一個獨立 Agent 去掃描倉庫或者分析長日誌,再把壓縮後的結果交還主 Agent,可以避免原始噪聲進入主線程。Claude Code 官方給 sub-agent 的用途之一就是 context isolation。
它的代價也很有意思:主 Agent 得到了更乾淨的狀態,卻失去了部分原始證據;多個 Agent 同時運行,還會建立各自的 context。因此 compaction、memory、sub-agent 放在一起看,其實已經很像一套 Agent 時代的記憶體層級:
當前 context 是昂貴的工作記憶體,compaction 負責壓縮,memory 保存跨 session 狀態,sub-agent 使用獨立地址空間隔離噪聲。問題也從「context 夠不夠大」變成了另一個層級:
哪些狀態需要高保真保存,哪些狀態只需要留下摘要。這個問題會直接影響後面的代碼質量。
05 無法提前預知要運行多久的程式
理解前面這套執行結構後,再看 Claude Code 的週額度,會發現平台很難繼續按「消息條數」計量 Agent。因為一條消息已經失去穩定意義。
更改變數名稱是一條訊息,重構整個認證模組也是一條訊息。前者可能幾步就結束,後者可能運行數十輪,讀取數十個檔案,再啟動多個 Agent。同樣的一個 request,背後的資源需求可能完全不在同一個量級。

Claude Code 將滾動限制和週額度打包;Codex 現已明確根據 input token、cached input token 和 output token 折算 credits;Cursor 的套餐則為 Agent 提供不同的 usage pool,第三方模型的消耗會受模型 API 價格影響。
三個產品的介面語言不同,底層需要解決的問題卻很接近:怎樣給一個執行路徑事先無法確定的智能程式分配推理資源。一個 Coding Agent 到底會跑多久,在任務開始時很難確定。

模型可能很快找到根本原因,也可能連續提出幾個錯誤假設;可能一次測試就通過,也可能進入長時間的 debug loop;可能只需要一個 Agent,也可能拆分出多個 sub-agent。
傳統 API 喜歡按 request 計費,是因為一次 request 的資源波動還能控制在一定範圍。Agent 把這種穩定性打散了。所以 Token 在這裡開始有點 CPU time 的味道。
這個類比不能畫等號。不同模型處理同樣數量 Token 的算力成本不同,input、cached input 和 output 也有不同成本。但站在開發者這一側,它們承擔的功能越來越相似:都是在描述一個任務為了繼續運行,到底佔用了多少計算資源。
Anthropic 在今年提高 Claude Code 使用上限時,也直接將額度提升與新增 compute capacity 聯繫在一起。這會帶來一個很有意思的指標變化。以前看 Coding Agent,容易比較「同一道題誰一次寫得好」。往後可能更有意義的是:完成同樣的工程狀態變化,誰消耗的有效計算更少。

如果一個 Agent 花掉大量 Token,只是在重複打開文件、重新跑測試、重新恢復已經丟掉的上下文,那些 Token 並沒有換來對應程度的工程推進。
而這類低效狀態恢復,恰好會與技術債在下一层碰在一起。
06 AI 祖傳代碼怎麼形成
這裡可以把一個 Coding Agent 維護的軟體抽象成兩套同時演化的狀態。一套是代碼狀態 R_t。檔案、類型、介面、測試、Git commit 全部屬於這一層。Agent 第 20 步加進去的一行 retry,只要沒有被刪除,第 100 步打開檔案時仍然完整存在。代碼對過去修改的保存精度非常高。
另一套是設計狀態 M_t。為什麼這裡需要 retry,為什麼那個快取只能放在 service,為什麼這個狀態不能有兩個 owner,為什麼一個看起來多餘的判斷暫時不能刪,這些資訊屬於設計因果。
M_t 沒有像 Git 一樣天然的無損存儲。它分散在對話、推理、工具返回、memory、規則文件和 compaction summary 裡。任務不斷推進以後,部分內容被清理,部分內容被摘要,部分內容需要重新檢索。

因此會產生一個至關重要的不對稱:實現的結果能夠高保真地累積,但生成這些結果的因果關係卻會不斷降採樣。這比單純說「Agent 會忘東西」嚴重得多。
在一次併發問題中,Agent 分析後將其加入了一個佇列。當時它掌握的完整結論是:只有寫入路徑 A 存在競爭,因此佇列只能包含 A;寫入路徑 B 需要低延遲,不能進入此佇列。

代碼完整地保存了 queue。經過長時間執行後,設計狀態可能只剩下「這裡用 queue 解決 race condition」。
後來 B 也出現一個偶發錯誤。Agent 再次讀取代碼時,很自然地把 B 也接進現有 queue。
隨後延遲上升,於是再加 bypass。bypass 又引出偶發狀態不一致,於是外圍補 retry。到了這裡,任何一次修改都不一定是荒謬的。每個補丁在當時所見的局部狀態下甚至可能相當合理。代碼卻已從「一個明確的併發模型」,變成了 queue、bypass 和 retry 互相補償。
AI 的代碼屎山很可能就是這樣長出來的。它不一定表現為模型突然寫出一團垃圾,更可能表現為局部正確不斷累積,整體模型逐漸消失。

在傳統軟體中,這類問題通常透過人員交接逐漸形成。原作者離開後,新開發者看到舊代碼,卻不知道它為何存在,於是在外層再包一層相容邏輯。
Coding Agent 將“人員交接”變成了“上下文交接”。第 20 步和第 100 步看起來仍是同一個 Claude Code session,但它們實際取得的設計狀態已不完全相同。從資訊角度看,更像兩名工程師透過一份不斷縮水的交接文件維護同一個倉庫。
測試也只能解決其中一部分。測試擅長保護行為:接口應該返回什麼,某種輸入不能崩潰,過去的 bug 不能重新出現。很多架構約束卻不天然表現成輸入輸出。
狀態只能有一個 owner、領域層不能反向依賴 UI、某個 package 不允許直連資料庫、寫操作必須經過統一事務邊界,這些約束如果只存在於文件或 Agent 記憶裡,就很容易在局部修復中被穿過去。
結果會出現一種很麻煩的工程狀態:測試還是綠的,代碼卻越來越難解釋。 更危險的是,這裡存在反饋迴路。

架構開始變亂,Agent 下一次理解功能就需要讀取更多檔案;依賴關係越繞,working set 越大;工作集越重,系統越需要清理和壓縮;設計因果保存得越薄,後面的修改又越容易依賴眼前程式碼和局部測試。
因此,代碼複雜度開始提升 Token 成本,Token 壓力又反過來鼓勵更短的狀態保留和更局部的修補。這才是 Agent coding 中「越迭代問題越多」背後較值得警惕的機制。
這不是單一的模型能力問題,而是一種代碼狀態和設計狀態保存精度不一致的系統問題。
07 Agent 需要「狀態保真率」
Coding Agent 已經越來越能長時間行動,但「能跑幾個小時」本身未必是一個很好的能力指標。
如果一個 Agent 工作 3 小時以後,需要重新閱讀自己 2 小時前改過的文件,重新推理某個抽象為什麼存在,再重新跑一次此前已經跑過的測試,那麼這 3 小時裡有相當一部分計算其實花在了狀態恢復上。
接下來的問題會變成:一個 Agent 在經歷 50 步、100 步之後,還能保留多少對後續決策有價值的因果資訊。
可以把它叫作 状态保真率。
因為它衡量的不是 context 能塞多少 Token,而是經過工具調用、壓縮、跨 session 和記憶檢索之後,多少關鍵設計資訊仍然以可用的形式存在。這也意味著,Agent 的長期記憶不能只靠更長的 context。
有些知識適合存在 memory 裡,比如項目構建方式和開發習慣;有些決策應該進入結構化的 ADR 或代碼索引;而那些一旦違反就會破壞系統的架構邊界,更適合直接寫進類型、測試、lint、依賴規則和 CI。
當一條規則已轉化為軟體可執行的約束時,Agent 就無需「記住」它。下一轮 Agent 可以忘記一段對話,卻無法輕易越過編譯器和測試。
如果保真率低,Agent 跑得越久,其實是在給系統埋越多的地雷。
這也可能正是 Coding Agent 從「會寫代碼」走向「能長期維護軟體」需要跨越的一道界線:將設計知識從概率性的語言記憶,逐步遷移到可檢索、可驗證、可執行的軟體狀態中。
否則自主運行時間越長,會出現一個很荒誕的場景。Agent 寫代碼的速度越來越快,項目也在飛快變化,可每隔一段時間,它又要重新理解上一段時間留下來的世界。
傳統祖傳代碼常見的一句話是:“這段別動,不知道為什麼會炸。”
AI 祖傳代碼可能更離譜:代碼確實是 Agent 寫的,只是後面的 Agent 已經不知道前面的 Agent 為什麼這麼寫了。
