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

在很多框架中,想要给系统添加功能,最简单的做法是写一个独立进程的 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》中,系统被抽象为五个紧密咬合的核心概念:

1. Plugin(插件):能力的贡献者,而非流程的主宰者

在 Cordis 中,插件可以是普通的函数、对象或类。插件的核心特征是:它只负责在自己的生命周期内向 Context 声明服务或注册事件,它绝不假定全局主循环长什么样,更不通过直接 import 其他具体插件来实现调用。

2. Context(上下文):服务容器与作用域隔离边界

每个插件在激活时,都会收到一个专属的 Context 实例。

  • 服务解耦:通过 ctx.llm、ctx.tools 等稳定服务键索取能力,只依赖接口契约,不绑定具体实现。
  • 作用域派生:
    • ctx.extend():创建一个继承父级全部能力的子作用域。
    • ctx.isolate('fs'):在当前子树中将文件系统服务完全隔离,形成沙盒边界。
1
2
3
4
5
6
// 主智能体作用域
const mainCtx = app.ctx;

// 为 Subagent 创建隔离作用域:继承基础配置,但沙箱与工具完全独立
const subagentCtx = mainCtx.isolate('sandbox');
subagentCtx.provide('sandbox', new EphemeralDockerSandbox());

3. inject(依赖注入):空间可组合性(Spatial Composability)

传统软件常靠人工硬编码模块加载顺序。而在 Cordis 中,插件直接声明它依赖的服务:

1
2
3
4
5
6
7
8
class MyToolApprovalPlugin {
// 显式声明:当前插件只有在 tools 与 sessions 服务就绪后才会激活
static inject = ['tools', 'sessions'];

apply(ctx: Context) {
// 业务逻辑安全执行,绝不会遭遇 undefined 异常
}
}

如果依赖的服务尚未就绪,插件自动处于等待挂起状态;一旦依赖的服务被提供,插件立即激活;当提供方插件被卸载时,依赖它的下游插件分支自动挂起并等待新的实现。这就是所谓的“空间可组合性”。

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
2
3
4
5
ctx.effect(() => {
const timer = setInterval(() => heartbeat(), 1000);
// 必须返回一个撤销该副作用的 Disposer
return () => clearInterval(timer);
});

正是因为每个副作用都有主、可逆,Cordis 才能实现可靠的热模块替换(HMR):卸载旧 Fiber $\rightarrow$ 逆序清理副作用 $\rightarrow$ 激活新 Fiber;若新插件初始化抛出异常,则立即回滚至最后一个健康状态。


三、为什么 Agent Loop 值得这份复杂度?

如果 Agent Loop 是一段硬编码在系统核心内部的 while (!done) 循环,那么二次开发者最多只能在预设好的几个阶段挂挂钩子。

而当 Agent Loop 本身也被降维成一个普通的 Cordis 服务(ctx.agentLoop)时,整个调度范式就可以随需替换:

  • 单步串行调度:标准的逐步思考、调工具模式;
  • 并发分支搜索:将调度算法替换为类似 MCTS(蒙特卡洛树搜索)或多候选展开模式;
  • 多智能体博弈循环:由仲裁者插件整体接管步骤推进。

这种高度的自由度,正是 Cordis 架构存在的最大合理性。


四、这套设计的真实失效模式与防御

没有银弹。进程内微内核虽然强大,但也引入了特有的风险面:

  1. 依赖隐式悬挂(Deadlock / Hang):如果插件声明的 inject 键名拼写错误,或者服务提供方因异常未能成功注册,该插件将永久处于挂起等待状态,且不会主动抛错。
    • 防御对策:必须借助系统提供的诊断工具(如 --dump-config 或健康检查接口),实时排查未就绪的依赖拓扑。
  2. 非托管副作用导致的内存泄露:如果插件开发者绕过 ctx.effect 直接在原生全局对象上挂载监听器(例如 process.on(...)),在插件热卸载后旧回调依然存活,导致脏逻辑执行。
  3. waterfall 链路被静默截断:某个开发者原本只想在模型请求前打印一行调试日志,但在编写 waterfall 拦截器时忘记了 await next(),直接导致后续的模型调用完全失效。
  4. 共享内存故障域:由于所有插件运行在同一个 Node.js 进程中,恶意的第三方插件可以直接访问未受保护的全局对象或导致主进程 OOM 崩溃。

针对教学与极简理解,社区提供了约 1600 行的轻量级参考实现 NanoCordis。但请切记:NanoCordis 为了代码精简,省略了流式支持、单会话独立作用域、多 Agent 沙箱隔离与审计事件流。在工业级生产环境中,正是这些被它省略的细节构成了 DSH 的核心壁垒。