DeepSeek Harness 子Agent架构设计

传统 Agent 框架通常把子 Agent 注册成普通 Tool,由模型根据 description 自行判断是否调用。DeepSeek Harness(dsh)则把子 Agent 提升为独立的运行时能力,用 Service Definition + Service Provider + Consumer 构成一条 Capability Seam。模型看到的只是 Consumer 工具,底层 Provider 可以按配置替换,调用入口和 prompt 都不需要改变。

这套设计要解决的并不是“能不能调用子 Agent”,而是子 Agent 的边界约束、能力声明、委派深度和会话续接如何从隐式约定变成硬性契约。

父 Agent / 模型调用侧

Consumer
tool-subagent / tool-bash

Service Definition
ctx.subagents / ctx.shell

Provider
spawn-in-process

Provider
fork-in-process

Provider
acp

Provider
codex / claude-code / dsh-sdk

一、痛点:子 Agent 的边界一直是隐式规则

在一个常见的代码重构场景里,Claude Code 或 Codex 会在执行过程中自主拉起子 Agent。子 Agent 再往下委派,一层一层继续嵌套,等开发者意识到问题时,上下文可能已经穿过了三层委派链路。

此时真正让人不安的不是“模型具备自主能力”,而是几个边界问题没有被确定下来:

  • 这个子 Agent 是谁发起的;
  • 当前一共嵌套了多少层;
  • 它是否还能继续向下委派;
  • 它的会话是否可以接续。

传统实现里,这些规则都写在 prompt 或 description 中。可以写“禁止嵌套”,但这只是软约束,模型是否遵守没有确定性保障。

dsh 的做法是把委派规则显式化成协议。它的子 Agent 子系统由三层机制共同形成硬约束:

  • 完整的类型定义;
  • 能力协商流程;
  • 委派深度持久化机制。

边界管控因此从“模型自行解读”,转变为“运行阶段由 Provider 声明并完成契约校验”。

二、骨架:一切皆插件,Agent Loop 也没有特权

2.1 dsh 内核与插件树

dsh 的底层内核来自 Cordis,Cordis 只负责插件的加载、卸载和依赖关系管理,不内置任何 Agent 业务能力。

在这个内核容器之上,一切皆插件:

  • 模型适配器是插件;
  • 工具注册中心是插件;
  • 会话日志组件是插件;
  • Agent 主循环本身也只是一个普通插件。

因此核心模块都可以被替换。要切换底层模型,就挂载对应的 adapter 插件;要替换主循环执行逻辑,就新增或替换插件,不需要 fork 源码去修改核心实现。

配置层由 Profile + Bundle 两层组成。一个 Profile 由有序的 Bundle 逐层叠加,最终形成完整的插件树:

  • dsh-base 是基础层,承载模型适配器、工具集、持久化组件、沙箱环境和审批策略;
  • dsh-web-app 在基础层之上追加浏览器交互组件;
  • dsh-headless 追加一次性执行的 runner 组件。

配置补丁按行 id 定位,并对整行配置内容完成替换。

2.2 能力缝可插拔架构,和传统可插拔 Tool 架构的区别

很多 Agent 框架都支持可插拔工具,但它和 dsh 的能力缝模型并不在同一个抽象层级。

传统可插拔 Tool 架构的插拔发生在工具层。工具包对外输出 ToolSpec,包含名称、描述、入参 Schema 和 handler 执行函数。框架维护全局工具注册表,模型通过 tool_call 触发 handler。

它的边界止步于工具实现。例如本地 tool-bash 的 handler 内部直接调用 child_process.spawn。如果要改成沙箱执行,或对接远程 E2B 服务,只能再写一个全新的 bash-sandbox 工具,并让模型在 prompt 的工具列表里感知并选择两个工具。

能力缝架构的插拔发生在底层能力契约层。模型看到的仍然是一个 tool-bash 工具,但它只是 Consumer,真正执行依赖 ctx.shell。ctx.shell 下面可以挂载 bash-local、bash-sandbox、pwsh-local 等多个 Provider。切换 Provider 只改配置,工具代码和 prompt 都不用动。

传统可插拔 Tool

模型看到多个工具条目

每个工具直接绑定 handler 实现

切换执行载体需要新增工具

prompt 与工具列表同步变更

Capability Seam

模型只看到一个 Consumer 工具

Consumer 依赖抽象 Definition

Definition 下挂多个 Provider

配置切换 Provider,上层不变

两者差异可以用一张表对照:

对比维度传统可插拔 Tool 架构Capability Seam 能力缝架构
插拔位置工具 ToolSpec 层面底层能力契约 ctx 层面
模型感知不同实现对应不同工具,模型需要选择模型只看到同一个调用入口,Provider 透明
依赖关系handler 直接绑定底层实现Consumer 只依赖抽象 Definition
替换方式增删工具,prompt、工具列表同步变更切换 Provider,工具、prompt 不变
级联传递工具之间互相独立支持级联替换,下游自动切换执行环境
多实现共存多个工具条目并行注册同一契约下多个 Provider 按名称注册
适用范围只面向模型动作调用覆盖 LLM、Shell、文件系统、子 Agent、持久化、审批等运行时能力

一句话概括:传统可插拔工具,插拔的是展示给模型的动作;能力缝架构,插拔的是支撑动作运行的底层执行能力。

三、子 Agent 为什么应该属于 Seam

3.1 传统方案:Agent 工具只是 Loop 的一个组成部分

传统框架把子 Agent 调用逻辑硬编码成一个 Agent 工具,和 bash、edit 注册在同一张工具表里。模型每一轮推理都能看到这个工具,自主判断是否调用,并选择 subagent_type。

框架收到 tool call 后,根据类型名称拉起子 Agent,分配全新上下文,等子 Agent 执行结束,把结果作为 tool result 回传给父 Agent,进入下一轮推理。

// 传统做法:Agent 工具注册在工具表,与 bash/edit 平级
tools.add({
  name: 'Agent',
  desc: '委托子任务,可选 subagent_type',
  call: (args) => spawn(args.subagent_type, args.prompt),
});
// 模型发起 tool call,结果作为 tool result 回灌下一轮

这个方案的最大问题是:边界管控全部依赖模型自觉。什么时候拆分任务、嵌套深度上限是多少、子 Agent 能输出什么内容、会话是否支持接续,都靠 description 文本解读。解读出现偏差时,框架没有拦截机制。

嵌套层级失控、上下文信息串扰,根源就在这里。整套流程走标准 Agent Loop 的工具执行链路,从架构归属上属于 Loop 的一部分,而不是独立运行时能力。

3.2 dsh 方案:子 Agent 是一条独立 Seam

dsh 把子 Agent 提升为一等运行时能力,和 bash 保持同一层级:

  • dsh-shell 定义契约;
  • dsh-bash-local、dsh-bash-sandbox 提供实现;
  • dsh-tool-bash 负责把能力暴露给模型。

子 Agent 同样采用这条 Seam 形态。类型定义不放在核心内核里,而是挂在 ctx.subagents 服务对象上,后端挂载多个 Provider,再通过 tool-subagent 这个 Consumer 把委派能力开放给模型。

模型工具调用

Consumer
tool-subagent

Service Definition
ctx.subagents

Provider
spawn-in-process

Provider
fork-in-process

Provider
acp

Provider
codex

Provider
claude-code

Provider
dsh-sdk

dsh 的 bash 和 subagent 都属于 Capability Seam,但有一个关键区别:bash 同一时间只允许一个 executor 生效,而 subagent 支持多个 Provider 按名称共存,全部挂载在 ctx.subagents 映射表下。

这意味着 spawn 子 Agent 并不存在唯一正确实现。可以在同进程内新建 Agent,可以 fork 一份继承父任务已完成 turn 前缀的实例,可以走远程 ACP 协议,甚至可以把 Claude Code、Codex 这类外部产品整体作为后端执行载体。框架的职责只是定义统一委派协议,不替使用者选定实现方案。

dsh 内置 6 种 Provider:

Provider执行机制是否继承父上下文
spawn-in-process同进程新建 Agent不能继承
fork-in-process同进程 fork,继承父已完成 turn 前缀继承 seed 信息
acpAgent Client Protocol 远程 Agent不能继承
codex拉起 codex app-server --stdio不复制父对话
claude-code调用 @anthropic-ai/claude-agent-sdk,persistSession: false不复制父对话
dsh-sdkDSH 自研 JSON-RPC SDK按 descriptor 配置决定

四、四项布尔能力标识:把校验前置到启动阶段

每份 Provider 在启动前必须声明自己支持的能力集合。这些定义来自 packages/subagent/src/types.ts:

interface SubagentCapabilities {
  readonly outputSchema: boolean
  readonly depthLimit: boolean
  readonly toolFilter: boolean
  readonly persona: boolean
}

四个字段分别表示:

  • outputSchema:是否支持结构化输出;
  • depthLimit:是否遵守委派深度上限;
  • toolFilter:是否支持裁剪子 Agent 可用工具集;
  • persona:是否支持分配独立人格。

关键约束不在声明本身,而在校验时机。父 Agent 发起委派时,如果要求子 Agent 具备某项能力,而 Provider 声明的能力集合不包含该项,dsh 不会先接收任务再静默妥协降级,而是在启动阶段直接抛出 SubagentError('UNSUPPORTED_CAPABILITY')。

官方把这条原则命名为 fail loud, no silent degradation:失败要明确抛出,不允许静默降级。

能力满足

能力缺失

父 Agent 发起委派请求
要求 outputSchema / depthLimit 等能力

Provider 启动前声明能力集合

启动子 Agent

SubagentError
UNSUPPORTED_CAPABILITY

fail loud
不静默降级

Claude Code 的 Agent 工具依靠 prompt 中的 description 让模型理解子 Agent 能力范围。模型读完描述后如果判定可以承接任务,就会发起委派;但实际子 Agent 不支持结构化输出或工具集不符合预期时,它不会提前拒绝,而是产出不符合预期的自然语言内容。问题排查往往要回溯很久才能定位到能力不匹配这个根因。

dsh 把判断逻辑从模型语义猜测,转变成 Provider 类型声明和运行阶段契约校验。四项布尔标识就是契约,无法满足契约就不能接入委派链路。这才是协议化管控的本质。

五、三家框架在子 Agent 方向上的设计哲学对比

Claude Code、Codex、DeepSeek Harness 在子 Agent 模块上走出了三条不同路线:

维度Claude CodeCodexDeepSeek Harness
子 Agent 模型Agent 工具 + subagent_type,单进程,模型自主决定 spawnapp-server stdio 沙箱进程,审批策略驱动命名式 Provider 注册表,多 Provider 共存,前置能力协商
边界决定权模型,依靠 prompt 与 description产品策略 + 审批流程Provider 能力声明 + 启动阶段 fail-loud 校验
隔离机制同进程独立上下文进程隔离 + sandbox 沙箱可选:同进程 spawn/fork、远程 ACP、外部产品进程
委派深度管控软约束,依靠 prompt 引导产品侧自主管控delegationDepth 持久化 + maxDepth 硬拒绝
可续子 Agent不支持不支持,任务属于临时实例支持 continuable + Activation + followup/interrupt/report 冷恢复
跨产品委派不能委派给 Codex不能委派给 Claude Code内置 codex、claude-code Provider,支持互相委派

模型依据 prompt 与 description 决定

产品策略与审批流程决定

Provider 能力声明与启动校验决定

Claude Code

软约束

Codex

策略约束

DeepSeek Harness

硬契约

这里有三处差异尤其值得关注。

第一,边界由谁决定。Claude Code 把决策权交给模型,灵活性高,但边界是隐式语义规则,容易漂移。Codex 把决策权收归产品层和审批策略,可控性更强,但扩展弹性偏弱。dsh 选择第三条路径:边界由 Provider 预先声明的能力集合决定,在启动阶段由类型系统完成校验,校验通过才允许接入,失败直接拒绝。约束强度从模型解读、产品管控到能力契约校验,逐级硬化。

第二,可续子 Agent 是 dsh 的独有特性。Claude Code 和 Codex 的子 Agent 都是临时实例,单次调用结束后生命周期终结。真实的长周期任务中,子 Agent 经常需要接收追加追问、接续执行。dsh 的 continuable、Activation、cold resume 机制,是当前开源 Agent 框架里相对完整的子会话生命周期方案。它确实带来更高复杂度,但解决的是工程落地中的真实痛点。

第三,跨产品委派更具颠覆性。dsh 内置 codex、claude-code 两个 Provider,把直接竞品封装成可调用的执行后端。使用方可以在同一套调度体系下,按需委派任务到不同产品的 Agent 执行引擎。

六、总结

很多框架把 Agent 扩展能力停留在工具层,新增能力等价于新增一条工具定义。小规模场景下这种模式足够好用,但随着委派链路变深、执行载体变多,维护成本会快速上升。

Capability Seam 的核心价值,是把调用入口和底层实现解耦。模型只需要面向稳定契约发起调用,底层执行载体可以按配置切换,不需要改动 prompt,也不需要修改上层调用代码。

落到子 Agent 场景,它解决的核心痛点有三类:

  • 多层委派带来的边界失控;
  • Provider 能力不可核验;
  • 子会话无法接续。

从架构演进角度看,把 Agent 运行时能力协议化、可插拔化,对构建大规模 Agent 调度体系有直接的参考价值。如果要做多引擎 Agent 编排或跨平台子任务委派,契约前置校验、多 Provider 共存,以及可续子会话设计,都是值得优先沉淀的工程能力。

Logo

更多推荐