自主目標迴圈(Goal Loop)
在對話裡丟一個目標,AI 員工就會自主規劃、執行、自我驗收,做到完成或卡住時回來通知你。這一頁說明從頻道使用 /goal 的方式、自主程度(AutonomyLevel)分級、相關設定鍵,以及卡住轉人工時的按鈕語意。
派工引擎自 v1.59 起預設開啟(沒有目標任務時只做週期性的 SQLite 輪詢,只有任務真正進入 review 才會花 LLM 呼叫,見下方「兩段式驗收裁決」),一問一答對話完全不受影響。不想要時在 config.toml 設 [dispatch] enabled = false,或到儀表板「設定 → 自動化」關閉「派工引擎」開關(免重啟熱生效)。
/goal 指令
Section titled “/goal 指令”在任何已接通的頻道(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(例如補充說明給人看),但凍結的基準值不會跟著變。判官與評估器繼續照原本建立時的標準裁決。真的要換一組驗收標準,等於是換一個目標。 - 沒有凍結基準的舊任務(凍結機制上線前建立的)退回讀可變欄位,行為與過去一致。
沒指定驗收標準時的引導
Section titled “沒指定驗收標準時的引導”/goal <目標描述>(不帶 ||)仍會照常建立任務,目標描述本身當驗收基準,但確認訊息會多附一段提示,幫你想清楚下次要不要補得更精確:
💡 這次沒有另外指定驗收標準,之後想清楚這四件事會更好抓: • 目標:要達成什麼 • 輸入:需要用到哪些資料或素材 • 輸出格式:成果長什麼樣子(例如 Word/Excel/一段文字/一張圖) • 約束:風格、時限等限制,以及「怎樣才算完成」 建議補 3-5 條具體、看得出結果的驗收標準(例如「報表含每月營收圖表」而非「做得好」), 用 /goal 描述 || 標準 格式重新交付一次即可。有明確帶 || 驗收標準時不會出現這段提示。開啟 planner_enabled 的子任務拆解也套用同一套紀律:每條驗收標準寫「結果」不寫「做法」(凍結 HOW 會讓走不同但同樣正確路徑的產出被誤判失敗)、精簡到 3-5 條就夠(契約愈肥,子任務愈難收斂)、範圍外的事項列為 Non-goals,不要硬塞進驗收項。
驗收帳本(逐條驗收標準)
Section titled “驗收帳本(逐條驗收標準)”凍結的驗收標準是一整段文字。判官被要求逐條檢查,但過去沒有任何程式讀得到的證據能說明每一條都被處理過。驗收帳本補的就是這個。
何時建立。 經儀表板或 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. 哪一條還沒完成),兩者不合併。
外層進度看板
Section titled “外層進度看板”目標任務的每個狀態轉移都會推一則簡短(一到三行)的進度訊息回來源對話:
- 開始執行 / 重試(第 N/上限 輪)
- 驗收中
- 未通過 → 修正後重試(附驗收判官回饋摘要)
- 完成 ✅(附結果摘要)
- 卡住 → 需要你決定(同時另外推送審批按鈕,附暫停原因分類,見下方「needs_human 按鈕語意」)
同一任務同一狀態不會重複推播。來源對話不存在時,退回 AI 員工的 [proactive] 頻道;兩者都沒有時只寫入儀表板 Activity Feed,不打擾你。
逾時進度通報
Section titled “逾時進度通報”任務被認領(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(或任何負數)整個功能關閉,且關閉時不花任何額外查詢。
工具連擊 advisory(tool streak)
Section titled “工具連擊 advisory(tool streak)”自主迴圈裡,AI 員工偶爾會卡在「對同一個工具、帶同一組參數,一次又一次呼叫」的迴圈。結果早就拿到了,卻沒有意識到自己在重複。這與下方「停滯偵測」不同:停滯偵測要連續兩整輪(派工→驗收)才能發現卡住,工具連擊 advisory 看的是單一輪內的工具呼叫序列,能在判官介入之前就先提醒。
判定依據是同工具、同一組遮罩後參數(沿用稽核紀錄本來就做過的機密遮罩)的連續呼叫次數,門檻與提醒逐級加重:
| 連續次數 | 提醒內容 |
|---|---|
| 3 次 | 建議先重讀上一次的執行結果,確認是否已取得所需資訊,避免重複呼叫浪費輪次 |
| 5 次 | 目前的做法可能沒有進展,建議換一個方法或角度切入 |
| 8 次 | 強烈建議停止重複嘗試:直接收斂目前已取得的結果回報,或改用 tasks_block 說明受阻原因並求助 |
提醒文字會注入下一輪派工的 <state> 區塊,零 LLM 成本、純 advisory:不會擋下、重試或否決任何一次工具呼叫或派工,是否要照做由 AI 員工自己判斷。提醒本身刻意排除在 state_hash 之外,不會干擾既有的(狀態,行動)震盪偵測。
設定 config.toml [goal_loop] tool_streak_advisory(預設 true)可整體關閉。
AutonomyLevel 自主程度五級
Section titled “AutonomyLevel 自主程度五級”每個 AI 員工的自主程度由 agent.toml [capabilities] autonomy_level 一個刻度控制。未設定 / 無法解析 → 預設 Approver(保守:只有卡住或需人工才問你)。
| 級別 | 行為 |
|---|---|
operator |
迴圈完全不自主驅動;任務建立後靜置,由人手動推進。 |
collaborator |
第一次派工前需人工核准(kickoff 審批),核准後自主重試到完成。 |
consultant |
同 collaborator 的 kickoff 審批。 |
approver |
預設。無 kickoff 閘;卡住 / 需人工時才轉人工審批。 |
observer |
全自動;需人工時只通知、不等待(任務自動結束)。 |
[capabilities]autonomy_level = "approver"階段性授權工具(scoped_tools,v1.41)
Section titled “階段性授權工具(scoped_tools,v1.41)”高風險工具可以宣告為「持授權才可用」:列在 scoped_tools 的工具,AI 員工沒有拿到有效授權(grant)前一律拒絕,且授權只活在單一任務的生命週期內。任務結束(通過、駁回、轉人工、取消)時全部自動撤銷,不會殘留到下一件事。
[capabilities]scoped_tools = ["shared_wiki_delete", "odoo_execute"] # 這些工具需逐任務授權grant_ttl_secs = 3600 # 授權硬性存活上限(秒),預設 3600取得授權的兩條路:
- AI 員工自行申請:呼叫 MCP 工具
capability_request { tool, reason, task_id? },會轉成一則審批(與其他審批走同一個通知/儀表板介面);你核准後授權生效,逾時未決視同拒絕。 - 目標任務開工時一併授予:goal 任務的 tags 加
grant:<工具名>,kickoff 審批(collaborator/consultant 級)通過時原子性授予,任務結束自動收回。
判定一律 fail-closed:授權資料庫讀不到就是沒有授權。未列入 scoped_tools 的工具完全不受影響。
config.toml(全域)
Section titled “config.toml(全域)”[dispatch]enabled = true # 啟用自主派工引擎(含 goal loop 驅動器)。預設 falsepolicy = "fixed_hierarchy" # 派工策略(選哪個 AI 員工接任務)。見下方「派工策略」。預設 fixed_hierarchygrounding_precheck_enabled = true # 驗收前的證據落地預檢(見「證據落地預檢」)。預設 truetwo_stage_judge = true # 驗收前先跑便宜的第一階段評估(見「兩段式驗收裁決」)。預設 truestrict_reply_parsing = "shadow" # 判官回覆的嚴格 JSON 契約:off / shadow / enforce(見「判官回覆嚴格契約」)。預設 shadowjudge = "mav" # 由誰做驗收裁決(見「換掉驗收判官」)。mav / external(evaluator_only / human_only 已在 v1.69.0 移除)。預設 mavjudge_provider = "antigravity" # 選填:讓判官跑在另一個 runtime 上(見「讓判官跑在另一個模型上」)。未設 ⇒ 預設的工具用 runtimejudge_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 # 困難目標的硬性派工上限,超過 → 轉人工。預設 5iteration_cap_simple = 3 # 簡單目標的派工上限(動態判官深度)。預設 3wall_clock_hours = 24 # 從建立起算的牆鐘預算(小時),超過 → 轉人工。預設 24max_concurrent = 3 # 同時在飛的目標任務上限(防 spawn 風暴)。預設 3tick_secs = 30 # 驅動器輪詢週期(秒)。預設 30stalled_secs = 600 # 派工後未被認領視為停滯、可重派的秒數。預設 600planner_enabled = false # 開啟後允許把目標拆成帶依賴的子任務 DAG(見「平行子任務」)。預設 falseresume_on_restart = "pause" # gateway 重啟時 in-flight 目標任務的處置,"auto" 或 "pause"(見「重啟行為」)。預設 pause,可在儀表板「設定 → 自動化」切換progress_report_minutes = 10 # 已認領任務多久沒進度訊號才通報一次,`0` 關閉(見「逾時進度通報」)。預設 10tool_streak_advisory = true # 同工具同參數連擊 3/5/8 次時是否注入提醒(見「工具連擊 advisory」)。預設 truecriteria_ledger = "report" # 驗收標準逐條帳本:off / report / enforce(見「驗收帳本」)。預設 reportsteering_enabled = false # 任務頁的「下一輪的指示」(見「任務頁的送指示與停止」)。預設 false
[dispatch_guard] # 回饋路徑斷路器(防再生型無限迴圈)window_secs = 60 # 滑動窗長度(秒)。預設 60max_in_window = 20 # 一個窗內允許的派工次數,超過即熔斷。預設 20cooldown_secs = 60 # 熔斷後拒絕派工的冷卻秒數。預設 60max_hop_depth = 5 # 委派鏈跨行程 re-spawn 的深度上限。預設 5所有區塊都可省略;缺省 / 部分設定一律退回上表的內建預設。未知的 policy 值一律退回 fixed_hierarchy 並記一筆警告。
平行子任務(依賴 DAG)
Section titled “平行子任務(依賴 DAG)”開啟 [goal_loop] planner_enabled = true 後,建立目標時會先讓 AI 員工「試著」把目標拆成一組帶依賴標注的子任務(例如:先各自查兩個資料源、再彙整)。拆出來的子任務會各自進 Task Board,depends_on 全部完成的子任務會並行開跑,各自獨立驗收。並行度仍受 max_concurrent 與 dispatch_guard 斷路器約束,不會繞過。
- 非強制:模型判斷不需要拆(或回覆無法解析)時,就退回單一任務,行為與關閉時完全一致。
- 循環依賴防護:拆出來的計畫若含循環依賴(或索引越界),整份計畫作廢、退回單一任務並記警告,絕不落地一個壞掉的 DAG。
- 上游卡住不孤兒化:某個子任務的上游依賴走到
failed/cancelled/needs_human(或依賴不存在),下游會繼承升級一起轉人工,讓你看到整條被卡住的分支;上游只是還在跑時,下游該輪凍結、下一輪再看。
預期效益以「多資料源查詢型」目標最大;獨立重測顯示加速約 1.25 倍(非論文自報的 3.7 倍),請以 eval 實測為準再推廣。
派工策略(DispatchPolicy)
Section titled “派工策略(DispatchPolicy)”[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 拉取與活動記錄一致。
團隊回合(Team-as-Agent)
Section titled “團隊回合(Team-as-Agent)”一位 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"都是一行還原。
你會看到什麼
Section titled “你會看到什麼”對話裡沒有任何新東西。員工用同一個聲音回答,進度出現在同一個看板,需要你的任務仍帶著六種暫停分類之一。內部則把一輪拆成三個階段,取代原本的一則喚醒訊息,只有執行者的最終成品會送到驗收判官,而判官就是同一套兩段式評估器加三面向判官團。團隊改變的是誰來做事,不改變誰來判定做完。
團隊任務有兩種新的原因會落到你手上:
| 暫停 | 發生了什麼 |
|---|---|
需要決策(blocked_needs_decision) |
規劃階段沒有交回任何子任務。迴圈不會從它的敘述文字去猜一份拆解,而是問你這個目標到底能不能拆,或是乾脆讓單一員工去跑。 |
預算用盡(budget_exhausted) |
任務在已經花掉一部分之後用完了角色成員的 spawn 額度。它會交出自己做出的最佳一輪,與其他預算升級相同。預算連一輪降級後的編組都付不起的任務會改走 Solo,因為讓一件根本沒開始的工作被停住,比用一般方式做完更糟。 |
每一輪開始前,一組零 LLM 的規則(crates/duduclaw-core/src/team_gate.rs)決定走 Solo 還是 Team。它刻意偏向 Solo。
- 強制 Solo:員工開了
[container] sandbox_enabled = true(排在所有模式之前檢查,always_team也蓋不過,所以開了沙箱的員工不會組成團隊)、即時通道回合、仍在等你核准的 plan-first 目標、計畫裡含不可逆動作、剩餘預算不足三輪。 - 計算四個訊號:批量(至少四個可獨立進行的工作項目,且沒有依賴樞紐)、脈絡溢出(估計的輸入超過模型的脈絡視窗)、能力差距(角色矩陣的差距達到或超過矩陣宣告的 MDE)、長程(至少三條驗收標準,且任務會產出檔案)。量不到的訊號永遠不會觸發。
- 判定:三個以上訊號成團;零或一個走 Solo;剛好兩個是灰帶。
灰帶時,迴圈會先跑一次規劃階段(這是任務本來就需要的一次呼叫),再套用同一套規則,這次的批量訊號改用規劃者實際寫出的子任務封包來量。計畫出現之前批量訊號沒有東西可量,所以灰帶一定是另外三個訊號中有兩個觸發;第二次判斷只有在計畫含四個以上、彼此之間沒有依賴樞紐的子任務時才成團,否則退回一般的單一員工回合。沒有規劃角色的團隊規格做不了第二次判斷,直接走 Solo。
每次裁決都會以 team_gate_decision 寫入稽核日誌,附上觸發的訊號,所以一個部署的 Solo/Team 比例可以量測,不必靠印象。
規格依任務凍結
Section titled “規格依任務凍結”任務所用的團隊在建立時決定一次,存在任務上。之後對 [team] 的修改只影響下一個任務。規格驗證失敗(最常見的是審核者與執行者同一個模型家族)時完全不會成團:任務走 Solo,不會出現局部團隊。
這個拒絕是否有聲音,取決於是誰要求組團隊。明寫了 enabled = true 的操作者,或設了角色但驗證失敗的操作者,會拿到以 team_refused 記錄的稽核列,附上角色與原因。完全沒動過 [team] 的部署只會得到一行 debug!,稽核日誌裡什麼都沒有:這個拒絕是 v1.66 翻轉預設值之後每個安裝的預設狀態,若在每個目標任務上都蓋一列,真正的拒絕反而會找不到。
角色的 model 沒設時,也可以先從量測得到的能力矩陣(<DUDUCLAW_HOME> 下的 role_model_matrix.toml,由 duduclaw eval --matrix 寫出)取值,取不到才退回員工的 [model] preferred。只採計該角色自己 runtime 上的 resolved 格,平手不選任何模型,明確設定的模型永遠優先。
預算與降級鏈
Section titled “預算與降級鏈”[dispatch.team_budget]max_spawns_per_task = 12 # 4 個角色 x 3 輪;下限鉗到 3max_turns_per_role = 3degrade_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 回合看到的內容與以前完全相同。
稽核事件與各角色紀錄
Section titled “稽核事件與各角色紀錄”| 事件 | 意義 |
|---|---|
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 |
封包自行宣告的證據等級與實際觀察不符,已被覆寫 |
封包放在哪裡
Section titled “封包放在哪裡”每個階段都透過呼叫 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 並記一筆警告,並發上限永遠不能被設定成完全關閉。
[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。 |
範例