上下文滿了,學會砍
「The agent can forget strategically and keep working forever.」策略性遺忘 = 工程能力。
為什麼要 compact?
agent 跑久了,messages[] 會膨脹:每個 read_file 回幾千 token、每個 bash 回幾百、每輪對話還有模型的思考文字。跑 50 輪,context 能塞到 100K+。兩個後果:
- 碰到模型上限:到了視窗大小就崩,或是每一次 API 呼叫價格線性上漲。
- 注意力稀釋:現在手頭上的任務淹沒在 30 輪前的無關 tool_result 裡,模型開始分心。
s06 的想法:讓 agent 主動遺忘不重要的內容,但保留關鍵狀態。三層機制,從輕到重。
Layer 1 · micro_compact(每輪靜默運轉)
最便宜的一層。每次 LLM 呼叫前跑一遍,把超過 3 個的舊 tool_result 替換成佔位符:
# 第 10 轮往前,大部分 tool_result 变成: { "type": "tool_result", "tool_use_id": "toolu_01A", "content": "[Previous: used bash]" # 从几千字符缩到几十 }
有個特例:read_file 的結果不壓縮。為什麼?因為 read 的輸出是參考資料,壓了模型就得重讀一遍,反而更貴。
PRESERVE_RESULT_TOOLS = {"read_file"} # 永不压缩
看 micro_compact 按 turn 吃掉舊結果
下面按步驟模擬 10 輪交互,每一輪前讓 micro_compact 跑一次。看 messages[] 裡舊的 tool_result 變成 [Previous: ...],但最近 3 個保持原樣。
Layer 2 · auto_compact(超過閾值觸發)
即使 micro 一直跑,累到一定規模還是會爆。 s06 設了個閾值(預設 50000 token):
- 估算 token 數
len(str(messages)) // 4(粗糙但夠用)。 - 超過閾值 → 把完整 transcript 寫盤到
.transcripts/transcript_TIMESTAMP.jsonl(留底)。 - 讓 LLM 寫一個 summary給整段對話。
- 把
messages整個換成一條"[compressed] SUMMARY..."。
代價很明顯-失去了具體的工具輸出、對話語氣,只剩下提綱。但 agent 能接著往下乾,這是核心收益。
Layer 3 · 模型自己調 compact tool
auto_compact 是 harness 自動觸發的,模型不知道。 Layer 3 反過來:給模型一個 compact 工具,讓它主動要求壓縮-例如覺得前面的探索已經沒用、要開新階段。
模型呼叫:
tool_use("compact", focus="keep the API design decisions")
觸發和 auto 一樣,但能帶一個 focus 參數告訴總結時重點保留什麼。實戰裡非常實用-模型知道哪些是 "已結束的小任務",比 harness 的啟發式更準。
哪層合適?判斷題
下面幾個場景,決定 micro / auto / manual 哪個觸發比較合理。