
字节笔记本
2026年10月6日 · 约 5 分钟读完
DeepSeek Harness 的 Cordis 入门
DeepSeek Harness 采用「一切皆插件」的架构,而撑起这句话的底层部件,是一个以 vendor 方式引入仓库的插件框架 Cordis。官方文档为它专门写了一篇入门(即 cordis-primer),内容定位很明确:插件作者在阅读各子系统自动生成的服务与事件参考之前,需要先掌握的框架概念;同一套概念另有配套的手把手教程,框架的 vendored 源码与上游同步流程则单独放在 vendor 目录的说明文件里。本文把这篇入门的要点整理成一份中文速览。

五个核心概念
入门用五句话概括 Cordis:
- **插件是实现 Service 的对象。**它可以是一个带可选
inject字段和apply(ctx)方法的函数,也可以是一个Service子类,其生命周期由 Cordis 挂载进当前上下文。 - **上下文是服务的容器。**一个服务认领一个稳定的
ctx.<key>,比如ctx.tools、ctx.llm、ctx.sessions;其他插件按 key 查找服务,而不是导入某个具体实现。 - **依赖用
inject声明。**声明了所需服务的插件,会等到这些服务真实存在才启动,加载顺序由此通过服务依赖来表达,不需要手动编排启动序列。 - **类型化事件负责通信。**服务通过 TypeScript 的声明合并登记事件名,再以
emit、waterfall、parallel、serial四种方式分发,分别对应监听器观察、包装、并行扇出与按序执行。 - **注册是可逆的副作用。**提示词片段、工具 schema、适配器、提供方和监听器,都通过
ctx.effect()或ctx.on()安装,reload 与 teardown 时会按预期撤销。
四种分发模式
每个事件只属于一种分发模式,也只能用对应的方法派发:
| 模式 | 是否 await | 执行顺序 | 有返回值 |
|---|---|---|---|
emit | 否 | 监听器按注册顺序观察 | 无 |
waterfall | 否 | 监听器按注册顺序观察 | 有 |
parallel | 是 | 所有监听器并行观察 | 无 |
serial | 是 | 监听器按注册顺序执行 | 有 |
分发模式是事件公开约定的一部分。新增的 harness 事件用 @mode 标签登记模式,这样生成的目录就能把声明与分发调用点做交叉校验,写错模式在文档层面就会被揪出来。

waterfall:一圈环绕中间件
ctx.waterfall 本质是环绕中间件。监听器收到 (...args, next):调用 next() 就把处理委托给下游监听器,下游的返回值经由 next() 回到当前层,包装后继续向外返回;不调 next() 直接返回,就是短路。
协作式监听器的常见做法,是修改一个共享的请求或决策对象然后委托;也可以选择整个替换结果,下游监听器只会看到替换后的值。prepend: true 只在监听器必须抢在普通注册之前运行时才用。
对单决策事件而言,短路就是设计意图:拥有决策权的策略监听器可以不调 next() 直接返回,而只做标注或观察的监听器则必须委托,把决策权留给该管的人。
loader:配置里的表达式
@deepseek-ai/cordis-plugin-include 会把 !!js 解析成表达式节点。loader 在声明的注入激活之后、针对该插件上下文(ctx.serviceName)插值条目的 config 字段;在每次挂载决策时、针对 loader 上下文插值 disabled 字段。include 会保留嵌套的行表达式,直到目标行激活,其余条目元数据保持字面值。如果要让环境来决定启用哪些插件,应该使用 overlay。
两条实践规则
入门的结尾给了两条实打实的规则。其一,把行为封装进插件:工具流水线事件属于 ctx.tools,模型流式输出属于 ctx.llm,实时 agent 协调属于 ctx.agents;拦截与策略优先用事件表达,直接的能力调用优先走服务方法。其二,每个注册都要有对应的 disposer,要么从 ctx.effect() 返回一个,要么使用 Cordis 的辅助方法自动处理;如果 teardown 顺序有要求,就把相关工作收进同一个 effect,确保资源按预期顺序释放。
这五个概念加一套分发语义,就是给 DeepSeek Harness 写插件的最小前置知识。概念过完,再去看各子系统生成的服务与事件参考,或跟着官方教程动手跑一遍,理解会顺畅得多。



