子代理派生
職責
delegate_tool 讓主 agent 把一個子任務派生給隔離的子代理:子代理有自己的對話、自己的終端、自己的工具集,跑完只把最終結果交回父代理。這實作了「零上下文成本」的流水線——父代理上下文 (context) 不被子任務的過程細節污染。子代理在 ThreadPoolExecutor worker 裡執行,危險命令預設 auto-deny(可設定 auto-approve)。
設計動機
長流水線會爆,不是因為模型不行,而是因為父代理看完了所有工具呼叫的中間過程。grep 出 200 行、patch 失敗的 diff、試錯性的 build——這些細節塞進父代理上下文後,後面決策就被稀釋。delegate_task 的賭注是「子代理的過程對父代理不可見」,只讓最終文字回填。代價是失去可觀測性,所以補了一套觀測層:DaemonThreadPoolExecutor 讓子代理執行緒不阻塞程序退出,_active_subagents 註冊表 (registry) 讓 TUI 能列出/暫停/打斷活著的子代理,max_concurrent_children 和 max_spawn_depth 給並發與深度硬上限。隔離邊界是用「能看見、能停」換來的。
關鍵檔案
def delegate_task:2426— 派生入口class DelegateEvent:625— 派生事件列舉auto-deny / auto-approve 回調:61-103—_subagent_auto_deny(安全預設)/_subagent_auto_approve(opt-in YOLO)_get_subagent_approval_callback:102-160— 按delegation.subagent_auto_approve設定選回調DaemonThreadPoolExecutor:1978-1990— 子代理用的常駐執行緒池with DaemonThreadPoolExecutor:2651-2660— 派生執行上下文tools/daemon_pool.py—DaemonThreadPoolExecutor實作多後端終端:5-15— 子代理的終端在 6 個沙箱 (sandbox) 後端之一執行(見沙箱)
資料流
- 主迴圈 provider 回傳
tool_call: delegate。 delegate_task:2426被呼叫,拿到子任務描述與可選 toolsets。- 在
DaemonThreadPoolExecutor:2651裡起一個 worker:- worker initializer 安裝審批回調(
tools/delegate_tool.py:102),預設 auto-deny 危險命令 - 子代理建構自己的對話上下文,呼叫
run_conversation:588跑迴圈 - 子代理的工具呼叫走
registry:765(可被父代理限制 toolsets)
- worker initializer 安裝審批回調(
- 子代理完成後只把最終文字交回,delegate_task 經
tool_result:798封裝回填給父代理。 - 父代理上下文只增長一條 tool 結果,不被過程污染。
為什麼要裝審批回調?註解裡寫得直白:CLI 的互動式審批回調存在 terminal_tool.py 的 threading.local() 裡,worker 執行緒不繼承,不裝回調會 fallback 到 input(),從 worker 執行緒讀 stdin 會和父代理的 prompt_toolkit TUI 死結。所以預設是 deny:
def _subagent_auto_deny(command: str, description: str, **kwargs) -> str:
"""Auto-deny dangerous commands in subagent threads (safe default)."""
logger.warning(
"Subagent auto-denied dangerous command: %s (%s). "
"Set delegation.subagent_auto_approve: true to allow.",
command, description,
)
return "deny"派生執行用 DaemonThreadPoolExecutor 而不是標準庫,理由在 daemon_pool.py 模組開頭:標準庫的 worker 是非守護執行緒,_python_exit 會無條件 join,一個卡死的 worker 就能讓程序退出卡幾分鐘。Daemon 版本 spaw daemon 執行緒並跳過 _threads_queues 註冊。派生入口 delegate_task 第一步是查暫停開關 is_spawn_paused(),這個開關由 TUI 的 p 鍵或 delegation.pause RPC 觸發,發現失控派生樹時凍結新派生但不動已經在跑的子代理:
if is_spawn_paused():
return tool_error(
"Delegation spawning is paused. Clear the pause via the TUI "
"(`p` in /agents) or the `delegation.pause` RPC before retrying."
)深度也硬限,預設 MAX_DEPTH = 1,父(0)到子(1)就到底,孫子會被拒,要開多級得顯式配置 delegation.max_spawn_depth。
邊界與失敗
- 預算洩漏:子代理有自己的
max_iterations,但 token 花費對父代理不可見,批量派生 N 個子代理時,N 倍 provider 費用直接堆上去。max_concurrent_children預設 3 是軟上限,設定高了會一次性燒穿預算 (budget)。 - 巢狀死結:預設深度是 1,顯式調高
max_spawn_depth後,orchestrator角色的子代理能再派生,任意中間層卡住都會拖住整條鏈。DaemonThreadPoolExecutor只解決程序退出卡死,不解決執行時掛起。 - 審批回呼叫錯人:
subagent_auto_approve=true是 process 級設定,開了之後所有子代理都 YOLO。沒有 per-job 粒度,批量場景容易誤開。 - 結果可信度:父代理只拿最終文字,中間過程不在上下文裡。子代理把幻覺當事實寫進最終回覆,父代理沒辦法基於過程糾偏,長鏈路派生時幻覺會逐層放大。
小結
子代理派生 = 上下文隔離 + 危險命令管控。父代理只看結果,不看過程,這讓長流水線不被過程雜訊撐爆。安全預設 auto-deny,批量/cron 場景可 opt-in auto-approve。終端執行落在沙箱後端。