DeepSeek Harness · 編排與子 Agent

Subagent 是一個 seam:從行程內到委派 Claude Code

子 Agent 是能力接縫,行程內、遠端、別家產品都能接。核心原始碼:packages/subagent/。

課程目標讀完你能說清三件事:為什麼 DSH 把子 Agent 定義成一個 provider 註冊表(seam,能力接縫),讓行程內新開、帶上下文 fork、委派給 Claude Code 或 Codex 共用同一個介面;能力不匹配時為什麼選擇啟動前報錯(fail loud)而沒有靜默忽略;以及一次性 run 和可繼續 Activation 這兩種子 Agent 生命週期各自解決什麼問題。
互動演示 · 委派切換臺

同一個子任務「調研這個模組並彙報」,切換四個 provider 分別委派一次。觀察兩件事:接縫兩端哪些東西完全不變,哪些東西隨實現而變。再開啟「附帶 persona 要求」開關,看能力門閂怎麼把超綱請求攔在啟動之前。

父 Agent 的會話日誌(DSH 行程內)
user「幫我把支付模組接進來」
assistant好的,我先看看現有結構。
turn/end第 1 輪結束(種子邊界)
user「順便查下這個模組的歷史問題」
tool/callsubagent「調研這個模組並彙報」
ctx.subagents · provider 註冊表(seam)
spawn fork acp codex claude-code sdk
能力門閂與子 Agent 舞臺
啟動時能力spawn
outputSchema(結構化輸出)支援
depthLimit(委派深度上限)支援
toolFilter(限工具)支援
persona(換人設)支援
SubagentError: subagent provider "claude-code" does not support the "persona" capability · code: UNSUPPORTED_CAPABILITY
子 Agent行程內
(上下文為空)
子 Agent 工作中
選一個 provider 點「播放」,或滾動到此處自動播放 spawn。
演示為教學化模擬。能力表資料來自各 provider 原始碼:spawn 與 fork 四項全支援(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 行。
邏輯拆解 · seam 定義在哪一層

先說結論。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 裡說不清楚。

tool-subagent 模型說「去調研」 StartRequest ctx.subagents 註冊表 按名字選 provider · 校驗能力表 缺能力 → UNSUPPORTED_CAPABILITY spawn / fork 行程內子 Agent fork 帶父日誌平衡前綴 四項能力全支援 acp / sdk 協議橋與遠端實例 localAgent: undefined claude-code / codex 起真實產品 CLI 行程 用父會話的 cwd 啟動時能力全為否 回到父 Agent SubagentRun.result → 一條工具結果 非 completed 對映為 isError 接縫之上一切相同;接縫之下,傳輸方式隨 provider 各不相同
教學化結構圖:節點與連線用於解釋原始碼關係,內容經過課程化整理。
fork 與 spawn 是兩個 provider

差別不在參數,在種子:fork 把父日誌切到最後一個 turn/end 做子會話種子,spawn 從零開始。inheritsParentContext 只是描述性欄位,供工具層生成不騙人的措辭。

一次性 run 與可繼續 Activation

SubagentRun 是一錘子買賣:等結果、dispose、結束。可繼續子 Agent 沒有 run,它是一份持久會話加至多一個駐留 Activation,父級用 send_message 追加輪次、interrupt_agent 打斷、收 report。

後台結束不會靜默

可繼續子 Agent 結算時,管理器無條件給父級投一條 subagent-settled 通知,帶最終輸出。它與子 Agent 主動的 report 用不同的訊息來源 kind,transcript 不會把執行時的記帳算成子 Agent 說的話。

關鍵證據 · 能力門閂與 fork 種子

第一段證據是能力門閂本體,邏輯不貼程式碼也說得清。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 的種子函式,七行說完帶上下文到底帶的是什麼:

packages/subagent/subagent-fork-in-process/src/index.ts第 48 至 54 行節選
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)
}
原始碼快照說明:依據本地倉庫 deepseek-harness-master,核對檔案 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 裡是一個配置開關的事。

橫向對比 · 產品內的委派形態 vs 傳輸層註冊表

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 的會話譜系。

課堂練習
01

推演一次超綱委派

部署啟用了 subagent_claude_code 工具,模型發起委派時帶上了 outputSchema(要求結構化輸出)。請按本課能力門閂的程式碼推演:錯誤在哪一行丟擲、錯誤碼是什麼、Claude Code 的 CLI 行程有沒有被啟動過?再回答:如果 DSH 選擇接受請求但忽略 schema,父 Agent 拿到的工具結果會出什麼問題,為什麼這比當場報錯更難排查?

Takeaway:子 Agent 在 DSH 裡是一個 provider 註冊表,行程內 fork 和委派 Claude Code 走同一個介面,差異全部沉在接縫之下。能力不匹配在啟動前 fail loud,絕不接受後靜默降級。想給自己的 Agent 系統加委派給別家的能力,先問接縫定義在哪一層,再問能力表怎麼校驗。