Cordis:五个核心概念,读懂一棵插件树
用 Plugin、Context、inject、Events 与 Effect 五个概念,建立 Cordis 运行时的最小心智模型。
短结论:Cordis 的运行时可以压缩成五个概念:插件贡献能力,Context 按名称连接能力,inject 等待依赖,事件传递协作,Effect 负责回卷副作用。
如果先把 API 名称放在一边,Cordis 其实只在回答一个问题:一项能力怎样进入运行中的应用,并在依赖变化或重新加载时安全地离开?
01 / 五个概念是一条运行链
这五个概念不是五个孤立的功能,而是一条从声明到撤销的链:
| 概念 | 它回答的问题 | 可观察结果 |
|---|---|---|
| Plugin | 这项能力贡献什么? | 一个函数、对象或 Service 类被挂载 |
| Context | 插件从哪里找到能力? | 服务以稳定名称出现在 ctx 上 |
| inject | 什么时候可以开始? | 依赖未满足时保持 PENDING |
| Events | 不认识消费者时如何协作? | 发送者按约定分发,监听者独立加入 |
| Effect | 卸载时如何清理? | disposer 撤销注册、监听器和外部资源 |
02 / Plugin 只描述自己的贡献
最小插件不负责启动整个应用,它只导出自己的贡献:
import type { Context } from '@deepseek-ai/cordis'
export const name = 'hello'
export function apply(ctx: Context) {
console.log('hello from my first plugin')
}Loader 负责找到模块并调用 apply(ctx)。因此插件文件可以专注于行为,组合文件可以专注于选择哪些行为进入这次运行。
Cordis 接受三种插件形态:函数、带 apply 方法的对象,以及公开服务时常用的 Service 子类。它们最终都成为一个受 Fiber 管理的插件实例。
03 / Context 是按名称连接的服务容器
服务提供方把能力注册到一个名称上,消费者使用 ctx.tools、ctx.llm 或 ctx.agents 这样的入口。消费者不需要导入某个提供方的具体类,因此组合层可以替换实现。
这里的 Context 不是一个鼓励随处写入的全局对象。Cordis 的 Context 是代理和作用域容器:普通属性读取经过服务解析,extend()、isolate() 可以创建不修改父级的子上下文。
04 / inject 把启动顺序改成依赖关系
export const inject = ['greeter']
export function apply(ctx: Context) {
console.log(ctx.greeter.greet('world'))
}声明 inject 后,插件只有在 greeter 服务存在时才会进入 apply。所以交换 cordis.yml 中两行的顺序不会改变结果;真正决定就绪时机的是服务依赖。
如果功能可以在缺少服务时继续工作,不要伪造硬依赖,而是在使用处通过 ctx.get('greeter') 探测可选服务。
05 / Events 让发送者不知道谁在听
事件适合“发生了一件事,但不需要知道所有消费者”的场景。事件名和 payload 通过 TypeScript 声明合并加入 Events,再选择 emit、parallel、serial、bail 或 waterfall 分发。
例如统计服务可以发出 stats/report,日志插件只监听这个事件;两者不需要互相导入。事件模式是公开约定的一部分,决定是否等待、是否收集返回值,以及监听器能否短路。
06 / Effect 让注册可逆
在 Cordis 中,注册不是散落在应用各处的永久修改。ctx.on()、服务注册、子插件挂载以及显式的 ctx.effect() 都把清理动作附着到当前 Fiber。
ctx.effect(() => {
const timer = setInterval(() => console.log('tick'), 200)
return () => clearInterval(timer)
})插件卸载时,返回的 disposer 执行,定时器不再继续运行。HMR 的“旧代码离场、新代码进场”正是建立在这条可逆关系上。
07 / 没有特权内核
这五个概念共同带来一个架构结果:模型适配器、工具注册表、会话、Agent 管理,甚至 agent loop 都可以是插件。添加行为的通常路径不是修改一个特权内核,而是定义服务、提供实现、注册消费者,或接入一个已有事件。
在 DeepSeek Harness 中,这意味着 profile 可以选择组合,Context 负责连接,Fiber 负责实例所有权,Effect 负责撤销。“一切皆插件”指的是替换点和资源所有权都可见,而不是目录里有很多文件。
下一步
更多文章
动手:从 hello.ts 写出你的第一个 Cordis 插件
用一个函数插件和一份 cordis.yml,跑通 Loader、Context 与 Fiber 的最短路径。
生命周期与 effect:让 Cordis 注册可以回卷
跟随 Fiber 从 PENDING 到 DISPOSED,理解 effect、disposer 与 HMR 如何共同管理插件副作用。
事件:不知道谁在听,也能让插件协作
从类型化事件和五种分发模式出发,理解 Cordis 如何让服务广播事实、并发工作或交出决策。
邮件列表
加入我们的社区
订阅邮件列表,及时获取最新消息和更新