Skip to content

声明式与命令式:Cordis 的五个核心概念 ​

把搜索服务从 Provider A 换成 Provider B,改配置再重启进程通常已经够用。DSH 仍然选择了一条更重的路:插件直接运行在 Harness 进程内,可以注册有状态服务、监听事件,甚至替换 Agent Loop。

问题不在于哪种插件“更先进”,而在于运行时是否需要替换进程内、带状态、被其他组件依赖的能力。一旦答案是需要,依赖、清理和失败回滚就不能再交给插件作者临场发挥,Cordis 正是用来托住这部分复杂度的。

两种插件世界观 ​

插件模型插件交付什么默认优点默认代价
声明式文件、Prompt、Skill、MCP 配置、Hook门槛低,进程边界清楚,重启即可替换只能在宿主预留的扩展点内工作
命令式进程内代码、服务、事件和状态能组合有状态能力,也能改变运行时行为必须处理依赖、清理、并发和更新失败

二者的能力上限未必不同。你也可以在独立 MCP Server 里实现自己的热更新,但生命周期责任由服务作者承担。差别主要在默认下限:Cordis 强制插件进入统一的依赖和清理协议,让复杂替换不必从零处理边缘情况。

对于搜索、Skill 和普通 Hook,声明式模型通常更划算。Cordis 的价值集中在会话存储、上下文管理器、后台任务和 Agent Loop 这类跨 Turn 持有状态的组件上。

五个概念不是五个零件 ​

官方 Cordis Primer 用五个概念描述插件运行时。重点不是背 API,而是看它们怎样形成一个闭环。

概念解决的问题形成的约束
Plugin谁向系统贡献能力插件只负责在自己的作用域内注册能力和行为
Context能力在哪里被发现消费方按稳定服务键取能力,不绑定具体实现
inject什么时候可以启动依赖未满足时等待,提供方变化时重新评估
Typed Event横切逻辑怎样协作通知、决策和环绕拦截使用不同分发语义
Effect / Fiber插件离开后怎样收场每个副作用有归属,并在卸载时逆序撤销

Plugin:贡献能力,而不是占有主流程 ​

Cordis 插件可以是函数、对象或类。形态不是关键,关键是插件通过 Context 提供服务、注册监听器或产生受托管副作用。它不需要知道完整系统长什么样,也不应该靠直接导入另一个插件的实例协作。

这使插件成为运行时组合单元,而不只是一个工具目录。模型适配器、工具注册表、会话日志和 Agent Loop 在组合层面没有特权差异。

Context:服务容器也是作用域边界 ​

ctx.llm、ctx.tools 这类稳定键表达“我要某种能力”,而不是“我要某个实现”。提供方可以替换,消费者不需要改 import。

Context 还可以派生局部作用域。extend 继承父级能力,isolate 则让某项服务在子树中拥有独立世界。工程上,这意味着某个 Agent 可以看到自己的工具、文件系统或策略,而不污染根上下文中的其他 Agent。

inject:把启动顺序变成依赖关系 ​

传统启动脚本通常靠人工排列模块。Cordis 让插件声明所需服务:依赖未就绪时,插件处于等待状态;服务出现后才激活;提供方离开时,依赖分支也会停下并等待新的实现。

这就是“空间可组合性”的工程含义:系统不是按一条固定加载顺序拼起来,而是根据当前作用域中有哪些服务,持续解析谁能够工作。

Typed Event:区分通知与决策 ​

服务适合直接调用,事件适合一对多通知和横切拦截。当前官方 Primer 重点区分四种分发方式:

模式语义典型场景
emit同步广播,不汇总结果会话事件、状态通知
parallel并发执行并等待全部完成相互独立的异步观察者
serial顺序执行,允许首个有效结果拍板多个候选处理器
waterfall监听器包裹下游,可委托也可短路模型请求、工具审批、重试决策

waterfall 是理解 DSH 策略面的关键。观察者必须调用下游,否则一个本想记日志的插件也会意外截断模型请求;只有真正拥有否决权的策略插件才应短路。它把“在哪里可以干预”从核心代码里的条件分支变成可组合的决策链。

Effect 与 Fiber:注册必须可逆 ​

事件监听器、定时器、服务注册和后台任务都会改变进程。Cordis 把这些变化归到插件实例对应的 Fiber 上,并要求非托管资源登记 disposer。卸载时,Fiber 逆序撤销副作用,尽量把运行时恢复到插件安装前的状态。

这就是“时间可组合性”的工程含义:安装插件不只是会做什么,还必须定义它离开后如何撤销。HMR 的本质因此不是替换文件,而是卸载旧 Fiber、清理副作用、激活新 Fiber;若新实现失败,则需要保留或恢复最后一个可用状态。

为什么 Agent Loop 值得这份复杂度 ​

如果 Loop 是核心中的硬编码控制流,插件最多只能在预设节点挂 Hook。若 Loop 本身是服务,就可以整体替换调度策略,例如从单 Agent 循环切换到多 Agent 协作,或重新定义工具并发、上下文装配和错误恢复。

这解释了 DSH 的复杂度为何一路延伸到 Fiber、disposer、依赖通知和事务性 HMR:它们不是互不相关的功能,而是在为“控制流本身也能被替换”提供基础设施。

这套设计的失败方式 ​

  • inject 写错或依赖永远不出现,插件会停在等待状态;诊断工具必须能解释它在等什么。
  • 插件绕过 Effect 创建资源,卸载后仍会留下监听器、连接或任务,热更新就不再可靠。
  • waterfall 观察者忘记委托,会把整条执行链静默截断。
  • 进程内插件共享故障域,恶意或低质量插件比独立进程插件拥有更大的破坏面。
  • 生命周期正确不等于业务状态正确;数据库事务、外部副作用和迁移仍需插件自己设计。

因此,Cordis 提供的是较高的工程下限,不是自动正确。只有当运行中替换有状态能力确实重要时,这份复杂度才值得承担。

事实基线见官方 Cordis Primer(来源地址:https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.1.0-rc.8/docs/cordis-primer.zh.md)、Architecture(来源地址:https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.1.0-rc.8/docs/architecture.zh.md) 与 Cordis 论文(来源地址:https://github.com/cordiverse/paper)。想动手验证的读者可参考 NanoCordis(来源地址:https://github.com/SheltonLiu-N/nano-cordis)——一个约 1600 行的教育复现,覆盖了 Plugin、Context、inject、Event 和 Effect 的核心机制,但有意省略了 streaming、per-session scopes、subagents、sandboxing 和审计事件;这些省略恰好标记了"Demo 能跑"与"生产可用"之间的差距。

小结 ​

  • Context 让能力按服务键和作用域发现,inject 让加载顺序由依赖推导。
  • Typed Event 把通知、协作和策略否决分开,waterfall 是主要拦截机制。
  • Effect 与 Fiber 让副作用有归属、可撤销,构成 HMR 的前提。
  • Cordis 真正服务的是运行中替换有状态组件,尤其是 Agent Loop,而不是让普通插件显得更复杂。

下一篇建议继续看:

把零散知识,整理成可以反复走的路。