跳到內容

自主目標迴圈(Goal Loop)

在對話裡丟一個目標,AI 員工就會自主規劃、執行、自我驗收,做到完成或卡住時回來通知你。這一頁說明從頻道使用 /goal 的方式、自主程度(AutonomyLevel)分級、相關設定鍵,以及卡住轉人工時的按鈕語意。

派工引擎自 v1.59 起預設開啟(沒有目標任務時只做週期性的 SQLite 輪詢,只有任務真正進入 review 才會花 LLM 呼叫,見下方「兩段式驗收裁決」),一問一答對話完全不受影響。不想要時在 config.toml 設 [dispatch] enabled = false,或到儀表板「設定 → 自動化」關閉「派工引擎」開關(免重啟熱生效)。


在任何已接通的頻道(Telegram / Discord / Slack / LINE / …)對 AI 員工輸入:

指令 行為
/goal <目標描述> 建立一個自主目標任務,指派給當前對話的 AI 員工。沒有另外指定驗收標準時,以目標描述本身當作驗收基準。
/goal <目標> || <驗收標準> 用 || 分隔:前半是目標,後半是驗收標準(判官核可的依據)。
/goal <目標> || <驗收標準> || outcome:<spec> 再加一段結構化產出驗收(見下方「結構化產出驗收」)。交付前先跑零成本的 deterministic 校驗,未達標直接退回修正,不燒判官。
/goal status 列出當前 AI 員工進行中的目標任務(短碼 / 狀態 / 第幾輪)。
/goal 顯示用法說明。

範例

/goal 整理這批客戶資料成月報並寄出 || 報表含每月營收圖表,寄到 boss@example.com
/goal 產出 Q3 月報 || 含每月營收圖表 || outcome:files:report.docx

建立後會回覆確認訊息,包含任務短碼、上限輪數,以及「完成或卡住會在這裡通知你」。任務進度與需人工的通知會推回你發起的這個對話(來源頻道),而不只是 AI 員工的 [proactive] 通知頻道。

若派工引擎被關閉([dispatch] enabled = false),任務仍會建立,但確認訊息會提醒你它不會自動開始執行。


目標契約:建立時凍結、事後不能悄悄改

Section titled “目標契約:建立時凍結、事後不能悄悄改”

建立目標的當下,驗收標準會被凍結成一份不可變的基準(acceptance_criteria_baseline)。之後所有裁決(第一階段評估器、MAV 驗收判官)一律讀這份凍結基準,不讀事後可能被改動的欄位。這是為了擋掉兩個方向的「悄悄改契約」:AI 員工不能在做的過程中把驗收標準改鬆,操作者也不會誤以為改了儀表板上的欄位就等於重新設定了裁決依據。

  • AI 員工不能改:agent 身分呼叫 MCP tasks_update 若帶了 acceptance_criteria 想改自己 goal 任務的驗收標準,會被整筆拒絕,並留一筆審計紀錄(原因 goal_contract_frozen)。goal 任務的 title 與 description 就是判官讀到的目標,AI 員工呼叫 tasks_update 改其中任何一個,也會以同樣方式被拒絕。任務上的控制用 tag(outcome:…、grant:…、auto-research),AI 員工也不能新增、移除或調換順序(原因 reserved_tag_change)。
  • 操作者可以編輯顯示用的副本,但不會回頭改變裁決依據:儀表板 tasks.update 仍可以修改任務上顯示的 acceptance_criteria(例如補充說明給人看),但凍結的基準值不會跟著變。判官與評估器繼續照原本建立時的標準裁決。真的要換一組驗收標準,等於是換一個目標。
  • 沒有凍結基準的舊任務(凍結機制上線前建立的)退回讀可變欄位,行為與過去一致。

/goal <目標描述>(不帶 ||)仍會照常建立任務,目標描述本身當驗收基準,但確認訊息會多附一段提示,幫你想清楚下次要不要補得更精確:

💡 這次沒有另外指定驗收標準,之後想清楚這四件事會更好抓:
• 目標:要達成什麼
• 輸入:需要用到哪些資料或素材
• 輸出格式:成果長什麼樣子(例如 Word/Excel/一段文字/一張圖)
• 約束:風格、時限等限制,以及「怎樣才算完成」
建議補 3-5 條具體、看得出結果的驗收標準(例如「報表含每月營收圖表」而非「做得好」),
用 /goal 描述 || 標準 格式重新交付一次即可。

有明確帶 || 驗收標準時不會出現這段提示。開啟 planner_enabled 的子任務拆解也套用同一套紀律:每條驗收標準寫「結果」不寫「做法」(凍結 HOW 會讓走不同但同樣正確路徑的產出被誤判失敗)、精簡到 3-5 條就夠(契約愈肥,子任務愈難收斂)、範圍外的事項列為 Non-goals,不要硬塞進驗收項。


凍結的驗收標準是一整段文字。判官被要求逐條檢查,但過去沒有任何程式讀得到的證據能說明每一條都被處理過。驗收帳本補的就是這個。

何時建立。 經儀表板或 MCP tasks_create kind="goal" 建立的目標,會在建立當下從凍結基準產生帳本:每個非空行一條,依序編號 C1、C2…。最多追蹤 20 條,第 21 行起併入 C20 並附註,不會丟掉。系統另外替每條算出穩定 id(canonical_id(任務 id, "criterion", 序號, 原文指紋)),模型只需回填短代號,不會自己造 id。這個功能上線前建立的目標、或模式為 off 時建立的目標沒有帳本,行為完全照舊。從聊天 /goal 指令、以及確認目標建議(「立為目標任務」與「想一想」兩種)建立的目標,也用同一套方式建立帳本。autopilot 規則建立的目標、以及規劃器拆出的子任務沒有帳本。

執行者看到什麼。 每一輪派工訊息在 <state> 區塊後面多一段 ## 驗收帳本,每條一行、附目前狀態,以及回報方式。執行者在 tasks_complete 的結果摘要最後附上標記:

<criteria_status>[{"id": "C1", "status": "covered", "evidence": ["寫入 reports/summary.md"], "unresolved": []},
{"id": "C2", "status": "blocked", "evidence": [], "unresolved": ["沒有寄信權限"]}]</criteria_status>

標記內只能是一個 JSON 陣列(前後不能夾文字、不能有多的欄位),每個代號恰好一次。status 只能是 covered(evidence 必填、unresolved 必須空)、blocked(unresolved 必填)、candidate(自認完成、待確認,evidence 必填)。每項 evidence/unresolved 截到 500 字,每欄最多 8 項。違反任何一條,整筆回報作廢:帳本維持原狀、invalid_reports 加一,並在 security_audit.jsonl 寫一筆 criteria_status_invalid(違規說明+最多 200 字遮罩後的標記內容)。沒附標記的回合什麼都不改,也不計數。判官讀到的文字與推回聊天的 ✅ 訊息都會先拿掉這個標記。

三種模式(config.toml [goal_loop] criteria_ledger,每一輪讀取,免重啟):

模式 帳本 判官
off 不建立、不注入、不解析 不變
report(預設) 建立、注入、解析、顯示給人看 多收到一段執行者自述的帳本,明標「自述,不是證據」;回覆格式不變
enforce 同 report 面板必須多回 criteria: [{"id": "C1", "pass": true, "reason": "..."}],每個代號恰好一次。少一條或重複=該條 FAIL;correctness 必須每條都過、且該面向本身也過才算過

未知值一律當 report。切到 enforce 比照 strict_reply_parsing 的觀察期紀律:看過真實判官回合後由你自己切換。enforce 下外部判官(judge = "external")不會被要求逐條裁決,以它自己的 pass/fail 為準。

你看得到什麼。 tasks.timeline 回傳 criteria_ledger(沒有帳本時為 null),內含 mode(目前生效的模式)、units[](id、handle、text、status、evidence、unresolved、updated_round)、last_report_round、invalid_reports。有帳本的目標卡到 needs_human 時,通知卡多一行,例如 驗收帳本:1/3 條已回報達成;C2 受阻(沒有寄信權限);C3 尚未回報。每一輪的帳本也會留在該輪的 iteration 紀錄上,事後可查。

needs_human 的 pause_reason 和帳本回答的是不同問題(迴圈為什麼停下 vs. 哪一條還沒完成),兩者不合併。

目標任務的每個狀態轉移都會推一則簡短(一到三行)的進度訊息回來源對話:

  • 開始執行 / 重試(第 N/上限 輪)
  • 驗收中
  • 未通過 → 修正後重試(附驗收判官回饋摘要)
  • 完成 ✅(附結果摘要)
  • 卡住 → 需要你決定(同時另外推送審批按鈕,附暫停原因分類,見下方「needs_human 按鈕語意」)

同一任務同一狀態不會重複推播。來源對話不存在時,退回 AI 員工的 [proactive] 頻道;兩者都沒有時只寫入儀表板 Activity Feed,不打擾你。

任務被認領(in_progress)後,如果超過 [goal_loop] progress_report_minutes(預設 10 分鐘)都沒有任何可觀察的進度訊號(以 Activity Feed 事件為準,updated_at 欄位會被 lease renewer 定期刷新,不能拿來當作「有在動」的證據),驅動器會推一則「已執行 X 分鐘未回報進度,仍在執行中」的通知(Activity Feed +來源對話),同一輪最多發一次。

這純粹是通報,不是介入:不會重派工、不升級、不取消任務。真正會動手的仍然只有 stalled_secs(重派)、iteration_cap(轉人工)、wall_clock_hours(轉人工)這幾條既有護欄。設成 0(或任何負數)整個功能關閉,且關閉時不花任何額外查詢。


自主迴圈裡,AI 員工偶爾會卡在「對同一個工具、帶同一組參數,一次又一次呼叫」的迴圈。結果早就拿到了,卻沒有意識到自己在重複。這與下方「停滯偵測」不同:停滯偵測要連續兩整輪(派工→驗收)才能發現卡住,工具連擊 advisory 看的是單一輪內的工具呼叫序列,能在判官介入之前就先提醒。

判定依據是同工具、同一組遮罩後參數(沿用稽核紀錄本來就做過的機密遮罩)的連續呼叫次數,門檻與提醒逐級加重:

連續次數 提醒內容
3 次 建議先重讀上一次的執行結果,確認是否已取得所需資訊,避免重複呼叫浪費輪次
5 次 目前的做法可能沒有進展,建議換一個方法或角度切入
8 次 強烈建議停止重複嘗試:直接收斂目前已取得的結果回報,或改用 tasks_block 說明受阻原因並求助

提醒文字會注入下一輪派工的 <state> 區塊,零 LLM 成本、純 advisory:不會擋下、重試或否決任何一次工具呼叫或派工,是否要照做由 AI 員工自己判斷。提醒本身刻意排除在 state_hash 之外,不會干擾既有的(狀態,行動)震盪偵測。

設定 config.toml [goal_loop] tool_streak_advisory(預設 true)可整體關閉。


每個 AI 員工的自主程度由 agent.toml [capabilities] autonomy_level 一個刻度控制。未設定 / 無法解析 → 預設 Approver(保守:只有卡住或需人工才問你)。

級別 行為
operator 迴圈完全不自主驅動;任務建立後靜置,由人手動推進。
collaborator 第一次派工前需人工核准(kickoff 審批),核准後自主重試到完成。
consultant 同 collaborator 的 kickoff 審批。
approver 預設。無 kickoff 閘;卡住 / 需人工時才轉人工審批。
observer 全自動;需人工時只通知、不等待(任務自動結束)。
agent.toml
[capabilities]
autonomy_level = "approver"

階段性授權工具(scoped_tools,v1.41)

Section titled “階段性授權工具(scoped_tools,v1.41)”

高風險工具可以宣告為「持授權才可用」:列在 scoped_tools 的工具,AI 員工沒有拿到有效授權(grant)前一律拒絕,且授權只活在單一任務的生命週期內。任務結束(通過、駁回、轉人工、取消)時全部自動撤銷,不會殘留到下一件事。

agent.toml
[capabilities]
scoped_tools = ["shared_wiki_delete", "odoo_execute"] # 這些工具需逐任務授權
grant_ttl_secs = 3600 # 授權硬性存活上限(秒),預設 3600

取得授權的兩條路:

  1. AI 員工自行申請:呼叫 MCP 工具 capability_request { tool, reason, task_id? },會轉成一則審批(與其他審批走同一個通知/儀表板介面);你核准後授權生效,逾時未決視同拒絕。
  2. 目標任務開工時一併授予:goal 任務的 tags 加 grant:<工具名>,kickoff 審批(collaborator/consultant 級)通過時原子性授予,任務結束自動收回。

判定一律 fail-closed:授權資料庫讀不到就是沒有授權。未列入 scoped_tools 的工具完全不受影響。


[dispatch]
enabled = true # 啟用自主派工引擎(含 goal loop 驅動器)。預設 false
policy = "fixed_hierarchy" # 派工策略(選哪個 AI 員工接任務)。見下方「派工策略」。預設 fixed_hierarchy
grounding_precheck_enabled = true # 驗收前的證據落地預檢(見「證據落地預檢」)。預設 true
two_stage_judge = true # 驗收前先跑便宜的第一階段評估(見「兩段式驗收裁決」)。預設 true
strict_reply_parsing = "shadow" # 判官回覆的嚴格 JSON 契約:off / shadow / enforce(見「判官回覆嚴格契約」)。預設 shadow
judge = "mav" # 由誰做驗收裁決(見「換掉驗收判官」)。mav / external(evaluator_only / human_only 已在 v1.69.0 移除)。預設 mav
judge_provider = "antigravity" # 選填:讓判官跑在另一個 runtime 上(見「讓判官跑在另一個模型上」)。未設 ⇒ 預設的工具用 runtime
judge_model = "gemini-3-pro-preview" # 選填:該 runtime 內的判官模型 id。未設 ⇒ 預設的工具用模型
admission = "queue" # 子代理(ephemeral spawn)撞並發上限時的處置,"queue" 或 "fail"。預設 queue(見下方「ephemeral spawn 准入排隊」)
[task_forward_model] # 任務層前瞻模型(見同名章節)。v1.54 起預設開啟
enabled = true
[goal_loop]
iteration_cap = 5 # 困難目標的硬性派工上限,超過 → 轉人工。預設 5
iteration_cap_simple = 3 # 簡單目標的派工上限(動態判官深度)。預設 3
wall_clock_hours = 24 # 從建立起算的牆鐘預算(小時),超過 → 轉人工。預設 24
max_concurrent = 3 # 同時在飛的目標任務上限(防 spawn 風暴)。預設 3
tick_secs = 30 # 驅動器輪詢週期(秒)。預設 30
stalled_secs = 600 # 派工後未被認領視為停滯、可重派的秒數。預設 600
planner_enabled = false # 開啟後允許把目標拆成帶依賴的子任務 DAG(見「平行子任務」)。預設 false
resume_on_restart = "pause" # gateway 重啟時 in-flight 目標任務的處置,"auto" 或 "pause"(見「重啟行為」)。預設 pause,可在儀表板「設定 → 自動化」切換
progress_report_minutes = 10 # 已認領任務多久沒進度訊號才通報一次,`0` 關閉(見「逾時進度通報」)。預設 10
tool_streak_advisory = true # 同工具同參數連擊 3/5/8 次時是否注入提醒(見「工具連擊 advisory」)。預設 true
criteria_ledger = "report" # 驗收標準逐條帳本:off / report / enforce(見「驗收帳本」)。預設 report
steering_enabled = false # 任務頁的「下一輪的指示」(見「任務頁的送指示與停止」)。預設 false
[dispatch_guard] # 回饋路徑斷路器(防再生型無限迴圈)
window_secs = 60 # 滑動窗長度(秒)。預設 60
max_in_window = 20 # 一個窗內允許的派工次數,超過即熔斷。預設 20
cooldown_secs = 60 # 熔斷後拒絕派工的冷卻秒數。預設 60
max_hop_depth = 5 # 委派鏈跨行程 re-spawn 的深度上限。預設 5

所有區塊都可省略;缺省 / 部分設定一律退回上表的內建預設。未知的 policy 值一律退回 fixed_hierarchy 並記一筆警告。


開啟 [goal_loop] planner_enabled = true 後,建立目標時會先讓 AI 員工「試著」把目標拆成一組帶依賴標注的子任務(例如:先各自查兩個資料源、再彙整)。拆出來的子任務會各自進 Task Board,depends_on 全部完成的子任務會並行開跑,各自獨立驗收。並行度仍受 max_concurrent 與 dispatch_guard 斷路器約束,不會繞過。

  • 非強制:模型判斷不需要拆(或回覆無法解析)時,就退回單一任務,行為與關閉時完全一致。
  • 循環依賴防護:拆出來的計畫若含循環依賴(或索引越界),整份計畫作廢、退回單一任務並記警告,絕不落地一個壞掉的 DAG。
  • 上游卡住不孤兒化:某個子任務的上游依賴走到 failed / cancelled / needs_human(或依賴不存在),下游會繼承升級一起轉人工,讓你看到整條被卡住的分支;上游只是還在跑時,下游該輪凍結、下一輪再看。

預期效益以「多資料源查詢型」目標最大;獨立重測顯示加速約 1.25 倍(非論文自報的 3.7 倍),請以 eval 實測為準再推廣。


[dispatch] policy 決定「選哪個 AI 員工接一項目標任務」。預設 fixed_hierarchy 的行為與過去完全相同(派給任務原本指派的員工)。

策略 行為
fixed_hierarchy 預設。派給任務原本的 assigned_to,不改動。零 LLM 成本、完全確定性。
round_robin 依「任務類別」(有標籤取第一個標籤,否則取優先級)在員工名冊中輪詢分派。狀態僅存記憶體,重啟即從頭。
llm_select 由工具用 LLM 從名冊挑最合適的員工。失敗關閉:輸出不在名冊內、或解析/LLM 失敗,一律退回 fixed_hierarchy 的結果,絕不派給捏造的員工。不硬編碼任何模型名(走設定的工具用 runtime)。
role_team 選出的仍然是AI 員工,與 fixed_hierarchy 完全一樣。角色活在該員工內部,見下方「團隊回合」。這個設定的用途是讓「這個部署會組團隊」在日誌與遙測中看得到,它不會改變任務指派給誰。

名冊 = <home>/agents/ 下的員工目錄。名冊為空時,round_robin / llm_select 都退回原指派(不孤兒化)。改派會寫回任務的 assigned_to,讓 heartbeat 拉取與活動記錄一致。


一位 AI 員工可以在自己內部把一輪目標當成小團隊來做:規劃 → 執行 → 審核,每個角色各用自己廠商的模型。自 v1.66 起 [team] enabled 預設為 true,但真正讓團隊成形的是在 [team.roles] 指名第二家廠商,與這個旗標無關。沒寫任何角色時,執行與審核兩個角色都會 cascade 到員工自己的模型,屬於同一個模型家族,去相關規則會拒絕這份規格:任務照舊走 Solo,安靜地,不留稽核列。完整說明見 56-team-as-agent.md,這一節只談它對 goal loop 的影響。

整條路徑(規劃者、執行者、審核者,以及在角色之間傳遞封包的 team_handoff 工具)都已落地,並已實際跑過一輪被判官接受的活體回合;這只是一次整合結果,不代表已量測出勝過 Solo。只在你看得到的部署上設定 [team.roles]。enabled = false(全域或單一員工)與 gate = "always_solo" 都是一行還原。

對話裡沒有任何新東西。員工用同一個聲音回答,進度出現在同一個看板,需要你的任務仍帶著六種暫停分類之一。內部則把一輪拆成三個階段,取代原本的一則喚醒訊息,只有執行者的最終成品會送到驗收判官,而判官就是同一套兩段式評估器加三面向判官團。團隊改變的是誰來做事,不改變誰來判定做完。

團隊任務有兩種新的原因會落到你手上:

暫停 發生了什麼
需要決策(blocked_needs_decision) 規劃階段沒有交回任何子任務。迴圈不會從它的敘述文字去猜一份拆解,而是問你這個目標到底能不能拆,或是乾脆讓單一員工去跑。
預算用盡(budget_exhausted) 任務在已經花掉一部分之後用完了角色成員的 spawn 額度。它會交出自己做出的最佳一輪,與其他預算升級相同。預算連一輪降級後的編組都付不起的任務會改走 Solo,因為讓一件根本沒開始的工作被停住,比用一般方式做完更糟。

每一輪開始前,一組零 LLM 的規則(crates/duduclaw-core/src/team_gate.rs)決定走 Solo 還是 Team。它刻意偏向 Solo。

  1. 強制 Solo:員工開了 [container] sandbox_enabled = true(排在所有模式之前檢查,always_team 也蓋不過,所以開了沙箱的員工不會組成團隊)、即時通道回合、仍在等你核准的 plan-first 目標、計畫裡含不可逆動作、剩餘預算不足三輪。
  2. 計算四個訊號:批量(至少四個可獨立進行的工作項目,且沒有依賴樞紐)、脈絡溢出(估計的輸入超過模型的脈絡視窗)、能力差距(角色矩陣的差距達到或超過矩陣宣告的 MDE)、長程(至少三條驗收標準,且任務會產出檔案)。量不到的訊號永遠不會觸發。
  3. 判定:三個以上訊號成團;零或一個走 Solo;剛好兩個是灰帶。

灰帶時,迴圈會先跑一次規劃階段(這是任務本來就需要的一次呼叫),再套用同一套規則,這次的批量訊號改用規劃者實際寫出的子任務封包來量。計畫出現之前批量訊號沒有東西可量,所以灰帶一定是另外三個訊號中有兩個觸發;第二次判斷只有在計畫含四個以上、彼此之間沒有依賴樞紐的子任務時才成團,否則退回一般的單一員工回合。沒有規劃角色的團隊規格做不了第二次判斷,直接走 Solo。

每次裁決都會以 team_gate_decision 寫入稽核日誌,附上觸發的訊號,所以一個部署的 Solo/Team 比例可以量測,不必靠印象。

任務所用的團隊在建立時決定一次,存在任務上。之後對 [team] 的修改只影響下一個任務。規格驗證失敗(最常見的是審核者與執行者同一個模型家族)時完全不會成團:任務走 Solo,不會出現局部團隊。

這個拒絕是否有聲音,取決於是誰要求組團隊。明寫了 enabled = true 的操作者,或設了角色但驗證失敗的操作者,會拿到以 team_refused 記錄的稽核列,附上角色與原因。完全沒動過 [team] 的部署只會得到一行 debug!,稽核日誌裡什麼都沒有:這個拒絕是 v1.66 翻轉預設值之後每個安裝的預設狀態,若在每個目標任務上都蓋一列,真正的拒絕反而會找不到。

角色的 model 沒設時,也可以先從量測得到的能力矩陣(<DUDUCLAW_HOME> 下的 role_model_matrix.toml,由 duduclaw eval --matrix 寫出)取值,取不到才退回員工的 [model] preferred。只採計該角色自己 runtime 上的 resolved 格,平手不選任何模型,明確設定的模型永遠優先。

config.toml
[dispatch.team_budget]
max_spawns_per_task = 12 # 4 個角色 x 3 輪;下限鉗到 3
max_turns_per_role = 3
degrade_order = ["utility", "verifier_second_pass", "executor_replica"]

一輪的計費是 planner? + executors + verifier + repair?。審核者是 utility 呼叫而不是 scaffold,但它會寫自己的帳本列,而預算計算的正是帶有 member id 的列,所以它和其他階段一樣佔一個額度。下限 3 就是規劃者 + 執行者 + 審核者。

spawn 預算縮水時,能力依下列順序放棄:先放棄合成,再放棄審核者的一次修補,最後 fan-out 收斂成單一執行者,之後任務才以 budget_exhausted 升級。無法辨識的項目會被丟棄並記一筆警告;整份清單都不可用時沿用預設鏈。

第一個團隊回合開始前,迴圈也會檢查一次 [dispatch] ephemeral_max_active 是否容納得了這份設定可能需要的並行角色成員數(max_concurrent × iteration_cap × roles,預設下是 45,而預設上限為 32),不足時警告並給出該調高的數字。這只是提醒,不會強制:超過上限後角色 spawn 會排隊、也可能過期,否則要到很晚才會以「某一輪莫名缺了審核者」的形式浮現。

角色成員的 spawn 走自己的斷路器 bucket 與預算([dispatch_guard] role_team_max_in_window,預設 60),所以組團隊中的員工絕不會觸發保護員工自己子代理 spawn 的斷路器。

團隊回合的證據:員工加上它的成員

Section titled “團隊回合的證據:員工加上它的成員”

一輪之後的所有環節,包括團隊自己的審核者、證據落地預檢,以及 MAV 判官團的 <tool_activity> 摘要,都讀取任務在認領到驗收這段時間窗內的工具呼叫稽核軌跡。Solo 回合時,這條軌跡屬於單一個 agent id。團隊回合則不然:工作由短命的角色成員以它們自己的 id 完成,而員工自己的時間窗裡可能只有開啟這一輪的那一筆帳務呼叫。

這樣讀的話,團隊回合看起來就像一個宣稱做了事、實際什麼都沒做的任務。活體第 3 輪正是如此,審核者與 settle 評估器都用「沒有工具活動支持檔案建立」駁回了確實完成的工作。所以團隊回合的證據集合是員工 ∪ 該輪的角色成員,成員取自 role_turns.jsonl,兩條路徑都一樣:

  • 團隊審核者的 <tool_activity> 區塊,以及
  • settle 路徑的證據落地預檢與判官摘要。

重複的 id 會合併,沒有角色成員的任務不會多出任何 id,所以 Solo 任務看到的證據與以前逐位相同。時間窗不變,因此即使依輪次查詢退回整個任務(迴圈內的進行中迭代計數與 settle 路徑的修訂計數是分開的,跨 gateway 重啟後可能分歧),其他輪的成員也不會貢獻任何內容。

角色成員也在員工的工作區工作,而不是在它們自己的一次性 scaffold 裡,所以成員寫出的檔案在審核者詢問時仍然存在。目前會寫檔的角色只限 claude 與 codex,見 56-team-as-agent.md。

把 id 取聯集有其必要,但還不夠。活體第 8 輪之後又補上兩個證據來源,兩者都由團隊審核者與 settle 路徑共用,因此兩邊不會對同一輪各說各話:

  • 原生工具工作會被持久化。 透過原生工具工作的成員(codex 的 shell、Claude 的 Write)不會發出 MCP 呼叫,所以在此之前它的工作在稽核軌跡裡完全沒有痕跡,第 8 輪就因此駁回了三個確實存在的檔案。現在每個原生工具事件都會以成員的 id 寫成一列 tool_calls.jsonl,帶著遮罩後的呼叫輸入與結果文字,以及 source = "native" 和產生它的 runtime/模型。自我回音類工具的輸出維持被抑制,與 MCP writer 的做法一致,所以角色永遠無法拿自己回音的封包來證明自己的宣稱。
  • <artifact_receipts>:封包宣告的每個 artifacts[].path 都會對員工的工作區做 stat 與雜湊,結果(<path> <bytes>B sha256=<hex> exists|missing|mismatch)同時成為 prompt 區塊與一列 artifact_receipt 稽核。宣告了路徑但沒給雜湊時,雜湊由實際位元組補上;宣告的雜湊與實際不一致時,保留宣告值並記為 mismatch,同時產生 team_packet_artifact_mismatch 事件,讓調包留得下痕跡,不會被悄悄校正。只有 exists 算確認。

沒有宣告任何產物的任務不會多出區塊,沒有原生工具成員的 Solo 回合看到的內容與以前完全相同。

事件 意義
team_gate_decision Solo / Team / 灰帶,附觸發的訊號
team_refused 已啟用的規格驗證失敗,任務走 Solo
team_round_started 三階段回合開始,附角色與任何降級步驟
team_member_spawned 建立了一個角色成員(角色、runtime、模型)
team_stage_failed 某個階段沒有產出下一階段需要的東西,或根本無法執行,附 role、runtime、model 與錯誤
team_handoff 某個角色歸檔了一個封包(leg、輪次、大小、對象、檔案)
team_packet_skipped 封包檔無法讀取、標記錯誤或無效而被忽略
team_packet_artifact_refused 封包宣告的產物路徑解析到員工工作區之外
team_packet_fidelity_corrected 封包自行宣告的證據等級與實際觀察不符,已被覆寫

每個階段都透過呼叫 team_handoff 交接,路徑由工具推導,不由呼叫者提供:

~/.duduclaw/team_packets/<task_id>/r<round>/planner-to-executor.json
~/.duduclaw/team_packets/<task_id>/r<round>/planner-to-executor.01.json … up to .99

規劃者把目標拆成四個子任務時,會在同一個 leg 上寫四個封包,所以新的封包佔下一個編號槽,重新歸檔同一個 packet_id 則覆寫它自己的檔案(逾時後重試不會讓子任務重複)。下一階段依數字順序讀回這些槽:先是標準檔,再是 .01、.02……。封包自己的 from_role / to_role / 任務 / 輪次與所在檔案不符,或驗證失敗時,會被略過並以 team_packet_skipped 稽核;一個壞檔不會讓該階段失去其餘封包。

每個階段也會在 <home>/role_turns.jsonl 追加一列:角色、runtime、模型、effort、它產出的封包、觀察的證據等級、結束方式。權限、鎖與輪替方式與 tool_calls.jsonl 相同。


子代理准入排隊(ephemeral spawn admission)

Section titled “子代理准入排隊(ephemeral spawn admission)”

目標拆解、委派等路徑有時會需要開一個短命的子代理(ephemeral spawn)。撞到並發上限(ephemeral_max_active,預設 32)時,config.toml [dispatch] admission 決定怎麼處理這次超限請求:

值 行為
queue 預設。有界 FIFO 排隊:請求不會憑空消失,等有空位釋放就依序執行。每張排隊券帶 TTL(queue_item_ttl_secs,預設 600 秒),逾期直接丟棄並記一筆稽核;佇列本身有深度上限(queue_max_depth,預設 64),滿了就明確拒絕(避免無界佇列本身變成新的失控風險)。發起請求的 turn/session 結束時,屬於它的排隊券會一併作廢,不會讓一個早就結束的流程突然冒出遲到的子代理。
fail 舊行為:超限直接拒絕,不排隊。

ephemeral_max_active 本身遵守「可調但不可為 0」:設成 0 會被鉗到 1 並記一筆警告,並發上限永遠不能被設定成完全關閉。

config.toml
[dispatch]
admission = "queue" # "queue"(預設)或 "fail"
queue_max_depth = 64 # 佇列深度上限,超過即拒絕
queue_item_ttl_secs = 600 # 排隊券存活秒數,逾期丟棄並稽核
ephemeral_max_active = 32 # 並發上限,0 會被鉗到 1

結構化產出驗收(outcome schema,WP2.4)

Section titled “結構化產出驗收(outcome schema,WP2.4)”

/goal … || outcome:<spec> 讓你在自由文字的驗收標準之外,再加一層機器可校驗的產出契約。當 AI 員工回報完成、任務進入 review 時,這層契約會在驗收判官(LLM)之前先跑一次 deterministic、零 LLM 成本的校驗:

  • 校驗不通過 → 任務直接退回 revising,回饋訊息帶具體缺陷(缺哪個欄位、少哪個檔),完全不呼叫判官。這是擋判官假陽性的防線:結構上明顯不合格的產出,不會被過度寬鬆的判官放行,也不浪費一次判官 LLM call。
  • 校驗通過 → 才進判官,且判官 prompt 會附上「結構化產出驗收已通過 deterministic 校驗」的註記,讓判官專注在品質面向。

三種 spec 型別:

spec 意義
outcome:text 預設。無結構化契約,行為與未加 outcome 時完全一致(不持久化、判官前不跑任何校驗)。
outcome:json:<JSON Schema> JSON Schema 子集(object / array / string / number / integer / boolean,支援 properties / required / items)。校驗 AI 員工最終回覆中的 ```json 區塊(找不到 fenced 區塊則退而解析整段回覆)。缺欄位 / 型別不符都會列成具體缺陷。
outcome:files:<glob,glob> 斷言 AI 員工工作目錄下有符合每個 glob 的產出檔(支援 */?)。例:outcome:files:report.docx, out/*.pdf。

範例FENCE9

界線與 fail-closed 行為:

  • 路徑穿越拒絕:files: 的 glob 若是絕對路徑、家目錄(~)、或含 .. 上層目錄,一律在 /goal 建立時就拒絕(fail-closed,任務不建立),校驗時再擋一次。工作目錄基準是 <home>/agents/<agent>/。
  • 畸形 spec 拒絕:json: 不是合法 JSON 物件、files: 空清單、未知型別前綴 → /goal 直接回錯誤、不建立任務(不會靜默降級成 text)。
  • 持久化:spec 以單一 outcome:<base64url> 標籤存在任務既有的 tags 欄位(base64url 不含逗號,不與標籤分隔衝突),不改資料庫 schema。text 不持久化。
  • 與 planner 的關係:設了 outcome spec 時會略過 planner_enabled 的子任務拆分,結構化契約針對單一最終交付物,不套用到每個被拆出的子任務。
  • 判官仍是後盾:標籤若毀損無法解碼,deterministic 校驗跳過、直接交給判官(判官照樣把關),不會因為觀測性缺口而卡住任務。

證據落地預檢(grounding precheck,v1.53)

Section titled “證據落地預檢(grounding precheck,v1.53)”

AI 員工回報完成、任務進入驗收時,在呼叫驗收判官之前會先跑一道零 LLM 的 證據預檢:把最終回覆與這次任務實際的工具執行紀錄(稽核日誌裡的工具結果) 比對。回覆若宣稱查到了什麼、做了什麼,卻找不到任何一段與真實工具結果重疊 的內容,就直接退回修正,不燒判官。兩個防偽細節:

  • 自我回音不算證據:像 tasks_complete 這類會把 AI 員工自己寫的摘要 原樣回傳的工具,列在排除名單上,不能拿自己的話證明自己。
  • 自己餵進去的不算:AI 員工放進工具呼叫參數裡的文字會從證據中扣除, 只有工具真正回傳的內容才算數。

config.toml [dispatch] grounding_precheck_enabled = false 可關閉。沒有 任何工具紀錄可比對時(例如純對話型任務),預檢會跳過(Skip),不誤傷。


任務層前瞻模型(task forward model,v1.53;v1.54 起預設開啟)

Section titled “任務層前瞻模型(task forward model,v1.53;v1.54 起預設開啟)”

開啟後,goal loop 在每次派工前會依過往同類任務的統計先「預測」這次執行 大概會如何(會不會失敗、大概動用哪些工具類別),執行結束後把預測與實際 觀察比對並記錄成轉移,讓系統對「做這類事會發生什麼」累積出任務層的世界 模型。每個 runtime 都會跑這套流程,但看得到多少,取決於該 runtime 有沒有把原生工具事件餵給收集器。Claude(派工的 stream-json 路徑)、Codex、Gemini CLI(v1.67.0 起棄用,v1.71.0 移除)、Antigravity 與 OpenAI 相容的 agent 會記錄原生工具事件,觀察可以達到 Full。Grok 與七個通用 print-mode CLI(Qwen Code、Kimi Code、GitHub Copilot CLI、Kiro、Cursor、Mistral Vibe、OpenCode)沒有接收集器,觀察只會是 McpOnly(只靠 tool_calls.jsonl),該輪在那個檔案裡沒有紀錄時則是 None。流程如下:

  • 預測分層退化:有同類統計用統計、沒有就用整體邊際、再沒有用先驗 預設值,冷啟動不花任何 LLM 費用。
  • 觀察誠實分級:每筆觀察都標記證據保真度(原生工具事件 / 只有稽核 日誌 / 無證據),不會把「沒看到」當成「沒發生」。
  • <state> 狀態區塊:任務進行中,提示裡會注入一個結構化的目前狀態 區塊,AI 員工可透過回覆中的狀態更新標籤修訂它;搭配(狀態,行動)訪問 圖,同一狀態重複做同一動作兩次以上會提早轉人工(震盪偵測)。
  • 預示警告:預測顯示這次派工大機率失敗時,派工提示會附上警告脈絡, 但不直接擋下(預測是輔助,不是閘門)。
  • 任務規則歸納:同型轉移重複出現時,以確定性模板歸納成任務規則注入 後續提示(上限 2 條),與其他學習規則共用同一套 helpful/harmful 生命週期,表現不好會自動退休。FENCE10

AI 員工回報完成、任務進入 review 之後,不是每次都直接燒一次完整的 MAV 判官團。先跑一個便宜很多的第一階段評估:無工具、單次 LLM 呼叫,只輸出三選一的 JSON 判定:

判定 意思 後續動作
continue 這輪還沒做完,但方向對 跳過 MAV,直接拿評估器給的下一步當回饋重新派工(計入迭代上限)
blocked 卡在外部阻礙(缺權限、缺資料、等第三方……) 直接轉 needs_human,不必假裝走完一輪判官
candidate_complete 看起來是完成候選 才進現行的 MAV 三面向判官團仔細核

評估器出任何狀況(逾時、解析失敗、呼叫本身出錯)一律降級直接跑 MAV 判官:絕不會因為第一階段故障就自動判過或自動判失敗,安全性與沒有這層時完全一樣。設定 config.toml [dispatch] two_stage_judge(預設 true);設成 false 退回單段式、每輪都跑完整判官團的舊行為。

「這件工作算不算完成」是整個平台唯一的放行權力點。預設由平台內建的 MAV 三面向判官團裁決,也可以換成別的實作,用 config.toml [dispatch] judge 指定:

值 誰來裁決 適用情境
mav(預設) 第一階段評估器 → MAV 三面向判官團 一般情況
external 你自己的程式(judge_command) 想接自家 CI、規則引擎、或第二個模型當判官
evaluator_only 只跑第一階段評估器,candidate_complete 直接判過 已在 v1.69.0 移除。 設定檔裡殘留的值會改用 mav 驗收(更嚴格,判官費用更高)。請改成 mav:two_stage_judge 本來就先跑便宜的評估器,只有完成候選才付判官團的錢
human_only 沒有機器裁決,每個 review 任務都轉 needs_human 已在 v1.69.0 移除。 設定檔裡殘留的值不會退回機器驗收:每件送驗的工作都停在 needs_human,直到你標記完成,或改好設定後重試(重試會清掉舊成果)。請改用 mav + 每 agent 的 [capabilities] autonomy_level / approval_required_tools

四個值仍然全部解析得到,已經設定棄用模式的部署行為完全不變,只會每個行程記一次警告;若該值是從儀表板寫入的,另記一筆 judge_mode_deprecated 審計事件。儀表板只提供 mav 與 external,但已存的舊值會照樣顯示(標「已棄用」),不會被偷偷換掉。詳見 deprecations.md。

寫錯值不會靜默生效:gateway 會警告並回退 mav(驗收最嚴的一個)。這個設定每次裁決時重讀,跟 two_stage_judge 一樣改完即生效,不必重啟。

與工作者同一個模型家族的判官,往往會原諒該家族自己才會犯的錯。它和工作者有相同的盲點,所以同一家廠商給的第二意見,價值比看起來低(arXiv:2607.13918)。兩個選填的鍵可以把驗收判官,連同便宜的第一階段評估器,一起移到另一個 runtime 與模型上:FENCE11

兩個鍵都是選填,彼此獨立。沒設時,判官沿用一般的工具用模型([runtime] utility_provider / utility_model),這正是每個既有部署目前的行為,所以省略它們不會改變任何事。

範圍:只有全域。 沒有每個 agent 各自的版本。每位 AI 員工的工作都由同一個設定的判官來判。

設定無法被採用時會發生什麼:下表四種情況(前三種在任何東西啟動前就攔下,第四種是判官執行中失敗)判官都會退回預設的工具用模型並繼續運作,因為路由偏好絕不能卡住一次裁決:

情況 行為
judge_model 指名另一家廠商的模型,但 judge_provider 沒設 拒絕,不猜。 在判官 runtime 解析為 Claude 時,只設 judge_model = "gemini-…" 是設定錯誤,不是要系統去推斷 runtime 的指示。覆寫會在任何東西被 spawn 之前就丟棄(所以不花任何成本),並把這次丟棄以 judge_seam_degraded 寫入 security_audit.jsonl。把 runtime 也寫上就能運作。
judge_provider 不是這個 build 認得的 runtime id 整個覆寫被丟棄並警告,訊息列出有效的 id。打錯字絕不會悄悄把判官送到別的後端。
judge_provider 的 CLI 或後端沒有安裝在這台主機上 在 spawn 之前偵測到,覆寫被丟棄,並以同樣方式稽核。
判官 runtime 可用、被使用,之後在執行中失敗(參數錯誤、驗證失敗、崩潰) 判官會在預設的工具用模型上重跑一次,失敗以 judge_seam_degraded 稽核,帶 reason: "hinted_runtime_failed",以及 provider、模型、底層錯誤,和最後實際完成判決的備援模型。關鍵是失敗的判官不會被一般的跨 runtime failover 救回:路由到另一個家族的呼叫會停用跨家族 failover 來執行,所以它永遠不會被工作者自己的家族悄悄接手回答。同家族 failover(Claude 的某一級換到另一級)仍照常運作。

兩個稽核事件讓這項設定的實際效果看得見,而不是被假定,兩者都寫入 security_audit.jsonl:

  • model_routed:覆寫生效,把判官移出了工作者自己的模型。帶 {slot: "judge", from: <worker model>, to: <judge model>, provider, reason: "dispatch.judge_model"}。
  • judge_same_family:工作者與判官其實屬於同一個模型家族,所以你並沒有拿到去相關的第二意見。這是警告,不是拒絕:裁決照常進行。每個(AI 員工, 判官模型)組合在每次 gateway 執行中只記一次,所以一個很長的目標迴圈只會產生一行,不會每輪一行。

與 judge 本身一樣,兩個鍵都在每次裁決時重讀,不必重啟 gateway。

Claude 會遵守「只回覆一個 JSON 物件」。Codex 不一定會,而團隊功能的活體第 8 輪證明這會讓一次裁決白費:設 judge_provider = "codex" 時,第一階段評估器與 MAV 判官團都用散文回答,兩個解析器都以 fail-closed 拒絕(「evaluator reply has no string decision field」)。解析器是對的,自動接受垃圾輸出正是判官解析器絕不能做的事,但一個在結構上無法被解析的判官,就是一個不能用的 seam。

所以每個裁決階段現在都會公布它自己的解析器所要求的 JSON schema,能強制回覆格式的 runtime 就會照做:

階段 Schema
第一階段評估器 {decision: "continue"|"candidate_complete"|"blocked", evidence, next_step}(blocker_key 可宣告但非必填,解析器在非 blocked 判定上會拒絕它)
MAV 判官團 每個啟用面向各一個 {pass, reason} 物件,全部必填;面向集合隨目標難度而定,與 prompt 和解析器的做法完全一致

目前只有 codex 接上了:schema 寫到暫存檔,以 codex exec --output-schema <FILE> 傳入。其他 runtime 只在 debug 層級記錄這個請求並忽略。schema 是偏好,不是前提條件:後端無法約束輸出時不會有任何東西失敗,而且 schema 檔案無法備妥時,呼叫會不帶這個旗標照跑,不會因此不跑。沒有任何設定鍵:schema 由解析器的契約推導,所以解析器變動與 schema 變動會一起移動。

判官 hint 除了 provider 與 model,也可以帶推理 effort。內部的 UtilityModelHint 有三個彼此獨立的部分:provider、model、effort,所以呼叫端可以把判官移到另一家廠商、改變它思考的深度,或兩者都做。

Effort 刻意不算進 hint 的「是否為空」檢查:這裡的空代表「解析到同一組 (provider, model) 規格」,模型家族驗證關心的正是這件事。Effort 改變的是模型想得多用力,不影響由哪個模型回答。

目前還沒有 [dispatch] judge_effort 鍵,設定檔這一半是刻意留作後續。目前沒設判官 effort 時,會退回被評判的 agent 自己 agent.toml [model] effort(各 runtime 的旗標對照與鉗制表見 Effort),對多數設定而言這就是你要的行為:判官思考的力度與它所評判的 agent 相同。

平台會執行這支指令,把一份 JSON 從 stdin 餵進去:FENCE13

指令要在 stdout 印出一個 JSON 物件當裁決:FENCE14

pass 收 true/false,也收 "pass"/"fail" 字串;feedback 可省略。

三件事值得先知道:

  1. 外部判官出任何狀況都退回 MAV 判官團,包含逾時、非零離開碼、stdout 不是合法 JSON、judge_command 沒設好。降級的方向永遠是變嚴,不會變成放行,每次降級都會寫進 security_audit.jsonl(judge_seam_degraded)。
  2. 它的輸出被當成不受信資料。feedback 會進到下一輪派工的 prompt,所以會先過注入掃描並截斷;掃描擋下來時整份裁決作廢,改由 MAV 判官團決定。回饋文字前面會標上來源,你在任務時間軸上看得出哪一段話是外部判官說的。
  3. judge_command 只能改檔案,不能從儀表板改。它指定一支可執行檔,所以 system.update_config RPC 只收 judge(四個列舉值),不收 judge_command 與 judge_timeout_secs;AI 員工本身也寫不了 ~/.duduclaw/config.toml。

想拿 duduclaw eval 當判官的話,直接把 judge_command 指向包一層 duduclaw eval 的腳本即可,不需要另一個模式。

子行程會繼承 gateway 的完整環境變數。 judge_command 是用平台的 process spawn 直接執行(tokio::process::Command),沒有做 env_clear() 或任何白名單過濾。你指定的判官程式看得到 gateway 行程當下的整組環境變數,這包含 gateway 用來呼叫 LLM 供應商、通道 API 的那些密鑰。這不代表資料被主動傳給判官(判官吃的輸入只有上面那份 stdin JSON);判官程式本身有能力讀取這些環境變數(例如惡意或寫壞的程式去讀 std::env::vars())。這不是漏洞,是這個 seam 目前的設計取捨:只指向你自己信任、來源清楚的程式,不要指向第三方或未經審查的執行檔;需要更嚴格隔離(例如判官行程完全看不到 gateway 密鑰)的話,把 judge_command 包成一支先自行清空環境變數、再重新注入判官實際需要的少數變數的 wrapper 腳本。

判官回覆嚴格契約(strict_reply_parsing)

Section titled “判官回覆嚴格契約(strict_reply_parsing)”

三個裁決解析點(MAV 判官團、第一階段評估器、外部判官)目前都用「第一個 { 到最後一個 }」切出 JSON,所以回覆前後夾著散文也會被接受,有些壞掉的回覆還會被順手修好。嚴格契約只接受這種回覆:整段去掉前後空白、最多拆掉一層 ```json 圍欄之後,剛好是一個形狀正確的 JSON 值,而且沒有多餘欄位。判官團是每個啟用面向一個 {pass, reason},評估器是 decision/evidence/next_step/可省略的 blocker_key,外部判官是 pass 加可省略的 feedback。FENCE15

模式 誰決定裁決 嚴格解析
off 現行寬鬆解析,逐位不變 完全不跑
shadow(預設) 現行寬鬆解析 對同一份回覆跑一次,只做比對
enforce 嚴格解析 違反契約就走該解析點原本的故障路徑:判官團回 fail-closed 的未通過;評估器與外部判官視為失敗,改由 MAV 判官團決定

不認得的值一律當 shadow。每次裁決都會重讀這個鍵,改了不用重啟。

每次比對都會讓 Prometheus 計數器 judge_parse_shadow_total{parser, outcome} 加一(parser 是 panel、pre_evaluator 或 external;outcome 是 agree、strict_rejects、lenient_rejects、both_reject 或 disagree)。只要結果不是 agree,就再往 security_audit.jsonl 寫一筆 judge_parse_shadow_mismatch,內容有解析點、比對結果、嚴格解析的違規說明,以及遮罩密鑰後的回覆前 200 字。

預設值從 shadow 換成 enforce,要等操作者看過數字後拍板:至少累積 50 次判官回合或觀察 7 天,而且 strict_rejects 低於比對總數的 2%。系統不會自己切換。想先在自己的部署切成 enforce 的話,先看你用的判官 runtime 的 strict_rejects:習慣在 JSON 後面補一句話的模型,在 enforce 下會讓判官團直接判未通過,評估器和外部判官則退回 MAV。

MAV 判官與第一階段評估器的 prompt 都內建幾條紀律,治的是「判官自己製造假駁回,讓正確的工作卡死」這個活測抓到過的失敗模式:

  • 反棘輪:驗收標準沒變時,不能每一輪都挑一個新毛病出來,這是讓目標永遠無法完成的典型失敗模式。
  • 只稽核、不自建證據:判官只能比對 AI 員工提交的證據與工具稽核摘要,不能自己想像、補寫證據,也不能拿「我覺得更好的做法」當標準。
  • 反契約外擴張:驗收標準沒寫的事項不能被拿來當駁回理由。這是最常見的假駁回,也是明明做對、做在範圍內的工作卡住不前的頭號原因。
  • agent 自稱完成不是證據:「已完成」「已處理好」這類自述本身不構成通過理由,判官必須逐項比對驗收標準與實際產出。

這幾條紀律沒有 config 開關,即刻套用到所有 goal 任務。

判官 prompt 另外帶一行由系統提供(不是執行者自述)的事實:執行者的工作目錄,也就是 <home>/agents/<agent_id> 的絕對路徑(<worker_working_directory>),結果裡的相對路徑都相對於它。這一行在每種模式都有,包含 criteria_ledger = "off";只有派工引擎沒有 home 目錄時才省略。加這一行之前,一個正確的 hello.txt 被駁回兩次,原因是判官把 agent 目錄當成「子目錄,不是工作目錄」。

驗收判官的檢核面向數量會隨目標難度縮放,省下不必要的判官 LLM 成本:

  • 簡單目標(短、單步、無多步/研究/比較/部署/遷移等關鍵詞):判官只查兩個面向 correctness + safety,派工上限用 iteration_cap_simple(預設 3)。
  • 困難目標:完整三面向 MAV panel correctness + completeness + safety,派工上限用 iteration_cap(預設 5)。

safety 面向在任何深度都保留(失敗關閉精神):降深度只裁掉 completeness 的細緻度,安全檢核永不裁撤。難度由本地零 LLM 啟發式(長度 + CJK-aware token 估算 + 關鍵詞)判定,判官深度與派工上限用的是同一套判定,兩者一致。


判斷「連續兩輪卡在同一個地方」不再只看駁回回饋是不是逐字相同。判官每次遣詞用字不一定一樣:「缺少 goal_loop.rs:120 的錯誤處理」跟「你忘記在 goal_loop.rs 第 120 行做驗證」講的是同一個 gap,但字串比對會判成兩件不同的事,讓真正卡住的訊號被措辭差異吃掉。

現在改抽取駁回回饋裡的 path:line 引用與反引號包住的關鍵詞(函式名、變數名、錯誤代號),正規化後(暫存/scratch 路徑歸一成同一個佔位符、忽略大小寫、去重排序)組成一組指紋,同一個 gap 換句話說也會得到同一個指紋。完全抽不到任何引用或關鍵詞時(例如純敘述性的回饋),退回原本的逐字比對,行為相容。連續兩輪同指紋才觸發下面的 needs_human,門檻本身沒有變。

AI 員工在自主迴圈裡有時會用「聽起來像收尾、但其實沒有被判官驗證過」的話結束這一輪,例如「我先做到這裡好了」「請稍後再來查看結果」「已提交待審」「VERDICT: PASS」(自己簽的,不是判官簽的)之類。這些話本身不代表工作有錯,只是一種「流程上」的警訊,值得被記錄下來、也值得下一輪多留意一下。

九條 zh+en 正則只比對 agent 這一輪回覆的最後一段非空文字,命中任何一條會:

  • 記一筆 Activity Feed 事件(goal_loop.premature_stop_suspected)
  • 累加 Prometheus 計數器 goal_loop_bail_pattern_total{pattern="<命中的樣式名>"}
  • 把提示帶進下一輪派工的 <state> 區塊、第一階段評估器的輸入、MAV 判官的輸入:一句中性提醒(「疑似提前收工,請確認任務是否真的完成」),不會替評估器或判官預先下判斷

這層偵測本身不會駁回、卡住或轉人工任何任務,純粹是訊號與提醒,實際要不要判過還是看評估器/判官對證據的判斷。

gateway 重啟或崩潰復原後,還在跑的目標任務預設會轉成 needs_human(resume_on_restart = "pause",預設值):gateway 每次開機時,會把所有非終態的 goal_mode 任務(todo/pending/revising/in_progress/review/blocked)轉成 needs_human(原因 gateway_restart),走既有的通道通知,等你按下「重試」才會繼續。一次非預期的行程重啟或部署,不會悄悄接著跑一個沒人重新確認過安全的目標。

想要接續原本更寬鬆的行為,把 [goal_loop] resume_on_restart 設成 "auto":還在跑的目標任務會直接接續執行,就像行程從未中斷過一樣(這是這項設定出現前的唯一行為)。

不論哪個方向,這個檢查只在 gateway 開機時跑一次,設定熱重載(system.update_config)不會觸發它,變更只在下一次 gateway 真正重啟時生效。

儀表板切換:設定 → 自動化(Automation)分頁的「gateway 重啟後的進行中目標任務」下拉選單可直接切換,不必手動編輯 config.toml;system.update_config 只接受 "auto"/"pause" 兩個值,其餘一律拒絕。


任務轉「需人工」時(達派工上限 / 牆鐘超時 / 連續兩輪駁回且 gap 指紋相同 / 驗收判官在重試預算耗盡時仍不通過 / 上游依賴子任務卡住而繼承升級 / resume_on_restart = "pause" 時 gateway 重啟),會推送四顆按鈕到 AI 員工的控制頻道。

「需人工」不再是單一個桶子。除了既有的自由文字 judge_feedback(判官或評估器的完整回饋,可能好幾句話),每次轉人工的當下,同時會蓋上一個六選一的封閉分類,這是給你一眼分辨「這是什麼類型的卡住」用的,judge_feedback 才是逐句細節:

分類 token UI 文案
no_progress 卡住沒進展
budget_exhausted 次數或時限用盡
blocked_needs_decision 等你決策
infra 系統問題
restart 系統重啟後暫停
unknown 需要人工確認

分類在觸發現場靜態標記(每個轉人工的路徑各自標記自己的類別),絕不從 judge_feedback 的 LLM 敘述反解。模型自己的措辭不可靠、也不該被拿來當路由依據。未分類、遇到辨識不出的舊值,或這個欄位上線前就存在的舊任務,一律讀成 unknown(「需要人工確認」):一個分不清類型的卡住,寧可讓你多看一眼,不能被誤判成一個看似明確、其實是猜的分類。

顯示位置:/goals 看板卡片與任務詳情頁的分類 chip、通道 needs_human 審批訊息裡的「類型」一行(Observer 全自動模式的純通知也有)。任務被人工決定(重試 / 標記完成 / 放棄)後,分類欄會清空,不會殘留到下一次卡住。

按鈕 動作
重試 任務回到待重試(pending),下一輪驅動器再派工。
標記完成 直接標記完成(done)。
放棄 取消任務(cancelled)。
交給我 你接手處理,任務標記由你認領(claimed_by);狀態仍留在 needs_human,所以驅動器本就不會再自動派工(候選查詢只看 todo/pending/revising)。這是目前的實作範圍:停止自動重試+標記+收斂卡片。完整把對話控制權轉給你(讓後續訊息不再進 AI 判斷)是下一階段的功能,尚未實作。

一則訊息的主要動作上限 3 顆,四顆超過此限,因此「放棄」與「交給我」在有次要層級的通道上收進次級:Telegram 是第二排按鈕、Discord 是第二排按鈕、Slack 是原生的 overflow 選單;LINE 沒有對應的次要選單機制,這兩個動作不會出現在 LINE 的快速回覆按鈕上,改在訊息裡以文字說明並附儀表板連結。

按鈕決策是冪等且失敗關閉的:重試/標記完成/放棄只會從 needs_human 狀態轉出,重複按或狀態已變一律無效(no-op);「交給我」則沒有終態可比對,重複按(甚至換一位有權限的人按)就是重新蓋章認領,不會報錯。collaborator / consultant 的 kickoff 審批同理,逾時未決=拒絕(fail-closed)。

自 v1.53 起,轉人工的審批會附上一段模擬預覽(simulate-before-act):若你選擇讓任務繼續,接下來三步大概會發生什麼。模擬產生有 15 秒上限,逾時就不附模擬、照常送出審批(不會因此卡住);模擬引用的知識庫內容限唯讀 namespace,且模擬敘述本身不能決定某個動作是否可逆(不能自證安全)。儀表板審批卡片會渲染這段預覽。

模擬預覽講的是「如果放行,接下來可能發生什麼」;「變更」分頁講的是已經發生了什麼。儀表板的收件匣決策卡與任務詳情頁都多了一個「變更」分頁,列出這個任務歷輪實際動過的檔案:

欄位 內容
路徑 被寫入/修改/刪除的檔案路徑;指令 類型顯示的是那道 shell 指令本身,不會假裝知道它碰了哪些檔案。可一鍵複製。
操作 新建/覆寫、修改、刪除、指令四種。
狀態 失敗或被攔截的呼叫也會列出並標記「未成功」,這正是即時查詢工具狀態看不到的那一半。
摘要片段 寫入內容或指令說明的片段,直接沿用稽核紀錄的遮罩結果(不會為了顯示而重讀原始檔案)。
來源 執行期工具事件(Write / Edit / NotebookEdit / Bash……)或 MCP 稽核紀錄(wiki_write 等)。

證據來自兩條既有軌跡:執行期的原生工具事件在每一輪派工後落成檔案變更紀錄(以任務 id 歸屬),MCP 稽核紀錄則沿用判官 <tool_activity> 同一套「認領→驗收時間窗+執行者」歸屬。查無就是查無:沒有紀錄時分頁直接顯示「此任務沒有留下檔案變更紀錄」,不會用敘述硬湊。

目前顯示的是「動過哪些檔案、動作是什麼」,還不是逐行的 before/after diff。真 diff 需要在寫入前留快照,屬後續項。


「想一想」計畫模式(plan-first,I-1c)

Section titled “「想一想」計畫模式(plan-first,I-1c)”

儀表板的交辦面板除了「問一問」「交辦」,還有第三種模式「想一想」:先讓 AI 員工擬一份執行計畫給你看,你核准後才會真的開始做。

選「想一想」送出時,前端一樣呼叫 tasks.goal_create,只是多帶一個 plan_first: true。後端在建立任務的當下(同步、非派工迴圈的一輪)呼叫工具用 LLM,依目標描述與驗收標準產出一份 3-8 條、純文字的執行計畫(不是 JSON,是給人看的敘述)。任務直接誕生在 needs_human(沿用既有的 blocked_needs_decision「等你決策」分類,沒有新增分類),核准前不會進入派工迴圈的候選查詢,所以連一輪都不會跑。

計畫文字同時寫進兩個地方:

  • judge_feedback(既有欄位):讓「等你決定」卡片、任務詳情頁、通道審批訊息不必改任何顯示邏輯就看得到計畫內容。
  • plan_pending(新欄位,I-1c 專用):與 judge_feedback 分開存放,是因為核准動作本身(任何 needs_human 任務都通用的「重試」按鈕)會用你的核准備註覆寫 judge_feedback;若計畫也存在同一欄位,核准那一刻就會被自己的核准動作蓋掉,永遠傳不到第一輪派工。

核准就是既有的「重試」按鈕,沒有新增按鈕種類。核准後任務轉回 pending,驅動器下一輪 tick 派工時,會把 plan_pending 的內容包成 <execution_plan> 區塊注入這一輪的工作提示,讓 AI 員工照計畫開始執行;注入後立刻清空 plan_pending,所以計畫只會被貼一次,不會每輪重複出現在提示裡。

計畫是指引,不是免驗收的保證。執行完成後仍要走與其他目標任務完全相同的兩段式驗收裁決/MAV 判官團,計畫寫的步驟被判官認定不夠、做錯,一樣會被打回重做。

呼叫工具用 LLM 產計畫的過程本身可能失敗(逾時、傳輸錯誤、或回覆整段空白)。這種情況任務照樣 fail-closed 停在 needs_human,但分類改成 infra(系統問題)而非 blocked_needs_decision,且不會帶 plan_pending。核准動作在這種任務上永遠只是把任務放行到「沒有計畫可注入」的第一輪,不會有計畫憑空消失或被悄悄跳過的情況。


任務詳情頁對進行中的 goal 任務有兩個控制,完整規則見持續任務、任務中送指示與停止。

  • 下一輪的指示(需要 [goal_loop] steering_enabled = true,預設關):最多 4000 字的補充,在下一輪派出時交給 AI 員工,正在跑的這一輪不會被打斷。只有那一輪真的派出去才算送達(「已在第 N 輪交給員工」);被擋下或派送失敗的那一輪,指示會退回等下一輪。指示不會動到凍結的驗收標準,判官也看不到。帶著指示的那一輪由 AI 員工單人執行,不走團隊回合。
  • 停止任務:用儀表板的真實身分立即取消任務與所有子任務。已經在跑的那一輪會先跑完(cancel_pending),確認沒有任何工作還在跑才顯示 stopped;有無法確認的項目(認領租約過期卻沒有完成紀錄、外部動作結果不明、任務樹超過掃描上限)時顯示 stopped_uncertain。確認框會記下打開時的任務版本,確認前任務若已變動,會提示並等你再確認一次。停止的任務不能重試或接著做,要重做請建立新任務。

驅動器(而非模型)掌握硬邊界,卡住的目標不可能無限迴圈:完成訊號只認驗收判官核可(不信任 AI 自評「做完了」);派工上限、牆鐘上限、並行上限、進度震盪偵測、回饋路徑斷路器各自獨立生效,任何一條踩線即轉人工或熔斷。