DeepSeek Harness 系列(08):多 Agent 协作——Subagent 与 Agent Teams

文章来源声明: 原文作者:冬奇Lab; 来源站点:掘金; 原文链接:https://juejin.cn/post/7685974290621956159; 本文基于上述来源整理/加工,觅优补充点评,仅供技术学习交流。版权归原作者所有。
觅优短评

多 Agent 协作的关键不在数量,而在权限边界与数据接口:toolFilter 控工具、outputSchema 控输出,让委派真正可控。适合需要动态分解大型任务的工程团队。

本文目录
一个工具解决不了的问题 -----------

假设有个任务:对一个大型代码库做全面重构——检查所有文件的命名规范、找出所有循环依赖、整理接口定义。文件数量超过一千个。

你可以把这个任务交给一个 Agent,让它一个文件一个文件地处理。但问题很快出现:

  • 上下文窗口有限:处理到第 300 个文件时,前面的内容已经被推出窗口了
  • 工具没有 reasoning 能力:工具只能执行固定逻辑,无法根据"这个文件属于哪个子系统"来调整策略
  • 工具没有独立的对话历史:工具执行完就结束了,没有状态保留,下一次调用完全不记得上次看到了什么

多 Agent 的本质,就是把子任务委派给另一个有独立对话历史的 Agent。子 Agent 有自己的 Session,有自己的 reasoning 过程,可以用工具,有自己的上下文窗口。父 Agent 只需要告诉子 Agent"去处理这一批文件,告诉我结果"。

这和单纯调用工具的区别,就像是把一件事"外包给另一个人",而不是"启动一台机器"。


dsh 的 Subagent 机制

dsh 通过 Subagent 实现 Agent 委派。有两种启动方式:

  1. 模型调用:模型决定委派某个子任务时,调用内置工具 subagent_spawn
  2. 代码调用:在工具的 execute 函数里,通过 ctx.subagents.start() 直接启动

启动子 Agent 时,需要提供一个 SubagentStartRequest,关键字段如下:

<span>// 精简自 packages/subagent/subagent/src/types.ts 的 SubagentStartRequest</span>
<span>// 实际字段更多,这里只列最核心的部分</span>

<span>interface</span> <span>SubagentStartRequest</span> {
  <span>// 子 Agent 的初始提示词(ContentBlock 数组,支持文本、图片等)</span>
  <span>prompt</span>: <span>ContentBlock</span>[]

  <span>// 父 Agent 对象(提供工作目录、血缘关系信息)</span>
  <span>parent</span>: <span>Agent</span>

  <span>// 取消信号(允许父 Agent 中止子 Agent 的执行)</span>
  <span>signal</span>: <span>AbortSignal</span>

  <span>// 可选:限制子 Agent 可用的工具(只给它用得到的工具)</span>
  toolFilter?: <span>ToolRestriction</span>

  <span>// 可选:给子 Agent 一个专属的 Persona(覆盖全局部署 persona)</span>
  persona?: <span>string</span>

  <span>// 可选:结构化输出 schema(让子 Agent 返回 JSON 对象而非纯文本)</span>
  outputSchema?: <span>ObjectJsonSchema</span>

  <span>// 可选:最大委派深度(防止子 Agent 再启动子 Agent 形成无限递归)</span>
  maxDepth?: <span>number</span>
}

重点看几个字段:

toolFilter:子 Agent 默认继承父 Agent 的所有工具,但大多数情况下你只想给它一个受限的工具集。比如做代码分析的子 Agent,只需要读文件,不需要写文件。

persona:子 Agent 可以有自己的系统提示词前缀。你可以给它一个专门的角色定义,让它在那个角色里工作,而不是继承全局 Persona。

outputSchema:这个字段让子 Agent 的输出不再是纯文本,而是结构化的 JSON 对象。父 Agent 拿到的 result.structured 是类型安全的。


一次性 vs 可继续

Subagent 有两种生命周期模式,区别在于父 Agent 是否需要多次和子 Agent"对话"。

一次性 Subagent(One-shot)

启动 → 子 Agent 完成所有工作 → 返回结果 → 结束。整个过程就一个来回,适合独立的子任务。

可继续 Subagent(Continuable)

子 Agent 启动后,会创建一个持久化的 Session。父 Agent 可以继续向它发消息,子 Agent 每次收到消息都会继续执行。适合需要多次往返的协作。

两种模式的对比:

一次性 Subagent:
  Parent ──<span>start</span>()──► Child 执行完整任务 ──result──► Parent
  (子 Agent 执行完毕后生命周期结束)

可继续 Subagent:
  Parent ──<span>startContinuable</span>()──► Child Session 创建,进入等待
  Parent ──<span>sendMessage</span>('第一批文件')──► Child 处理第一批 ──result──► Parent
  Parent ──<span>sendMessage</span>('第二批文件')──► Child 处理第二批 ──result──► Parent
  Parent ──<span>sendMessage</span>('汇总结果')───► Child 汇总 ──final result──► Parent
  (子 Agent Session 在整个过程中保持活跃,有完整的对话历史)

可继续 Subagent 的优势在于:子 Agent 的对话历史是连续的,它能记住前几轮处理了什么,在汇总阶段可以利用这些记忆。


通讯路径:谁能给谁发消息

多 Agent 场景下,一个自然的问题是:Agent 之间可以任意通讯吗?

答案是不能。dsh 对通讯路径有明确的约束:

通讯方向是否允许说明
父 → 子✅ 允许要求子 Agent 的 `parentSession` 指向父 Agent
子 → 父✅ 允许子 Agent 可以向父 Agent 发消息
兄弟之间❌ 不允许同父的两个子 Agent 不能互发消息
跨代(祖父 → 孙子)❌ 不允许隔代通讯被拒绝
       Parent
      /      \
  Child <span>A</span>   Child <span>B</span>
     |
   Grandchild

✅ Parent ──► Child <span>A</span>
✅ Child <span>A</span> ──► Parent
❌ Child <span>A</span> ──► Child <span>B</span>(兄弟不能通讯)
❌ Parent ──► Grandchild(跨代不行)

这个约束是有意为之的。允许任意通讯会让 Agent 网络变成一个难以追踪的消息图,调试时完全不知道消息从哪里来的。层级清晰,每个 Agent 的责任边界才清晰。


toolFilter 与 persona

工具过滤

给子 Agent 只分配它需要的工具,是多 Agent 系统里最重要的权限控制手段:

<span>// 子 Agent 只能使用以下三个工具</span>
<span>// 父 Agent 拥有的其他工具(比如 write_file、execute_shell)对它不可见</span>
<span>toolFilter</span>: {
  <span>allow</span>: [<span>'read_file'</span>, <span>'search_files'</span>, <span>'list_files'</span>],
  <span>// 也可以用 deny 来排除特定工具</span>
  <span>// deny: ['write_file', 'execute_shell']</span>
}

为什么要这样做?

原因不只是安全,更是任务专注度。一个分析代码的子 Agent,如果工具列表里有 write_file,模型可能会"顺手"修改它觉得有问题的代码,这不是你期望的行为。给它只读工具,它就只能读。

Persona

<span>// 子 Agent 使用专属的系统提示词前缀</span>
<span>// 这会覆盖全局部署 persona,让子 Agent 在一个专门的角色里工作</span>
<span>persona</span>: <span>'You are a specialized test writer. Focus only on writing unit tests for the given code. Do not modify existing source files, do not suggest refactoring.'</span>

Persona 的意义在于:父 Agent 可以是一个通用助手,而它启动的子 Agent 可以是一个极度专注于单一任务的专家。每次创建子 Agent 都重新定义角色,不受全局配置的干扰。


结构化输出(outputSchema)

子 Agent 默认返回文本。但在自动化流水线里,父 Agent 需要的往往是可以直接处理的数据,而不是一段自然语言描述。

outputSchema 解决这个问题:

<span>// 让子 Agent 返回结构化 JSON——父 Agent 不需要解析文本(伪代码)</span>
<span>const</span> result = <span>await</span> ctx.<span>subagents</span>.<span>start</span>({
  <span>prompt</span>: [{ <span>type</span>: <span>'text'</span>, <span>text</span>: <span>'分析这段代码,找出所有 bug。'</span> }],
  <span>parent</span>: currentAgent,
  signal,
  <span>outputSchema</span>: {
    <span>type</span>: <span>'object'</span>,
    <span>properties</span>: {
      <span>bugs</span>: {
        <span>type</span>: <span>'array'</span>,
        <span>items</span>: {
          <span>type</span>: <span>'object'</span>,
          <span>properties</span>: {
            <span>file</span>:        { <span>type</span>: <span>'string'</span> },
            <span>line</span>:        { <span>type</span>: <span>'number'</span> },
            <span>severity</span>:    { <span>type</span>: <span>'string'</span>, <span>enum</span>: [<span>'critical'</span>, <span>'major'</span>, <span>'minor'</span>] },
            <span>description</span>: { <span>type</span>: <span>'string'</span> },
          },
        },
      },
    },
  },
})

<span>// result.text 是子 Agent 的文本输出(如果有)</span>
<span>// result.structured 是类型安全的 JSON 对象——直接用,不需要解析</span>
<span>console</span>.<span>log</span>(result.<span>structured</span>.<span>bugs</span>)
<span>// → [{ file: 'auth.ts', line: 42, severity: 'critical', description: '...' }, ...]</span>

这让多 Agent 系统的数据流变得干净:子 Agent 的分析结果直接成为父 Agent 下一步决策的输入,不需要再加一层文本解析。


六种 Subagent Provider

dsh 支持多种底层实现,满足不同的部署场景:

Provider说明适用场景
`spawn-in-process`在同一进程内创建新 Agent 实例本地开发、测试
`fork-in-process`Fork 当前 Session,子 Agent 继承前缀历史需要继承上下文的子任务
`dsh-sdk`通过 dsh SDK 启动独立运行时需要完全隔离的子 Agent
`acp`通过 ACP 协议与远程 Agent 通讯远程 Agent 集群
`codex`调用 Codex Agent代码专项任务
`claude-code`调用 Claude Code代码专项任务

大多数本地开发场景用 spawn-in-process 就够了。需要隔离(比如不同子 Agent 有不同的文件系统权限)时,考虑 dsh-sdk


实验性功能:Agent Teams

Subagent 是层级模型——父子关系明确,通讯路径严格。但有些协作场景天然是"平等的":多个专家 Agent 围绕同一个任务并行工作,谁的结果先出来谁先汇报。

Agent Teams 是 dsh 正在开发的多 Agent 协作框架,通过 ctx.agentTeams(实验性服务)访问:

  • roster(花名册):注册团队成员,每个成员有名字和角色
  • mailbox(邮箱):成员之间通过邮箱收发消息
  • task board(任务板):团队共享的任务列表,成员可以认领和提交任务
  • 协调者模式:可以有一个协调者 Agent 分发任务、汇总结果

和 Subagent 的层级模型相比,Teams 更接近"团队协作":没有严格的父子层级,成员之间可以平等通讯,任务可以动态分配。

Subagent 模型(层级):        Agent Teams 模型(平等):

     Orchestrator                   ┌──────────────────┐
    /     |      \                  │    task board     │
  子<span>A</span>    子<span>B</span>    子C                 └──────────────────┘
(严格父子关系,                  Agent <span>A</span> ◄──► Agent <span>B</span>
 单向委派)                         │              │
                                   └────► Agent C ◄┘
                                   (平等通讯,动态协作)

注意:Agent Teams 目前是实验性功能,API 可能在后续版本中变化。生产环境中使用前请确认版本稳定性。


实战:主 Agent 调用子 Agent

下面是一个完整的模式示例:主 Agent 定义一个工具,该工具内部启动子 Agent 完成专项分析任务。

<span>// 伪代码:主 Agent 的工具定义,委派子 Agent 做代码分析</span>
<span>const</span> analyzeCodebaseTool = <span>defineTool</span>({
  <span>name</span>: <span>'analyze_codebase'</span>,
  <span>description</span>: <span>'启动一个专门的子 Agent,对指定代码库目录做全面分析。'</span>,
  <span>parameters</span>: {
    <span>directory</span>:   { <span>type</span>: <span>'string'</span>, <span>required</span>: <span>true</span>, <span>description</span>: <span>'要分析的目录路径'</span> },
    <span>focus</span>:       { <span>type</span>: <span>'string'</span>, <span>required</span>: <span>true</span>, <span>description</span>: <span>'分析重点(例如:命名规范、依赖关系)'</span> },
  },

  <span>async</span> <span>execute</span>(<span>args, exec</span>) {
    <span>// 启动子 Agent(伪代码,实际由 ctx.subagents.start() 实现)</span>
    <span>const</span> result = <span>await</span> ctx.<span>subagents</span>.<span>start</span>({
      <span>prompt</span>: [{
        <span>type</span>: <span>'text'</span>,
        <span>text</span>: <span>`分析目录 <span>${args.directory}</span>,重点关注:<span>${args.focus}</span>。按文件逐一检查,汇总问题清单。`</span>,
      }],
      <span>parent</span>: exec.<span>agent</span>,   <span>// 传入父 Agent,建立血缘关系</span>
      <span>signal</span>: exec.<span>signal</span>,  <span>// 传入取消信号,父 Agent 取消时子 Agent 也停止</span>

      <span>// 子 Agent 只能读文件,不能修改代码</span>
      <span>toolFilter</span>: {
        <span>allow</span>: [<span>'read_file'</span>, <span>'list_files'</span>, <span>'search_files'</span>],
      },

      <span>// 专属角色:代码审查专家</span>
      <span>persona</span>: <span>'You are a code review expert. Read code carefully, identify issues, and provide a structured summary. Do not modify any files.'</span>,

      <span>// 要求返回结构化 JSON</span>
      <span>outputSchema</span>: {
        <span>type</span>: <span>'object'</span>,
        <span>properties</span>: {
          <span>issues</span>: {
            <span>type</span>: <span>'array'</span>,
            <span>items</span>: {
              <span>type</span>: <span>'object'</span>,
              <span>properties</span>: {
                <span>file</span>:        { <span>type</span>: <span>'string'</span> },
                <span>issue</span>:       { <span>type</span>: <span>'string'</span> },
                <span>severity</span>:    { <span>type</span>: <span>'string'</span>, <span>enum</span>: [<span>'high'</span>, <span>'medium'</span>, <span>'low'</span>] },
              },
            },
          },
          <span>summary</span>: { <span>type</span>: <span>'string'</span> },
        },
      },
    })

    <span>// result.structured 是类型安全的 JSON,直接使用</span>
    <span>const</span> { issues, summary } = result.<span>structured</span>
    <span>return</span> <span>`分析完成。共发现 <span>${issues.length}</span> 个问题。\n<span>${summary}</span>`</span>
  },
})

这个模式的核心价值:父 Agent 不需要知道子 Agent 怎么分析代码,它只需要拿到结构化结果,继续做后续决策。


和其他框架的对比

框架多 Agent 模型与 dsh 的主要区别
**Claude Code Agent tool**也是 spawn subagent接口更简单,但没有 `toolFilter` / `persona` / `outputSchema`
**LangGraph**图状工作流,固定拓扑dsh Subagent 是动态的——由模型在运行时决定何时委派,而不是预先定义的图
**AutoGen**对话式多 Agent,Agent 直接互发消息dsh 是层级模型,有明确父子关系;兄弟 Agent 不能直接通讯

简单说:LangGraph 适合流程固定的场景,dsh Subagent 适合流程由模型动态决定的场景。


系列下一篇

下一篇讲 可观测性:Agent 在运行时做了什么,怎么知道?dsh 提供了哪些机制来追踪工具调用、Token 消耗、子 Agent 血缘树,以及如何在生产环境里接入日志系统。


PrimeSkills 可以找到已在真实企业场景验证过的 AI Agent 技能和工作流,不是演示级的,是用在实际项目里的。

更多内容见我的个人主页