深入 DeepSeek Harness:Cordis 如何把 Agent 能力组合成可扩展的产品
很多 Agent 项目最初都很简单,一个循环就够了:

从图中看,如果只是做一个 Demo,这个 Reason-Act-Observe 循环已经足够。但现实业务中,让 Agent 真正走向产品,问题会迅速复杂起来:
- 不同用户可能使用不同模型
- Web、CLI 和 SDK 需要共享同一套 Agent 能力
- 有些 Agent 可以执行 Bash,有些只能读取文件
- 工具执行前需要经过权限判断和用户审批
- 本地执行环境可能需要切换成远程沙箱
- 会话需要持久化、恢复、回放和分叉
- 新功能需要接入现有流程,却不能不断修改 Agent Loop
这时候面对的就不再是"如何调用一次模型",而是如何组织一个不断增长、持续迭代的 AI Agent 产品。DeepSeek Harness(简称 DSH)对这个问题的答案,就是 Cordis。
一、Cordis 到底是什么
从技术上说,Cordis 是 DeepSeek Harness 底层使用的插件运行时。但如果只把它理解成"插件系统",还是低估了它的作用。Cordis 管理四类运行时机制:

在 DSH 中,模型适配器、工具注册表、会话日志、权限策略,甚至 Agent Loop 本身,都是挂载到 Cordis 上的插件。Cordis 自身是一个精简的运行时,不承载具体的 Agent 业务逻辑;模型、工具、会话和 Agent Loop 都通过插件接入,没有哪个业务模块拥有不可替换的特权地位。
Cordis 的核心价值,就是把 Agent 产品拆成一组可以组合、替换、隔离和回收的能力。
二、Cordis 如何把能力组合成一个 Agent
Cordis 并不是把几个插件直接拼成一个 Agent 对象,而是先组装出一套运行时能力,再由 Agent Loop 使用这些能力创建和驱动 Agent。
假设我们要启动一个能够读取代码、执行命令并回答问题的 Agent:
pnpm dsh --profile headless "分析这个项目的结构"
命令背后,系统需要准备模型调用、工具执行、提示词和会话管理等能力,再创建 Agent 来处理任务。DSH 定义要组合哪些能力,Cordis 负责加载插件并管理它们之间的依赖。
1. 从启动命令到插件配置
命令中的 headless 是一个 Profile,表示一次性命令行任务的运行方式。它里面声明使用哪些 Bundle,Bundle 则包含插件配置及对应代码。整体加载过程如下:

再往下看具体的配置合并过程。headless Profile 会先应用 dsh-base(模型、工具、会话、权限等基础能力),再应用 dsh-headless(任务输入与结果输出),两者合并成基础插件配置;之后依次经过 Profile Patch、Home Patch 和命令行 --patch,最终得到生效配置:

这种分层配置的好处是:基础能力和运行形态解耦,换一个 Profile(比如从 headless 换成 web),底层的模型、工具、会话插件可以原样复用。
2. 插件如何把工具交给 Agent 使用
假设我们希望 Agent 能执行 Bash 命令,工具接入涉及两个部分:
- Bash 工具插件:定义工具名称、参数,以及如何执行命令
- Tools 服务:统一管理已注册的工具,并提供执行入口
Bash 工具插件加载后,会把工具定义注册到 Tools 服务;Agent 运行时,通过这个服务查找并执行工具。三者关系如下:

把过程分成两个阶段理解:
- 启动时,先把工具注册进来。 Bash 工具插件告诉 Tools 服务:"我提供一个叫
bash的工具,这是它的参数定义和执行函数。"此时只是登记工具,并没有执行命令。 - 运行时,再根据模型的工具调用执行。 模型请求调用
bash后,Agent Loop 将调用交给 Tools 服务。Tools 服务找到对应实现,经过执行前检查等处理后,调用 Bash 工具的执行函数。
Tools 服务本身从哪里来?Cordis 加载 dsh-tools 插件时,会创建工具服务,并将它以 tools 这个名称提供给其他插件。因此 dsh-tools 是提供服务的插件,ctx.tools 是访问这个服务的方式。
3. 依赖就绪后,Agent Loop 才能工作
插件挂载后并不一定立即运行。Cordis 会先检查它通过 inject 声明的服务依赖,只有这些服务全部就绪,才会激活插件。例如,Agent Loop 声明了六项依赖:
static inject = [
'agents',
'sessions',
'llm',
'tools',
'systemPrompt',
'sessionProjections',
]
图中的箭头表示服务依赖。假如 ctx.tools 还没就绪,Agent Loop 就会保持等待;等六项服务全部可用后,才进入初始化。需要注意的是,插件的激活顺序由依赖关系决定,而不是由 YAML 的行顺序决定。

初始化时,Agent Loop 会向 ctx.agents 注册一个 Agent Factory,也就是负责创建 Agent 实例的实现。在 Headless 模式下,Headless Runner 负责接收命令行任务:它通过 ctx.agents 请求创建 Agent,ctx.agents 再将创建工作委托给这个 Factory;实例创建完成后,Runner 将用户任务提交给 Agent。

至此,配置中的插件已经形成可用的服务,Agent 实例也已创建并收到任务。剩下的,它会使用这些服务组装上下文、调用模型,并在模型请求工具时执行对应工具。Agent Loop 负责组织这个运行过程,模型适配、具体工具实现和会话持久化,则由相应插件承担。
三、扩展 Agent,为什么不必总改 Agent Loop
前面 Agent 已经能通过 Tools 服务调用 Bash 了。如果现在要加一个需求:某些工具操作必须获得用户批准才能执行,该怎么实现?
简单拆解,这个需求涉及三个职责:判断是否需要审批、获取审批结果、根据结果决定是否继续。如果直接在 Agent Loop 里加判断分支,每加一种策略就要改一次核心循环,很快会变得难以维护。Cordis 的做法是把扩展点提前留好。
1. 在工具执行前,留出策略接入的位置
Tools 服务提供了 tools/pre-execute 事件。工具执行前,服务会通过这个事件调用已注册的策略监听器,取得执行决定。策略插件可以返回 allow、deny 或 ask,Tools 服务负责处理决定:

这里需要区分两个层次:Cordis 提供事件分发机制,DSH 定义 tools/pre-execute 这个扩展点及其允许返回的决定,业务策略再通过插件接入这个位置。因此,已有扩展点能够表达的审批规则,可以在策略插件中实现,无需为此给 Agent Loop 增加新的判断分支。
2. Waterfall:继续委托,还是直接作出决定
tools/pre-execute 使用 waterfall(瀑布式事件):多个监听器依次处理同一次调用,每个监听器可以通过 next() 继续交给后续处理,也可以直接返回决定,结束这条处理链。

例如,一个监听器只记录调用信息,记录后应继续调用 next();一个负责审批判断的监听器,如果确定当前操作需要确认,就可以直接返回 ask。这种设计让多个策略可以叠加,而不需要互相知道对方的存在。
3. 请求审批之后,还不能直接执行
当执行前决定为 ask 时,Tools 服务调用 ctx.approval.request(),审批服务根据配置和可用的审批渠道处理请求,再返回结果:

批准后,表示这次调用通过了审批,但它仍然需要通过后续守卫和取消检查——避免将"用户同意"误当成绕过其他执行约束的许可。
四、一切皆插件,代价是什么
一切插件化的好处,是让 Agent 的能力可以独立组合和替换;但同时也会带来新的工程复杂度。当模型、工具、会话、权限、Agent Loop 都被拆成插件之后,原来集中在一个流程里的逻辑,会分散到不同插件、服务和事件之间。这时候需要额外处理几类问题:
- 插件之间的依赖关系怎么维护
- 谁先加载,谁后激活
- 一个事件经过了哪些插件
- 某个插件失败后,问题怎么定位
- 插件创建的连接、进程、沙箱由谁负责释放
- 插件之间的接口发生变化后,如何保证兼容
也就是说,插件化并没有让复杂度消失,它只是把原来写在 Agent Loop 里的代码复杂度,变成了插件之间的组合复杂度和协作复杂度。Cordis 的四类运行时机制(服务解析、依赖激活、事件编排、生命周期所有权),本质上就是在系统性地应对这些复杂度。
五、小结
Cordis 的设计思路可以概括为三点:
- 能力插件化:模型、工具、会话、权限、Agent Loop 全部是插件,没有不可替换的核心
- 依赖显式化:通过
inject声明服务依赖,激活顺序由依赖关系决定,而非配置顺序 - 扩展事件化:通过
tools/pre-execute这类 waterfall 事件预留扩展点,新策略以插件形式接入,不改 Agent Loop
对于正在构建 Agent 产品的团队,Cordis 的价值不在于"又一个插件框架",而在于它提供了一套把 Agent 从 Demo 推进到可扩展产品的组织方式:先把能力拆成可组合的插件,再用运行时管理它们的依赖、事件和生命周期。
DeepSeek Harness 项目地址:https://github.com/anywhere-labs/deepseek-harness