
字节笔记本
2026年10月6日 · 约 5 分钟读完
Cordis 插件框架入门:五个核心概念与四种分发模式
Cordis 是驱动 DeepSeek Harness 的开源插件框架,上游在 cordiverse 组织下开源,设计思想出自论文《A Programming Paradigm for Spatiotemporal Composability》。Harness 以 vendor 方式把它内置进仓库,"一切皆插件"的整个架构都建立在它之上。本文整理自该仓库的 Cordis 入门文档,并改写成独立可读的版本:先讲五个核心概念,再看四种分发模式与瀑布式语义,最后是配置装载规则和实践纪律。

五个核心概念
插件是实现 Service 的对象。 它可以是一个带可选 inject 字段和 apply(ctx) 方法的函数,也可以是一个 Service 子类。无论哪种形态,生命周期都由 Cordis 挂载进当前上下文来管理。
上下文是服务的容器。 一个服务在上下文里占据一个稳定的 ctx.<key>,比如 ctx.tools、ctx.llm、ctx.sessions。其他插件通过 key 查找服务,而不是导入具体实现。这样解耦之后,同一个 key 背后换成别的提供方,消费方插件不必关心实现是谁。
依赖用 inject 声明。 插件把需要的服务写进声明,Cordis 会等这些服务就绪后才启动它。加载顺序由此表达成服务之间的依赖关系,而不是手工编排的启动序列。
通信走类型化事件。 服务通过 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() 直接返回,而只做标注或观察的监听器必须委托。仓库的 AGENTS.md 把这条纪律写成硬规约:瀑布式监听必须调用 next(),直接 return 会切断整条链。
Loader 配置
Include 插件把 !!js 表达式解析为表达式节点。Loader 在声明的注入激活之后,基于该插件上下文(ctx.serviceName)插值条目的 config;disabled 字段则在每次挂载决策时基于 loader 上下文求值。Include 会保留嵌套行表达式,直到目标行激活;其余条目元数据保持字面值。需要按环境选择插件时,正确做法是使用 overlay,而不是绕开这套机制。
注意仓库约定里 !!js 是唯一合法前缀,!js 不被接受,它只允许出现在插件的 config 和条目的 disabled 之下。
实践规则
把行为封装进对应的服务域:工具流水线事件属于 ctx.tools,模型流式输出属于 ctx.llm,实时 agent 协调属于 ctx.agents。拦截和策略优先用事件表达,直接的能力调用优先走服务方法。
每个注册都应有对应的 disposer:要么从 ctx.effect() 里返回一个,要么使用 Cordis 提供的辅助方法自动处理。如果释放顺序有要求,就把相关工作放进同一个 effect,确保 teardown 按预期顺序收尾。
这套设计的要点是秩序不靠自觉:依赖声明决定加载顺序,事件模式决定分发行为,effect 决定生命周期。插件越写越多之后,是框架在替你兜底。



