DeepSeek Harness (DSH) 插件机制解析:一切皆插件的 Agent 运行时
基于 deepseek-ai/deepseek-harness 仓库源码分析
一句话结论
dsh(DeepSeek Harness)是一个 Agent harness,核心设计是**「一切皆插件」**------它没有把功能写死在框架里,而是构建在基于 source-vendored 的 Cordis 4.0(vendor/cordis/,重命名 @deepseek-ai/cordis)之上,所有能力都以插件形式挂载。理解 dsh 的插件机制,就理解了整个 harness 的运行逻辑。
一、总体架构

六层结构:界面层(web/cli)→ 启动层(app-boot)→ 组合层(bundle patch)→ 核心层(约 50 个功能包)→ 能力层(工具、记忆、会话等)→ 框架层(vendor 的 Cordis/loader/include)。
二、插件机制的 4 个核心概念
1. Context 是 Proxy,不是普通对象
vendor/cordis/src/reflect.ts 的 get 陷阱 :读 ctx.xxx 时沿 fiber 链向上查找,找不到就报 cannot get property "X" without inject。
含义:插件不能随便读全局状态------每个属性都必须能追溯到注入来源。这保证了依赖关系的显式化。
2. Fiber 生命周期状态机
PENDING → LOADING → ACTIVE / FAILED → UNLOADING → DISPOSED。epoch 依赖串 (fiber.ts _refresh())驱动自动 reload/unload------依赖版本变化时,相关 fiber 自动刷新。
3. ctx.effect() 是唯一注册原语
- 所有副作用(监听事件、注册工具、创建资源)都必须通过
ctx.effect()注册 - disposers 逆序执行:卸载 = 精确回滚
- 好处:无论插件以什么顺序加载/卸载,清理都不会泄漏
4. 事件 5 种分发方式
| 方式 | 语义 | 典型场景 |
|---|---|---|
| emit | 广播,不等待 | 通知类事件 |
| parallel | 并行执行 | 无依赖的监听者 |
| serial | 串行执行 | 有顺序要求的监听者 |
| bail | 短路返回 | 审批/校验,可拒绝 |
| waterfall | 逐个传递返回值 | 策略注入点 |
waterfall 是插件机制的关键:返回值可以修改后传给下一个监听者。dsh 的策略注入点全部用 waterfall:
tools/pre-execute(工具执行前拦截)agent/request(组装请求前修改)llm/stream(流式输出逐段加工)internal/config(配置合并)
三、Profile 与 bundle 贴纸叠层
Profile = $DSH_HOME/profiles/<name>/,内容是一份 bundle 有序列表 + cordis.patch.yml。
bundle 补丁顺序(后贴赢):
- bundles(按配置顺序)
- profile patch
- home patch
--patchoverlays(命令行)
就像贴纸叠层:后贴的覆盖先贴的。调试命令:dsh --profile web --dump-config 可离线打印最终插件树。
四、Loader 与 Include:插件的加载与配置
Loader(vendor/loader/)
- 核心模型:EntryTree / Entry
name动态import模块 →ctx.registry.plugin()开 fiberinternal/plugin监听绑定 fiber↔entry- 插件自我 dispose 时写回
disabled: true(下次启动不加载)
Include(vendor/include/)
cordis.yml是 YAML 方言,!!js表达式延迟求值(配置值可以引用运行期对象)applyEntryPatches:id 整体替换 / insert 追加,不做深合并- 理解:patch 是「替换或追加」,不是「递归合并」------避免歧义
五、类型声明合并机制
- 103 个文件
declare module '@deepseek-ai/cordis'扩充 Context/Events 接口 - 156 个文件用
import type {} from '@deepseek-ai/dsh-xxx'空类型导入拉声明(编译后擦除) - augmentation 跟着声明它的包走,不在 cordis 包里;外部插件作者必须显式拉取
引用服务 3 件事:
- 类型层:拉声明(
import type {}) - 运行层:装 provider(
ctx.provide) - 依赖层:inject
六、一次对话的回合流程
会话日志是唯一事实来源------所有事件在发生时立刻追加(不是最后统一写)。
持久事件清单 :turn/start、user/message、assistant/chunk、assistant/message、tool/call、tool/result、step/end、turn/end。
七、动手理解:一个最小插件长什么样
核心 API 只有三样:
js
// 1. defineTool:声明一个工具
const tool = defineTool({
name: 'demo-hello',
execute: async (args) => { /* 干活 */ }
})
// 2. ctx.tools.register:挂载到 Context
ctx.tools.register(tool)
// 3. ctx.on('session/event'):订阅会话事件
ctx.on('session/event', (event) => { /* 观察 */ })
写插件 = 注册工具 + 订阅事件 + 用 effect 管理副作用。这就是「一切皆插件」的全部要义。
八、小词典(关键术语)
| 术语 | 含义 |
|---|---|
| Harness | Agent 的「运行容器」,管生命周期/依赖/事件 |
| Cordis | 插件化运行时框架(dsh 的 vendored 底座) |
| Fiber | 单个插件的运行实例,有完整生命周期 |
| Context (ctx) | 插件的运行环境,Proxy 实现,属性需注入 |
| effect | 副作用注册原语,卸载时逆序回滚 |
| epoch | 依赖版本标识,变化时驱动 fiber 自动刷新 |
| bundle | 一组插件的打包单元,可被 patch |
| patch | 对 bundle/配置的覆写(id 替换 / insert 追加) |
| Profile | 一份运行配置:bundle 列表 + patch |
| waterfall | 事件分发方式,返回值逐级传递(策略注入点) |
| Entry/EntryTree | Loader 的加载单元模型 |
| cordis.yml | 配置方言,!!js 表达式延迟求值 |
| 声明合并 | declare module 扩充 Cordis 接口的机制 |
| provider | 通过 ctx.provide 提供的服务实例 |
| inject | 依赖注入声明 |
| dump-config | 离线打印最终插件树的调试命令 |
推荐学习资源
- 从零开始打造一个AI Agent CLI --- 手把手构建完整 AI Agent 命令行工具。
- AI Agents 开发实践 --- Agent 架构设计、记忆系统、多 Agent 协作。
- AI 全栈编程生存指南 --- AI 时代全栈开发者生存法则。
参考
- 仓库:deepseek-ai/deepseek-harness
- 源码依据:
vendor/cordis/src/reflect.ts、vendor/cordis/src/fiber.ts、vendor/loader/、vendor/include/、docs/architecture.md