Orkas Orkas
首頁 博客 架構
架構

循環檢測不等於停滯檢測:抓住那些不重復卻在原地打轉的 Agent

Orkas 有三層循環防線。它們都抓不住一個卡住的強模型,因為三層問的都是「你在不在重復做同一件事」——而卡住的模型恰恰不重復。這篇講這個盲區、我們從 BEACON 借來的「輸出側」進展定義,以及在動手改之前我們打算先量什麼。

這是長程 Agent 設計手記系列的第一篇,起因是我們精讀了 BEACON(浙江大學,arXiv:2605.06078)。

要讓一個 Agent 扛得住長任務,我們認為有兩件事必須做好:

  • 它能持續往目標推進。這件事又拆成三塊:上下文壓縮(忘掉什麼才是安全的)、里程碑設計(怎麼知道一步真的完成了)、防止打轉(卡住了要有人發現)。
  • 它能反思並持續改進。

這篇講防止打轉,因為這是我們代價最大的一個。

一句話版本 一個不重復但在原地打轉的 Agent,照樣需要被抓住 停滯檢測就在 Orkas 里,所以一次長任務會告訴你它卡住了,而不是安靜地把剩下的預算花完。
下載 Orkas — 免費

先說現象

我們收到過使用者反饋,內部也發現過:一些超長任務跑到一半就不往前走了。

看日誌,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=2
SPIN_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。能用的替代只有歷史同類任務的統計,噪聲大得多,也完全沒有論文里那條方差隔離的保證。

第一步是量,不是改

在動任何決策邏輯之前,我們想先埋點,回答一個現在答不上來的問題:線上發生的那些停滯,有多少是失憶型,有多少是無梯度型?

  • 多數伴隨資訊增益歸零、但呼叫簽名各不相同 → 無梯度型。現有三層結構上就抓不到,補輸出側檢測才是解法。
  • 多數伴隨重讀已被壓縮掉的內容 → 失憶型。該修的是壓縮時保留什麼。

這兩個結論對應的投入完全不同。先量比先設計劃算,而且埋點幾乎是免費的——那些觀測資料本來就存在。

上面所有內容都壓在一個概念上,而這篇一直在用它卻沒定義過:已驗證的里程碑。下一篇就講這個。為什麼「模型說這步做完了」不構成它真的做完了的證據、產品里其實已經有的兩半為什麼一直沒接上,以及一條論文里沒有的判據:看不可逆,別看重要。