- 北大与 DeepSeek-AI 团队 8 月 13 日发布 88 页预印本《A Programming Paradigm for Spatiotemporal Composability》,把「时空可组合性」从工程直觉升级为有运算语义的形式化范式。
- 论文把 effect(效应)与 coeffect(余效应)从编译期类型系统抬升为运行时机制:可逆效应负责卸载回收,反应式余效应负责依赖协调。
- 落地实现是 Cordis——一个 MIT 开源的 TypeScript 元框架(约 690 星),只做「插件系统」一件事,不绑定任何具体领域。
- 规模验证来自 Koishi:基于 Cordis 的聊天机器人框架,论文称累计 4000+ 社区插件,官方市场当前约 3000+。
- 同日 DeepSeek 开源的 Harness(「一切皆插件」,上线当天破 2.2 万星)底层正是 Cordis,本论文是它的设计依据。
- 论文明确声明没有量化基准:抽象层的运行时开销、相对其它架构的开发效率对比都标注为「未来工作」。
插件卸载为什么这么难:时空可组合性要解决什么
写一个插件容易,安全地卸载一个插件很难。VS Code 把所有扩展跑在同一个「扩展宿主」进程里,扩展可以动态安装,却没法在运行时单独卸载某一段代码:一旦 activate 执行过,停用或卸载它就得重启整个宿主,连累所有已加载的扩展。论文统计了安装量前 100 的扩展,其中 87 个包含可执行代码,卸载时都要触发这种重启;只有主题、键位、代码片段这类纯声明式扩展才能自由移除。
更隐蔽的问题在依赖上。VS Code 虽然提供了 extensionDependencies 用来声明扩展间依赖,但几乎没人用——前 100 扩展里只有 7 个声明了对非内置扩展的依赖。原因在于它的 API 只暴露命令、视图、语言特性这类固定扩展点,扩展之间没有安全的、带类型检查的互相依赖方式,返回的 exports 默认还是 any。
这还不是最难的场景。论文真正的靶子是「自进化 agent harness」:未来一个 agent 框架可能一边持续服务请求,一边由 AI 生成、替换自己的组件,每一次替换都是一次动态组合,卸载不干净就会让缓存、连接、监听器越积越多。论文把问题拆成两个正交维度,一句话定义:时间可组合性等于移除组件时彻底回滚它对共享环境的所有改动;空间可组合性等于组件能声明、发现并反应式地协调彼此依赖。
两个核心机制:可逆效应与反应式余效应
时间维度靠「可逆效应」解决。传统 effect 系统只描述一个计算会做什么副作用,Cordis 则要求每一次「上下文变换」都携带一个逆操作,由运行时跟踪。这很像 Git 的 staging 逻辑或 C++ 的 RAII:临时改动随时可撤,真正进入生命周期的那一步才需要显式处理。区别在于 Cordis 把「可撤销」从代码纪律变成了运行时保证——插件作者在 ctx.effect() 里注册副作用时返回一个 disposer,卸载时运行时按依赖逆序自动调用,作者不用手写 uninstall 路径。
空间维度靠「反应式余效应」。coeffect 是 effect 的对偶,描述的不是「计算改变了什么」,而是「计算依赖环境提供什么」。Cordis 让组件通过 inject 声明需要的服务,上下文每变化一次,运行时就把变化对每个组件分类为「激活」「停用」或「中性」。这比常见的前端依赖注入更进一步:依赖不是启动时一次性注入,而是随时会变的活拓扑——存储后端切换、聊天平台适配器重连时,只有依赖真正改变的组件被重新激活,其余组件纹丝不动。
论文最关键的抽象动作,是把效应上下文和余效应上下文合并成同一个「上下文类型」,在余效应上定义观测等价来给效应提供独立性,从而构成完整范式;再把这些机制装进「组件」概念,给出一套动态组合演算,证明单个组件的时空可组合性能随组合传递到整个交错运行的系统。对读者可以简化成一句:无论加载、卸载、重载多少次,系统最终行为只由最后启用的那批组件决定,与中间过程无关。
现实验证与效果边界:Koishi 的 4000+ 插件说明了什么
论文用 Koishi 做验证。Koishi 是基于 Cordis 的开源聊天机器人框架(GitHub 约 5700 星),发展四年,论文称累计 4000+ 社区插件,官方市场当前收录约 3000+。它把「一切皆插件」做到极致:IM 适配器、数据库驱动、管理后台、用户功能全是插件,Koishi 本体只提供聊天领域的词汇;它的网页控制台还是第二个独立运行的 Cordis 应用。
这个案例证明了范式在两个方向成立。时间上,运营者在控制台禁用一个插件,副作用当场撤干净;开发时改一个插件,热更新只重放这一个插件,系统其余部分的缓存和活跃连接原样保留。空间上,适配器、驱动和功能插件构成真实依赖拓扑——切换存储后端或重连适配器时,只有依赖真正变化的插件被重新激活,依赖缺失的插件安静地保持非激活,而不是报错。
但论文把边界写得很诚实。它明确说这不是量化评估:抽象的运行时开销、相对其它架构的开发效率对比都是未来工作;证据只有一个生态、一种宿主语言(TypeScript),无法区分范式本身的功劳与 TS 实现、Koishi 特定领域的功劳。社区反馈也提醒了现实成本:Koishi 基于 Node.js,内存占用偏高,小内存 VPS 跑不动;插件质量参差不齐,官方甚至给部分插件打「不安全」标识。
快速上手:从 Cordis 到 DeepSeek Harness
最省事的入口是 DeepSeek Harness。它是 DeepSeek 同日开源的 agent harness,底层以 vendor 方式引入 Cordis,一条命令即可启动 Web UI,默认地址 http://127.0.0.1:3080:
想直接基于 Cordis 写插件,只需掌握五个概念:插件是实现服务的对象(函数或 Service 子类);上下文是服务的容器(ctx.);inject 声明依赖;类型化事件用于通信;注册是可逆的副作用。一个最小插件的骨架长这样:
事件分发有四种模式,对应不同语义:emit 让监听器观察、waterfall 允许短路包装、parallel 并行扇出、serial 按序执行。判断该用事件还是服务方法有一条原则:拦截和策略用事件,直接调用稳定能力用服务方法。
生态定位:谁该关注,为什么是今天
论文真正的信号是生态归属。Cordis 从 2022 年就在 GitHub 上迭代,作者是 Koishi 的创始人,现任职 DeepSeek-AI。8 月 13 日 DeepSeek 开源 Harness 并把它作为底层插件框架,等于给这个原本偏「小圈子」的框架贴上了主流背书,也让这篇论文从纯学术预印本变成了一个正在大规模落地的工程规范。
和现有方案相比,差异点很清晰。ZIO、Effect-TS、fp-ts 这类单子式效应系统,要求你把程序写进 effect 类型里才能获得追踪,Cordis 则把追踪做成普通宿主代码之上的一层覆盖;Effekt、Granule 走编译期静态路线,只覆盖词法固定的作用域,Cordis 把同一对概念做成运行时机制,专门处理组件在运行时来来去去的情形。
三类人最该读这篇论文。写框架的人——Cordis 演示了「框架的框架」如何只负责可组合语义、把领域词汇留给上层;做插件系统的团队——时空可组合性给了热更新、热卸载一个明确的形式化验收标准;做 agent harness 的人——论文把自进化 agent 的场景写进了动机,那正是它下一步要验证的靶子。
行动清单:五步把可逆性落地到你的插件系统
- 把所有副作用收口到注册点:通过上下文做的资源分配、监听器、定时器,都走 ctx.effect() 注册并返回 disposer,别在模块顶层裸写。
- 用 inject 声明依赖,不要手动排启动顺序:让运行时按依赖关系决定谁先加载、谁先卸载。
- 判断用事件、调用用服务方法:策略和拦截走事件,稳定的直接能力走 ctx. 上的服务方法。
- 卸载路径交给框架,别自己写 uninstall:依赖逆序自动回收是范式核心承诺,手写清理既重复又容易漏。
- 先跑通 DeepSeek Harness 再谈深度:用 npx @deepseek-ai/dsh web 启动,再照着 cordis-primer 教程把五个概念过一遍。