DeepSeek Harness 02:Cordis 核心概念——时空可组合的微内核体系

DeepSeek Harness 02:Cordis 核心概念——时空可组合的微内核体系
Asaakii在很多框架中,想要给系统添加功能,最简单的做法是写一个独立进程的 MCP(Model Context Protocol)服务,或者改改配置文件然后重启整个服务。
但如果你的系统需要在进程内部替换一个持有大量内存状态的组件(例如正在持续运行的会话存储、动态更新的上下文管理器、甚至是控制整个任务步进的 Agent Loop 调度器),“改配置并重启进程”就会中断长程运行中的任务。
DSH 选择了基于 Cordis 微内核架构 来托住这套高难度的运行时。理解 Cordis 的核心模型,是读懂 DSH 所有高级特性的第一把钥匙。
一、两种插件世界观:声明式 vs 命令式
在深入具体 API 之前,我们先梳理业内两种截然不同的插件世界观:
| 维度 | 声明式插件(Declarative) | 命令式插件(Imperative / Cordis) |
|---|---|---|
| 交付形式 | 静态配置文件、Prompt 模板、Skill 文件、MCP Server 配置 | 进程内原生代码、服务实例、事件拦截器与响应式状态 |
| 隔离级别 | 进程级或纯文本级物理隔离,边界清晰 | 同进程内存空间内协作,性能极高但共享故障域 |
| 典型优势 | 上手门槛极低,插件崩溃不影响主进程,更新依赖重启 | 能够直接重构主流程行为,能跨 Turn 持有高效内存状态 |
| 默认代价 | 只能在宿主预留的狭窄 Hook 点填空,无法改变控制骨架 | 必须严格处理依赖拓扑、副作用销毁、并发竞争与回滚 |
对于绝大多数简单的只读搜索工具或外部 API 封装,声明式架构确实更便宜也更安全。而 Cordis 的价值,集中在会话存储、分层沙箱、长轮次上下文管线以及 Agent Loop 这类高频且跨 Turn 持有状态的核心资产上。
二、Cordis 的五个核心概念
在官方 Cordis 体系与论文《A Programming Paradigm for Spatiotemporal Composability》中,系统被抽象为五个紧密咬合的核心概念:
flowchart TD
subgraph Cordis_Engine ["Cordis 微内核引擎"]
Context["Context (作用域容器与服务定位)"]
inject["inject (空间拓扑:声明依赖与动态解析)"]
Fiber["Fiber / Effect (时间维度:副作用归属与逆序销毁)"]
Event["Typed Event (横切机制:emit / serial / parallel / waterfall)"]
end
Plugin["Plugin (功能单元)"] -->|注册能力| Context
Plugin -->|声明需求| inject
Plugin -->|登记副作用| Fiber
Plugin -->|拦截/监听| Event
1. Plugin(插件):能力的贡献者,而非流程的主宰者
在 Cordis 中,插件可以是普通的函数、对象或类。插件的核心特征是:它只负责在自己的生命周期内向 Context 声明服务或注册事件,它绝不假定全局主循环长什么样,更不通过直接 import 其他具体插件来实现调用。
2. Context(上下文):服务容器与作用域隔离边界
每个插件在激活时,都会收到一个专属的 Context 实例。
- 服务解耦:通过
ctx.llm、ctx.tools等稳定服务键索取能力,只依赖接口契约,不绑定具体实现。 - 作用域派生:
ctx.extend():创建一个继承父级全部能力的子作用域。ctx.isolate('fs'):在当前子树中将文件系统服务完全隔离,形成沙盒边界。
1 | // 主智能体作用域 |
3. inject(依赖注入):空间可组合性(Spatial Composability)
传统软件常靠人工硬编码模块加载顺序。而在 Cordis 中,插件直接声明它依赖的服务:
1 | class MyToolApprovalPlugin { |
如果依赖的服务尚未就绪,插件自动处于等待挂起状态;一旦依赖的服务被提供,插件立即激活;当提供方插件被卸载时,依赖它的下游插件分支自动挂起并等待新的实现。这就是所谓的“空间可组合性”。
4. Typed Event(强类型事件):通知与决策的分离
针对横切关注点的协作,Cordis 严格区分了四种不同的事件分发语义:
| 分发模式 | 核心语义 | 适用场景 |
|---|---|---|
emit |
同步多播广播,不收集任何返回值 | 会话步进通知、心跳上报、单纯的日志记录 |
parallel |
并发触发所有监听器,并等待 Promise.all 决议 |
相互独立的异步外部观察者(如异步推送遥测数据) |
serial |
按顺序依次执行,允许第一个有效返回值立即拍板 | 多个候选协议解析器抢占处理 |
waterfall |
责任链式包裹拦截,前一个监听器控制是否执行下游 | 模型调用代理、工具执行鉴权、错误重试策略 |
[!IMPORTANT]
waterfall 的拦截铁律:在 waterfall 拦截链中,普通的观察型监听器必须显式委托调用下游函数next(),否则会导致整条流水线被意外掐断;只有真正拥有否决权的策略拦截器(如安全规则检查未通过),才应当主动中断并返回决策。
5. Effect 与 Fiber:时间可组合性(Temporal Composability)
插件在运行时注册监听器、启动定时器、挂载文件观察者,都会污染运行环境。
Cordis 为每个插件实例分配一个受管的 Fiber(执行纤程)。插件所产生的一切状态变更与事件绑定,都会被登记为 Fiber 上的 Effect(副作用)。当插件被卸载或热更新时,Fiber 会按注册的相反顺序(逆序)逐一执行 Disposer 销毁函数,把系统状态精准还原到插件挂载前。
1 | ctx.effect(() => { |
正是因为每个副作用都有主、可逆,Cordis 才能实现可靠的热模块替换(HMR):卸载旧 Fiber $\rightarrow$ 逆序清理副作用 $\rightarrow$ 激活新 Fiber;若新插件初始化抛出异常,则立即回滚至最后一个健康状态。
三、为什么 Agent Loop 值得这份复杂度?
如果 Agent Loop 是一段硬编码在系统核心内部的 while (!done) 循环,那么二次开发者最多只能在预设好的几个阶段挂挂钩子。
而当 Agent Loop 本身也被降维成一个普通的 Cordis 服务(ctx.agentLoop)时,整个调度范式就可以随需替换:
- 单步串行调度:标准的逐步思考、调工具模式;
- 并发分支搜索:将调度算法替换为类似 MCTS(蒙特卡洛树搜索)或多候选展开模式;
- 多智能体博弈循环:由仲裁者插件整体接管步骤推进。
这种高度的自由度,正是 Cordis 架构存在的最大合理性。
四、这套设计的真实失效模式与防御
没有银弹。进程内微内核虽然强大,但也引入了特有的风险面:
- 依赖隐式悬挂(Deadlock / Hang):如果插件声明的
inject键名拼写错误,或者服务提供方因异常未能成功注册,该插件将永久处于挂起等待状态,且不会主动抛错。- 防御对策:必须借助系统提供的诊断工具(如
--dump-config或健康检查接口),实时排查未就绪的依赖拓扑。
- 防御对策:必须借助系统提供的诊断工具(如
- 非托管副作用导致的内存泄露:如果插件开发者绕过
ctx.effect直接在原生全局对象上挂载监听器(例如process.on(...)),在插件热卸载后旧回调依然存活,导致脏逻辑执行。 - waterfall 链路被静默截断:某个开发者原本只想在模型请求前打印一行调试日志,但在编写 waterfall 拦截器时忘记了
await next(),直接导致后续的模型调用完全失效。 - 共享内存故障域:由于所有插件运行在同一个 Node.js 进程中,恶意的第三方插件可以直接访问未受保护的全局对象或导致主进程 OOM 崩溃。
针对教学与极简理解,社区提供了约 1600 行的轻量级参考实现 NanoCordis。但请切记:NanoCordis 为了代码精简,省略了流式支持、单会话独立作用域、多 Agent 沙箱隔离与审计事件流。在工业级生产环境中,正是这些被它省略的细节构成了 DSH 的核心壁垒。











