Subagent 是一個 seam:從進程內到委派 Claude Code
子 Agent 是能力接縫,進程內、遠程、別家產品都能接。核心源碼:packages/subagent/。
同一個子任務「調研這個模組並彙報」,切換四個 provider 分別委派一次。觀察兩件事:接縫兩端哪些東西完全不變,哪些東西隨實現而變。再打開「附帶 persona 要求」開關,看能力門閂怎麼把超綱請求攔在啓動之前。
| 啓動時能力 | spawn |
|---|---|
| outputSchema(結構化輸出) | 支持 |
| depthLimit(委派深度上限) | 支持 |
| toolFilter(限工具) | 支持 |
| persona(換人設) | 支持 |
subagent-spawn-in-process/src/index.ts 第 42 行、subagent-fork-in-process/src/index.ts 第 62 行),Claude Code 與 Codex 均為 NO_START_CAPABILITIES(各自 src/index.ts 第 54、49 行)。報錯文案逐字復刻 packages/subagent/subagent/src/index.ts 第 490 至 493 行。先説結論。DSH 沒有做一個單獨的子 Agent 功能,它做的是一個叫 ctx.subagents 的註冊表:任何實現了 SubagentProvider 約定的傳輸層,都可以按名字註冊進來。官方發行版註冊了六個:spawn(進程內新開)、fork(進程內帶上下文)、acp(協議橋)、codex、claude-code(各起一個真實的產品 CLI 進程)、sdk(遠程 DSH 實例)。出處在 docs/subsystems/subagent.zh.md 第 5 至 7 行。
輸入是什麼:工具層把模型的委派請求組裝成 SubagentStartRequest,帶上 prompt、父 Agent、取消信號,外加四個可選項(結構化輸出 schema、深度上限、工具過濾、persona)。發生什麼:服務先查所選 provider 的靜態能力表,四個可選項每一項都要有對應的能力 flag,缺一項就在啓動之前拋 UNSUPPORTED_CAPABILITY。輸出是什麼:一個 SubagentRun 句柄,父 Agent 等它的 result,最後落成一條普通的工具結果。對父 Agent 來説,四種 provider 回來的都是同一種東西。
值得停一下的是 fork 的身份。很多框架把帶不帶父上下文做成一個布爾參數。DSH 把 fork 做成了一個獨立 provider,原因是它倆差的不只是一個開關:fork 要從父日誌裏切出「已完成輪次的平衡前綴」當種子,切到最後一個 turn/end 為止,進行中的輪次不平衡、回放不了,必須排除。這是會話日誌層面的約定,塞進一個 flag 裏説不清楚。
fork 與 spawn 是兩個 provider差別不在參數,在種子:fork 把父日誌切到最後一個 turn/end 做子會話種子,spawn 從零開始。inheritsParentContext 只是描述性字段,供工具層生成不騙人的措辭。
一次性 run 與可繼續 ActivationSubagentRun 是一錘子買賣:等結果、dispose、結束。可繼續子 Agent 沒有 run,它是一份持久會話加至多一個駐留 Activation,父級用 send_message 追加輪次、interrupt_agent 打斷、收 report。
後台結束不會靜默可繼續子 Agent 結算時,管理器無條件給父級投一條 subagent-settled 通知,帶最終輸出。它與子 Agent 主動的 report 用不同的消息來源 kind,transcript 不會把運行時的記帳算成子 Agent 説的話。
第一段證據是能力門閂本體,邏輯不貼程式碼也説得清。assertCapabilities 把請求裏的四個可選項排成一張需求清單:請求帶了 outputSchema,就要求 provider 能力表裏 outputSchema 為真;帶了 maxDepth 就要求 depthLimit;toolFilter 和 persona 同理。然後逐項對照,第一個對不上的當場拋 SubagentError,錯誤文案直説哪個 provider 不支持哪個能力,錯誤碼 UNSUPPORTED_CAPABILITY。沒有降級、沒有警告後繼續,子進程在這之前一個都不會啓動。
出處:packages/subagent/subagent/src/index.ts 第 481 至 495 行,核對日期 2026-08-13。演示區的報錯文案逐字復刻自第 490 至 493 行。
第二段證據是 fork 的種子函式,七行説完帶上下文到底帶的是什麼:
function completedTurnPrefix(parent: Agent): SessionEvent[] {
const events = parent.session.events
const lastEnd = events.findLast(e => e.type === 'turn/end')
if (lastEnd === undefined) return []
// seq === array index (the append contract), so slice up to and including it.
return events.slice(0, lastEnd.seq + 1)
}
packages/subagent/subagent/src/index.ts 與 packages/subagent/subagent-fork-in-process/src/index.ts,核對日期 2026-08-13。程式碼塊保留源碼原文。還有兩處機制不貼程式碼,用文字標出處。Claude Code provider 的整個實現只是:解析 claude 可執行文件,在父會話的 cwd 裏通過官方 Agent SDK 起一個真實 CLI 進程,把它掛到共享的 subprocess owner 之下(subagent-claude-code/src/index.ts 第 62 至 91 行)。發行版的四個官方 preset 裏,codex 與 claude-code 的委派工具行都帶着 disabled: true 出廠,註釋寫明:複製 preset、刪掉 disabled,就能只對複製版的會話開放這個產品後端(apps/cli/config/agent-presets/standard/agent.cordis.yml 第 200 至 219 行)。委派別家產品在 DSH 裏是一個配置開關的事。
Claude Code 把委派做在產品層。Task 工具(AgentTool)一個入口,參數裏塞進各種形態:subagent_type 挑角色、run_in_background 後台、isolation: "worktree" 開隔離副本、model 換模型(書稿 study/chapters/05-multi-agent.md 第 32 至 52 行引 AgentTool.tsx 原文)。往上還有 Coordinator Mode 和 Agent Teams 兩套模式。表達力很強,但每種形態都是這一個產品裏的功能分支:委派對象永遠是另一個 Claude Code 實例,把任務交給另一個產品這件事沒有位置。DSH 的選擇相反,產品差異下沉到 provider 一層,接縫之上只有一個詞彙表。
Grok Build 走的是第三條路:把子 Agent 的配置解析抽成純邏輯庫 xai-grok-subagent-resolution,按 explicit override > role > persona > parent 的優先級解析出生效配置(該 crate src/lib.rs 第 7 至 8 行),執行仍在自家 shell 進程內。它抽象的是子 Agent 長什麼樣,DSH 抽象的是子 Agent 跑在哪。Grok 的角色、上下文繼承與樹形譜系,站內已有三課展開:子 Agent 的四種角色、上下文繼承與深度控制、子 Agent 的會話譜系。
推演一次超綱委派
部署啓用了 subagent_claude_code 工具,模型發起委派時帶上了 outputSchema(要求結構化輸出)。請按本課能力門閂的程式碼推演:錯誤在哪一行拋出、錯誤碼係咩、Claude Code 嘅 CLI 進程有冇被啓動過?再回答:如果 DSH 選擇接受請求但忽略 schema,父 Agent 攞到嘅工具結果會出咩問題,點解呢樣比起當場報錯更難排查?