乐于分享
好东西不私藏

DeepSeek 新论文,想让软件插件真正即插即拔

DeepSeek 新论文,想让软件插件真正即插即拔

DeepSeek 新论文,想让软件插件真正即插即拔

论文题目与作者信息 · 图片:论文原图

我们来介绍一下 DeepSeek 的一篇新论文。

论文题目叫《A Programming Paradigm for Spatiotemporal Composability》,中文可以理解为「一套让软件组件随时加入、随时退出的编程方法」。论文一共 88 页,由 Yifan Shi、Wei Zhang 和 Tianyi Cui 完成,作者单位标注为北京大学与 DeepSeek-AI。

这篇论文没有讨论模型训练,也没有比较推理速度。它盯住了一个更基础的软件问题:程序已经跑起来之后,能不能安全地装上、卸下或者替换其中一个插件?

我们平时说插件「即插即用」,注意力通常都在「插」上。点一下安装,插件出现,新功能开始工作。真正麻烦的部分发生在它离开时。插件注册过命令、监听器和定时任务,打开过连接,还可能把服务交给其他插件使用。删掉代码文件,这些东西不会自动消失。

论文想做的事情很具体:软件要记住每个组件改过什么、依赖谁、谁又在用它。组件退出时,系统按照清楚的顺序把这些改动一项项收回来。作者把这套方法叫作「时空可组合性」。名字听起来很学术,核心其实可以拆成三件事:留下撤销记录,维护依赖清单,安排退出顺序。

88 页的篇幅也围着这三件事展开。前面用插件系统和 Agent 软件框架说明问题,中间用一套形式化规则描述组件怎样进入和离开,后面再落到 Cordis 的实现与 Koishi 的插件案例。读这篇论文时,抓住「撤销、依赖、顺序」三个词,后面的公式会容易理解很多。

论文从 VS Code 插件讲起。作者按 2026 年 6 月 9 日的 Marketplace 数据检查了安装量最高的 100 个扩展。其中 87 个包含可执行代码,禁用或卸载时需要重启扩展宿主;只有 7 个声明了对其他非内置扩展的依赖。插件装得进去,单个插件很难在程序继续运行时被完整拔出来。

插件装上容易,拔干净很难

论文将问题拆成撤销组件改动与管理组件依赖两个方向 · 图片:论文原图

先看最常见的情况。一个插件启动后注册路由、监听消息、申请数据库连接,再启动一个定时任务。卸载时,这四件事都要清理。少删一个监听器,回调可能继续触发;忘记停掉定时任务,旧逻辑仍会在后台运行;连接没有释放,新的插件版本可能拿不到资源。

操作系统和容器已经有一套简单办法:重启进程,或者直接换掉整个服务。这种办法很实用,代价也很清楚。为了卸载一个插件,其他插件会一起中断,缓存、连接和正在进行的计算也会随进程消失。论文希望把处理粒度缩小到单个组件。

作者把问题分成两个方向。第一个方向叫时间可组合性,关心组件离开后,它对系统做过的修改能不能被完整撤销。第二个方向叫空间可组合性,关心组件之间的关系:它需要哪些服务,谁提供这些服务,提供者换了以后,使用者该怎样响应。这里的「空间」指依赖关系形成的网络,没有物理空间的含义。

这两件事要同时解决。一个插件可以删掉自己注册的监听器,但它提供的数据库服务也许仍被其他插件使用。此时直接关闭连接池,其他插件的清理代码就会出错。反过来,依赖关系写得再清楚,组件退出后还留下半条路由和一个定时任务,系统依旧没有恢复干净。

静态程序通常在编译或启动时把依赖安排好,资源也能被词法作用域约束。动态系统一直在运行,组件会中途出现、消失或者换成新版本。它留下的影响可能持续很久,依赖对象也会随时变化。论文的主要工作,就是把这些变化放进一套可以追踪、可以推理的运行规则里。

每个动作都留一张撤销单

组件最基础的两个状态:未激活与工作中 · 图片:论文原图

论文先给组件做过的每一次修改配上一张「撤销单」。编程语言理论里,这类修改叫 effect。作者要求每个 effect 同时带着 inverse,也就是对应的撤销动作。

例子很好理解。注册事件监听器时,同时返回删除监听器的函数;添加路由时,同时保存注销路由的动作;申请资源时,同时登记释放资源的方法。运行时把这些撤销动作收集起来,组件退出时倒着执行。安装顺序是 A、B、C,清理顺序就是 C、B、A。

这个设计和我们熟悉的撤销操作很像。组件运行期间做了多少次修改,系统就留多少条记录。到了退出时刻,运行时从末尾一条往前回放,让共享环境尽量回到组件进入之前的状态。

运行时负责保存和执行撤销单,撤销单写得对不对仍要由组件作者保证。论文的证明建立在一个前提上:inverse 确实能够撤回原来的操作。系统不会从任意代码里自动猜出撤销方法,也不会凭空判断两次操作是否完全抵消。

这里所说的「恢复」也不要求内存里的每一个数字都回到原值。论文采用的是观察结果相同:只要外部通过系统公开接口看到的行为和组件加入前一致,就可以认为恢复完成。对象地址、内部编号等实现细节允许发生变化。

多个组件交错运行时,事情会复杂一点。假设执行顺序是 A1、B1、A2、B2,现在只卸载 A。系统要撤掉 A1 和 A2,同时留下 B1 和 B2。如果两边注册的是互不相关的路由,顺序通常可以调整;如果两边改的是同一条中间件链,撤销 A 可能改变 B 的位置。论文因此要求不同组件的修改在需要交换顺序时不能互相干扰。

另一半是依赖清单。论文把它叫作响应式 coeffect。组件可以声明自己需要 database、logger 或某个服务标识。共享环境发生变化后,运行时重新检查这些要求。依赖齐全,组件进入工作状态;依赖被撤走,组件开始退出;无关的变化不会让它反复重启。

Effect 和 coeffect 随后被放进同一个 Context。可以把 Context 理解成系统的共享账本:它记着当前有哪些服务、谁在提供、谁在使用,也保存每个组件积累下来的撤销动作。这样,运行时既知道一个组件改了什么,也知道它和其他组件是什么关系。

谁先退出,顺序很关键

加入加载中与卸载中之后的完整组件生命周期 · 图片:论文原图

依赖关系一旦存在,退出顺序就不能随便安排。假设组件 A 提供数据库服务,组件 B 正在使用它。A 想退出时,不能马上关闭连接池。B 的清理代码可能还要归还连接、结束事务,或者撤掉基于这项服务注册的监听器。

论文给出的流程分两段。A 先停止接受新的使用者,B 随后发现依赖即将失效,开始自己的卸载。B 在清理期间仍能访问已经拿到的旧依赖。等所有使用者都离开,A 再执行自己的撤销动作,真正释放数据库资源。

这个顺序解决了一个很常见的尴尬:服务提供者准备离场,使用者又必须靠它完成收尾清理。把「不再接新客」和「彻底关门」分开,中间就留下了一个安全退出窗口。

论文把每次运行中的组件实例叫作 Fiber。最简单的 Fiber 只有两个状态:Inactive 和 Active。实际程序还要处理加载到一半、卸载到一半、异步任务报错和依赖突然变化,所以完整模型又加入 Reloading 与 Unloading。

如果组件加载到一半出错,Fiber 会记录失败,并执行已经收集到的撤销动作,把半完成的状态清理掉。父组件创建的子组件也会被放进同一棵生命周期树。父组件离开时,运行时沿着这棵树逐层退出,避免子组件被遗留在后台。

加载过程也可以分成多步。组件可以等待异步请求,可以一边拿到依赖一边继续初始化,也可能在完成之前收到退出信号。论文分别描述了继续加载、完成加载、转入卸载和抛出错误时的处理方式,目的都是避免系统卡在一个没人说得清的中间状态。

论文的大量篇幅都用在这些顺序的形式化描述上。普通读者无需跟完每个公式,结论可以说得很直白:热插拔要成为可靠能力,程序必须明确知道当前处于哪一步、前一步做了什么、后一步应该由谁先执行。

Cordis 已经跑起来,Agent 还在下一步

新模块加载失败时,Cordis 恢复缓存并重新装回旧版本 · 图片:论文原图

作者把这套模型做成了 TypeScript 元框架 Cordis。它不限定具体业务,核心工作就是追踪撤销动作、解析依赖关系,再管理组件的加载和退出。聊天机器人、Web 服务或者 Agent 系统都可以在这一层之上继续搭自己的功能。

开发者接触到的接口不算复杂。ctx.effect(...) 用来执行并记录可撤销操作,ctx.set(...) 用来提供服务,ctx.get(...) 读取服务,ctx.use(...) 创建组件。接口背后,Cordis 负责把组件的改动、依赖和生命周期放进同一个运行环境。

论文还给出了一套事务式热更新流程。旧组件先退出,运行时清理模块缓存,再导入新版本并创建新的 Fiber。新模块如果因为语法错误等原因加载失败,系统会恢复原来的缓存,把旧版本重新装回来。更新失败后,程序不会停在新旧版本各占一半的状态。

实际案例来自聊天机器人框架 Koishi。论文写道,Koishi 建立在 Cordis 上,四年间积累了超过 4000 个社区插件,覆盖即时通信适配器、数据库驱动、管理控制台和用户功能。不同作者的插件围绕同一套依赖规则工作,服务提供者变化时,运行时只需要处理相关的使用者。

这个案例也有边界。Koishi 目前使用 Cordis v3,论文介绍的是重新整理 effect、coeffect 语义并重做加载器的 v4。数据来自一套 TypeScript 生态,论文没有给出与其他架构的受控对比,也没有量化运行开销和开发效率的改善幅度。

Agent 是论文提到的潜在应用。一个 Agent 系统可能同时装着工具、记忆、权限、沙箱、持久化和子 Agent 调度。以后如果系统需要在运行中更换这些组件,每次都重启整个进程会打断正在执行的任务,直接替换又可能破坏依赖。Cordis 提供了一套可以继续验证的底层结构。

论文已经完成的是编程模型、Cordis 实现和 Koishi 案例。能够持续改写自身工具链的 Agent 仍属于动机和后续方向。文中没有展示一个已经长期自我更新的 DeepSeek Agent,也没有实验数据证明这套方法会直接提高 Agent 的任务表现。

撤销能力也有天然边界。内存里的注册表、临时文件和连接句柄可以被追踪;一条消息已经发给用户,一笔支付已经完成,外部世界就发生了变化。此时系统只能延迟提交,或者执行退款、删除文件、发送更正等补偿动作。补偿可以减轻影响,无法让已经发生的事情从现实中消失。

回到文章开头,DeepSeek 与北京大学的这篇论文给出了一套很清楚的答案:每次修改都留下撤销记录,依赖变化随时重新检查,组件按照使用关系安排退出顺序。三件事合在一起,软件插件才有机会在程序不停机时被完整换掉。

这套方法不会自动修好所有热更新问题。组件作者仍要写对撤销动作,接口版本仍可能冲突,循环依赖也会让组件无法启动。论文的价值在于,它把过去散落在工程经验里的问题整理成一套明确规则。以后软件需要频繁更换自己的零件时,我们至少知道,装上新零件只完成了一半工作,旧零件能不能安全离场同样重要。

参考来源

1. A Programming Paradigm for Spatiotemporal Composability · User-provided research paper · 2026-08-13