Cordis:DeepSeek 插件内核的可回滚副作用设计

GitHub 上一个 TypeScript 仓库最近引来大量关注:cordiverse/cordis,4038 star、198 fork,MIT 协议,仓库描述只有一句话,Meta-Framework of Spatiotemporal Composability(时空可组合性元框架)。它的 README 指向一篇 88 页的配套论文,作者来自北京大学与 DeepSeek-AI;仓库 homepage 指向 deepseek-harness 的官方参考文档,其中写明 Cordis 以 vendor 方式作为 DeepSeek Harness 底层的插件框架被引入。聊天机器人框架 Koishi(5899 star)四年积累的 4000 多个社区插件,跑的也是这套内核。

Cordis 仓库主页

动态组合的两个维度

论文把「动态组合」拆成两个正交维度。

时间可组合性(temporal composability):一个组件被移除时,它对共享环境造成的全部副作用可以完全恢复。对应到插件系统,就是「卸载一个插件后,环境回到加载它之前的状态」。

空间可组合性(spatial composability):组件之间可以声明依赖,系统在运行时解析、提供、撤销这些依赖,并在依赖状态变化时自动响应。对应到插件系统,就是「插件 A 依赖插件 B 提供的服务,B 上线 A 自动激活,B 下线 A 自动休眠」。

论文认为现代软件正同时走向这两个维度的极限:一边是 VSCode 这类插件宿主,一边是不断自我修改组件的 AI agent harness。作者给后者的判断相当直接:当 agent 以有限人工监督持续生成并部署对自己 harness 的修改时,每次修改都是一次动态组合;缺少时间可组合性,每次自我修改都要整体重启,进程内累积状态全部丢失,出错的自我修改甚至可能瘫痪负责恢复的进程本身;缺少空间可组合性,模块只能各自用临时手段探测依赖变化,朴素的代码替换会静默破坏依赖方或引入循环依赖。

插件系统的老问题,量化过了

论文用 VSCode 做代表案例,数字都来自对安装量前 100 扩展的统计。

时间维度:VSCode 把所有扩展跑在一个共享的 extension host 进程里,没有卸载单个扩展代码的机制。纯声明式扩展(主题、键位、代码片段)不含代码可以自由移除,但前 100 名里有 87 个包含可执行代码,移除时需要重启整个 host,波及所有已加载扩展。VSCode 提供 deactivate 钩子,但它只在宿主进程终止时作为优雅停机回调生效,不支持在运行中移除;而且它把副作用回收与副作用创建拆在两个函数里,违背了关注点局部性,完整清理难以验证。

空间维度:VSCode 有 extensionDependencies 字段,但前 100 名里只有 7 个声明了对非内置扩展的依赖。扩展之间交互走 vscode.extensions.getExtension(...).exports,返回值默认无类型,依赖方拿不到受检查的接口。插件宿主只暴露固定的表面扩展点,扩展之间没有安全、结构化的互相依赖方式。

可回滚的副作用:每个效应自带逆变换

形式化从效应系统(effect system)入手。Cordis 把「效应」建模为类型 Γ → Γ × (Γ → Γ) 的函数:作用于当前上下文,返回修改后的上下文,外加一个显式的逆变换。逆变换交还给运行时跟踪,组件卸载时按序执行,环境即被恢复到加载前状态。

多个「变换-逆变换」对之间定义一种扭曲复合(twisted composition):(f₁,g₁)∘(f₂,g₂) = (f₁∘f₂, g₂∘g₁)。正向变换按执行序复合,逆变换按相反顺序累积,这正好就是卸载时需要的行为:后装的先拆。这组对在该复合下构成幺半群,单位元是恒等变换。

实现层面,整套系统只有 ctx.effect 一个上下文变更原语:服务提供、组件实例化、其他一切改变上下文的操作都归结为一次 ctx.effect 调用。它接受一个生成器式回调,回调每产出一个逆变换,运行时就把它前插进复合逆变换里;返回的 dispose 闭包被挂到上下文的卸载链上。LIFO 回收由此成为结构性保证,而非约定。

反应式协效应:把 IoC 容器形式化

空间维度从协效应系统(coeffect system)入手。传统 IoC 容器把依赖建模为简单的键值映射,Cordis 把它升格为协效应上下文:一个依赖偏函数类型 Σ = (k:K) ⇀ 𝒱ₖ,键的类型族 𝒱 保证每个服务键对应特定值类型,依赖访问有静态类型安全。提供服务不能重复提供同一键,撤销服务要求该键存在,违反前置条件的操作报错且不产生状态迁移。

组件把所需服务声明为一份规约(specification),上下文每次变化都对照规约分类为激活、去激活或中性三类,分类结果直接驱动组件的启动与停止。加载顺序从依赖图中自动涌现,应用作者不需要手动编排启动序列。Koishi 的实际运行验证了这一点:一个插件和它依赖的插件通常由不同作者编写,双方唯一的协调物就是连接它们的协效应声明,依赖不可用时插件保持休眠而非报错,依赖恢复时自动唤醒。

fiber:声明式装配与热替换

组件的每次实例化称为一个 fiber,携带自己的生命周期状态机(加载、激活、卸载中,失败态记录错误值)。应用编排者面对的则是声明式配置层:一份持久化记录,每个条目(entry)声明一个 fiber,记录 id(调和键)、url(组件模块地址)、isolate(隔离标注)、intercept(拦截标注)、config(绑定给组件的配置)、disabled(管理开关)六类字段。绑定是双向的:配置变化时 loader 调整 fiber,组件自我修订配置或禁用自己时,变更写回条目。

热模块替换(HMR)建立在这套机制上。Webpack 和 Vite 的 HMR 需要开发者手工标注接受边界(accept 边界),Cordis 不需要:fiber 已经圈定了组件的全部效应与协效应,替换一个作为组件的模块只需处置旧 fiber(回收它安装的一切)再用重载后的模块实例化新 fiber。引擎分三阶段工作:先对变更文件的依赖子图做不动点分类(一旦某个 import 被接受即接受,全部 import 被拒绝即拒绝,陷入 import 环的默认拒绝);再检测依赖树触及变更模块的过期条目;最后执行置换,置换失败(比如新代码有语法错误)时从备份恢复缓存并回滚已做的替换。

DeepSeek 在其中扮演什么角色

仓库 homepage 直接指向 deepseek-harness 文档站的 cordis-primer 页面。该文档(有简体中文版)描述了 Cordis 在 harness 中的实际用法:每个服务占据一个稳定的 ctx.〈key〉槽位,如 ctx.tools、ctx.llm、ctx.sessions,其他插件通过 key 查找服务而非导入具体实现;插件用 inject 声明所需服务,等待就绪后启动。服务间通信用类型化事件,每种事件固定四种分发模式之一:

模式是否 await分发顺序返回值
emit按注册顺序观察
waterfall按注册顺序包装
parallel并行扇出
serial按注册顺序执行

waterfall 是环绕式中间件,监听器收到 (...args, next),可以修改共享决策对象后委托下游,也可以在拥有决策权时直接短路返回。文档给的实践规则:工具流水线事件属于 ctx.tools,模型流式输出属于 ctx.llm,实时 agent 协调属于 ctx.agents;拦截与策略优先用事件,直接能力调用优先用服务方法;每次注册都要有对应的资源释放函数。

论文作者署名为 Yifan Shi(北京大学、DeepSeek-AI)、Wei Zhang(北京大学)、Tianyi Cui(DeepSeek-AI),草稿日期 2026 年 8 月 13 日。结论一节把自我演化的 agent harness 列为下一个验证方向:在 agent 持续生成、替换自身 harness 组件的场景下检验快速替换下的完整恢复保证与拓扑频繁变化下的依赖协调保证。

Koishi:四千插件的四年实证

论文的生产验证来自 Koishi,一个建立在 Cordis 上的开源聊天机器人框架,2019 年 12 月创建,四年积累 4000 多个社区插件,覆盖 IM 适配器、数据库驱动、管理控制台与终端功能。GitHub 贡献者统计显示,Koishi 的 3870 次头部提交与 Cordis 的 537 次提交(占该仓库全部 550 次的 97%)来自同一位作者 shigma,两个项目是同一主线的前后两段。

Koishi 的 Web 控制台是第二个独立的 Cordis 应用,插件组合的是浏览器与 UI 原语而非服务器原语,同一模型跑在两个完全不同的运行时里。论文同时明确标注了证据边界:这是单一生态、单一宿主语言下的观察性结果,属于存在性与采用度证明,对照实验仍然缺失,抽象的开销与对开发者生产力的影响也未量化。Koishi 当前使用 Cordis v3,论文呈现的是语义与 loader 均经过重新设计的 v4。

服务多路复用:从插件到滚动更新

论文第 6 节把协效应模型对齐到 OSGi 式服务架构,并区分两种多实现共存形态。独占绑定:多个实现共享一个接口但同一时刻至多绑定一个,切换要卸载旧提供者再加载新提供者,短暂扰动所有消费方。服务代理(service broker):一个中央服务作为接口入口,后备提供者与消费方都注入它,多提供者共存,由代理分发请求;更新某个后备提供者时代理原地不动,消费方看不到依赖变化,不触发重载。

代理模式支撑三个能力:负载均衡(round-robin、最少负载、延迟加权等策略,提供者本身是普通组件,增删即扩缩容,卸载自动撤销注册并退出路由集);滚动更新(升级实现归结为受控的提供者迁移);跨进程调用。

现状与适用边界

README 顶部标注:Cordis 处于活跃开发中,API 尚未稳定,可能不经通知即变更。论文是修订中的预印本。仓库 2022 年 5 月创建,组织 cordiverse 现有 26 个公开仓库,包结构为 core、create、group、hmr、include、loader、logger-console、timer、utils 九个包。对想上手的开发者,入口是 cordis-primer 文档与论文第 5 节的 Theory-to-implementation 对应表:论文里的每个形式化构件(效应回调、协效应表、fiber 状态机、L-Begin/L-Iter/L-Finish 迭代循环)都在源码里有具名对应物,方向是双向可查的。

来源:

  • GitHub 仓库:github.com/cordiverse/cordis(star、fork、协议、包结构、贡献者统计取自 GitHub API)
  • 论文:A Programming Paradigm for Spatiotemporal Composibility,Yifan Shi、Wei Zhang、Tianyi Cui,草稿日期 2026-08-13,github.com/cordiverse/paper
  • 官方文档:deepseek-harness.github.io/deepseek-harness/reference/cordis-primer
  • Koishi:github.com/koishijs/koishi