第二幕 · 核心机制 / 04 Cordis 五件事
0%
第二幕 · 核心机制 · 第 04 章

Cordis 五件事

乐高积木之间靠什么咬合?—— Cordis。五个核心概念,吃透它们就吃透了 dsh 的一半。

🔩开场小故事:积木的咬合标准

上一章说 dsh 是乐高积木。但积木之间靠什么咬合?不同厂商的积木为什么能互插?靠的是统一的凸点与凹槽标准——就像手机充电口,Type-C 一统江湖,你出门才不用带八根线。

Cordis 就是 dsh 的「咬合标准」——一个插件框架(dsh 以 vendor 方式引入它)。它的设计思想来自一篇论文《A Programming Paradigm for Spatiotemporal Composability》。名字很唬人(念出来能劝退 90% 的读者),但你只需要记住:插件往共享的「上下文」里贡献服务、事件和可逆副作用,积木就能彼此协作、也能干净地拆掉。

一句话讲清楚:Cordis 用 五个概念定义插件如何协作 —— 插件(功能单元)、上下文 ctx(服务的容器)、inject(依赖声明)、 类型化事件(通信方式)、可逆副作用(注册与撤销)。
Cordis 五个核心概念关系图
图 04-1 插件位于中心,通过上下文、注入、事件与副作用跟世界打交道。

五件事,逐一说清

① 插件 —— 功能单元

插件可以是带可选 injectapply(ctx) 的函数, 也可以是一个 Service 子类。它的生命周期(加载、就绪、卸载)由 Cordis 管理。 一个插件通常只做一件事:注册工具、注册适配器、监听事件、或提供一项服务。

② 上下文 ctx —— 服务的容器

每个服务占据一个稳定的 ctx.<key>ctx.llm(模型)、 ctx.tools(工具注册表)、ctx.sessions(会话日志)、 ctx.shell(bash 执行)、ctx.fs(文件系统)……

关键规则:其他插件通过 key 查找服务,而不是 import 具体实现。 这就像插座按「标签」找插头:只要标签对,插头是谁家的都行 —— 这正是「换实现不用改调用方」的基础。

③ inject —— 声明依赖

插件声明自己需要哪些服务(inject 列表),Cordis 会等这些服务就绪后才启动该插件。 于是加载顺序由依赖关系自动推导,不需要手动编排启动顺序。 你不需要关心「谁先加载」,只需要说「我要什么」。

④ 类型化事件 —— 通信方式

服务之间用事件通信。事件名通过 TypeScript 声明合并注册(插件可以给别人的事件表「加新事件」而不改源包), 并且每种事件有固定的分发模式

模式是否等待?顺序有返回值?像什么
emit按注册顺序观察广播喇叭:大家听,别插嘴
waterfall按注册顺序包装接力棒:传下去或拍板短路
parallel并行观察同时举手
serial按注册顺序执行排队叫号

waterfall 值得单独记住,因为 dsh 的所有「拦截点」几乎都是它: 监听器收到 (...args, next),调用 next() 把接力棒传下去并拿到下游结果; 不调用 next() 直接返回 = 短路,下游全部被跳过。 所以「想改行为的插件」在 waterfall 里等下游结果再包装,或直接拍板替换; 「只想看一眼的插件」必须记得 next(),否则会把整个链条掐断。

⑤ 可逆副作用 —— 注册与撤销

插件的所有「安装动作」(注册工具、挂监听器、装适配器)都是副作用, 通过 ctx.effect()ctx.on() 完成。每个注册返回一个 disposer(释放函数);插件卸载或重载时,Cordis 会按预期顺序撤销一切。

这解决了插件系统最头疼的问题:「拆积木」留下的残渣。 传统系统里卸载插件经常留下幽灵监听器、幽灵定时器;Cordis 里卸载 = 一切注册随之撤销,干干净净。

💡实践规则(官方原话的精神)

把行为封装为插件:工具流水线事件属于 ctx.tools,模型流式输出属于 ctx.llm, 实时 agent 协调属于 ctx.agents拦截和策略优先用事件;直接能力调用优先用服务方法。

费曼自查

1为什么「服务靠 key 查找而不是 import」很重要?
参考:因为这样服务的实现可以整体替换而不动调用方。调用方只依赖 `ctx.llm` 这个「插座标签」,至于插头是 DeepSeek 适配器还是别的适配器,它不关心 —— 这是能力缝(第 07 章)的根基。
2waterfall 事件的监听器什么时候可以「不调用 next()」?
参考:当它拥有决策权、要拍板短路时 —— 比如直接拒绝一次工具调用。只做观察或标注的监听器必须调用 next() 委托下去,否则会截断整条链。
3「可逆副作用」解决了什么问题?
参考:解决了卸载残留。每个注册都有 disposer,插件卸载时 Cordis 自动按序撤销所有注册(工具、监听器、适配器),不会留下幽灵监听器或定时器,支持安全的重载与热插拔。
4插件想「在每次模型请求前注入一段上下文」,该用哪种机制?
参考:用事件(扩展点)。dsh 的 agent/request、agent/pre-step 等 waterfall 就是为这类拦截设计的(第 05 章会看到它们登场)。这就是「拦截和策略优先使用事件」。