這是長程 Agent 設計手記系列的第一篇,起因是我們精讀了 BEACON(浙江大學,arXiv:2605.06078)。
要讓一個 Agent 扛得住長任務,我們認為有兩件事必須做好:
- 它能持續往目標推進。這件事又拆成三塊:上下文壓縮(忘掉什麼才是安全的)、里程碑設計(怎麼知道一步真的完成了)、防止打轉(卡住了要有人發現)。
- 它能反思並持續改進。
這篇講防止打轉,因為這是我們代價最大的一個。
先說現象
我們收到過使用者反饋,內部也發現過:一些超長任務跑到一半就不往前走了。
看日誌,agent 一直在忙——讀檔案、搜索、跑命令,一刻沒停。但半小時過去,什麼都沒推進。
我們最初歸因於上下文丟失:壓縮把「這條路已經試過」這條記錄丟了,所以又去試一遍。把代碼和日誌對著看之後才發現,這只是一半。
我們本來就有防線,而且有三層
| 層級 | 判據 | 閾值 |
|---|---|---|
| 精確重復 | 工具名 + 規範化參數,逐字節相同 | LOOP_WARN=3 警告 / LOOP_HARD=5 強制停止 |
| 近似重復 | 除易變的 id、timestamp 字段外相同 | NEAR_DUP_LOOP_WARN=6 / NEAR_DUP_LOOP_HARD=12 |
| 自旋收斂 | 壓縮 ≥2 次 且 燒掉 ≥75% 工具輪次預算 | SPIN_CONVERGENCE_MIN_COMPACTIONS=2SPIN_CONVERGENCE_TOOL_LOOP_RATIO=0.75 |
第三層的注釋原文寫著:Compound "may be spinning after context loss" signal。有人早就預見到了。所以問題從來不是沒做,而是做出來的東西抓不到它。
盲區:三層全是「輸入側」檢測
三層共用的簽名是工具名 + 規範化參數。它只回答一個問題:你在不在重復做同一件事?
但一個卡住的強模型根本不會重復呼叫。它會這樣走:
讀檔案 A → grep X → 換個行號範圍再讀 A
→ 跑個略有差別的命令 → 讀檔案 B → 回頭再 grep …每一次呼叫簽名都不同,三層檢測器全部靜默。而這整段時間里,可驗證的狀態變化是 0:沒有檔案被真正改寫,沒有命令產出新結果,沒有任何不可逆的事情發生。
循環檢測不等於停滯檢測。前者看輸入,後者必須看輸出。
論文給的正是這個輸出側定義
BEACON 的段內獎勵:
r_t = R_ms · γ^(t_k − t) 若這一段以里程碑收尾
= 0 否則一個沒有以里程碑收尾的段,拿到的是精確的 0,不管它執行了多少個動作。動作數量根本不進入公式。這是我們見過的「忙 ≠ 有進展」最乾淨的形式化。
基線那一層更狠。基線取的是組內平均每步回報,所以一段走了 8 步而組內平均 5 步,整段的 advantage 一齊偏負,懲罰還隨超出程度遞增。打轉不只是沒獎勵,是被主動懲罰,而且是比例性的。
論文 Figure 8 里有條失敗軌跡,最後兩個動作拿到完全相同的 −2.20。那個「最後一個里程碑之後再也夠不到下一個」的尾段,就是打轉的數學形態。而它很常見:完成了至少一個子目標但最終失敗的軌跡,穩定佔 39%~47%。
一組消融資料否定了「按步數切」
| 切分方式 | 成績 | 相對基線 72.8 |
|---|---|---|
| 隨機切 5 段 | 74.2 | +1.4 |
| 真實里程碑 | 91.4 | +17.2 |
按任意步數切分幾乎沒有價值,按真實結構切才有價值。
回頭看我們第三層的判據——消耗了 75% 的工具輪次預算——正是一個「任意步數」觸發器。它問的是你燒了多少,不是你達成了什麼。正確的形態應該是:
✗ if 步數 > N → 干預
✓ if 步數 > N 且 零驗證里程碑 → 干預後者不會誤傷一個正常推進的長任務,因為這種任務每隔一段就會有里程碑。前者會。
還有一個正反饋環
把常數放在一起看:上下文到 82% 觸發壓縮;而自旋收斂要壓縮滿兩次、再加燒掉 75% 輪次預算才響。
打轉 → 上下文填滿 → 觸發壓縮 → 持久狀態被摘要掉
→ 重新推導丟失的東西 → 更多打轉自旋檢測器是通過「觀察到壓縮發生了很多次」來判斷的,而壓縮正是造成失憶的那一步。它檢測的是下游症狀,還要等這個環轉滿兩圈才響。
更麻煩的是,它的干預方式是提醒模型回錨到自己的持久狀態。可如果持久狀態恰恰就是被壓縮掉的那部分,模型根本沒有東西可以回錨。這是在 prompt 層修一個 state 層的問題。
我們要加的:輸出側停滯檢測
補第四層,由兩個計數器構成。兩者都從宿主已經記錄的工具觀測里機械派生,都不需要模型判斷。
距上一個已驗證里程碑的步數。最簡單的進展度量,配合上面那個復合判據使用,不單獨用。
去重後的新增狀態變化數。兩者中更有價值的一個:
- 讀檔案時內容 hash 與此前一致,就不算新資訊——換個行號範圍讀同一個檔案,hash 不會變。
- 命令名、退出碼、輸出 hash 三者都與此前一致,不算新資訊。
- 寫入後的 after-hash 等於 before-hash,說明根本沒寫進去。
這個計數器專抓簽名匹配漏掉的那種情況:動作各不相同,資訊增益為零。判據是機械的,不需要任何語義理解。
然後,壓力應該是連續的,而不是一次性提醒:
把計數器擺進上下文,讓模型看見
→ 強制修訂計劃(承認此路不通)
→ 問使用者
→ 中止,但保留已達成的里程碑最後一級很重要。如果這次運行注定要停,那也該帶著已經掙到的成果停——這正是論文量到的那 39%~47% 被白白丟掉的部分進度。
論文幫不上的地方
BEACON 是訓練方法。它讓訓練出的策略不容易游走,但它本身沒有運行時的檢測和干預機制。它提供的是「進展」的定義,不是控制器。閾值怎麼定、階梯怎麼升、什麼時候中止,都得我們自己設計。
它的 per-step 基線還需要「組內平均段長」作參照。而生產環境里,同一個使用者任務通常只跑一次,沒有這個 group。能用的替代只有歷史同類任務的統計,噪聲大得多,也完全沒有論文里那條方差隔離的保證。
第一步是量,不是改
在動任何決策邏輯之前,我們想先埋點,回答一個現在答不上來的問題:線上發生的那些停滯,有多少是失憶型,有多少是無梯度型?
- 多數伴隨資訊增益歸零、但呼叫簽名各不相同 → 無梯度型。現有三層結構上就抓不到,補輸出側檢測才是解法。
- 多數伴隨重讀已被壓縮掉的內容 → 失憶型。該修的是壓縮時保留什麼。
這兩個結論對應的投入完全不同。先量比先設計劃算,而且埋點幾乎是免費的——那些觀測資料本來就存在。
上面所有內容都壓在一個概念上,而這篇一直在用它卻沒定義過:已驗證的里程碑。下一篇就講這個。為什麼「模型說這步做完了」不構成它真的做完了的證據、產品里其實已經有的兩半為什麼一直沒接上,以及一條論文里沒有的判據:看不可逆,別看重要。