若你已读过 《DeepSeek Harness 架构解读:三层组合与「一切皆插件」》,会对 Profile 叠 Bundle、Cordis 上的运行时主干、Session 日志作事实源有印象;插件注册走 ctx.effect(),依赖声明走 inject,卸载时会撤销注册。那篇文章偏「dsh 怎么读」;本文换一层:这些机制背后的设计问题是什么,论文里怎么命名、怎么论证、边界画在哪。
论文1 全称 A Programming Paradigm for Spatiotemporal Composability (时空可组合性),作者来自北京大学与 DeepSeek-AI,稿于 2026 年 8 月公开在 cordiverse/paper。Cordis 是论文提出的插件框架实现;DeepSeek Harness 是 Cordis 上的应用之一,但论文正文里的规模案例是聊天机器人平台 Koishi(四千余个社区插件),并没有把 dsh 当作独立案例写进实验章节。下文在讲清论文概念后,再对照 Harness 里你已见过的 API;可与 《DeepSeek Harness 架构解读:三层组合与「一切皆插件」》 对照阅读。
本篇想要分享的是:论文1 如何把「运行时装卸组件」形式化成 spatiotemporal composability(时空可组合性)------时间维要求移除组件时完全撤销其对共享环境的修改,空间维要求组件声明并响应式管理彼此依赖;以及 revertible effect、reactive coeffect 在 Cordis / Harness 里分别对应 ctx.effect() 与 inject。动机不只来自 VS Code 式插件平台,也来自论文 §1.2.2 讨论的 self-evolving agent harness(在持续服务中改自己的 harness 组件)。
卸载扩展为什么要重启:VS Code 里的反例
动态组合系统有一个朴素期待:装一个扩展,系统多一块能力;卸掉它,系统应回到装之前的状态。论文1 §1.2.1 用 VS Code 说明这期待经常落空:作者统计 Marketplace 安装量最高的 100 个扩展,有 87 个在文档或 issue 里写明「卸载后需要重启或重载窗口」,否则菜单项、命令、主题或全局状态可能残留。残留不一定恶意,多半是扩展在激活时改了共享环境------注册了命令、挂了全局 listener、写了配置------卸载路径却没有对称的清理。
对插件平台来说,这既是体验问题,也是组合语义问题:若 A、B 两个插件可以任意顺序安装卸载,而 A 卸载后留下 B 无法感知的全局状态,则「当前系统 = 已装插件集合的函数」这一说法不成立。论文1 §1.1 把这一维称为 temporal composability(时间维可组合性):组件被移除时,它对共享环境造成的修改必须被完全、安全地撤销;实现上需要追踪该组件的每次资源分配、事件注册与状态变更,并在移除时有次序地回收。Harness 文档里 ctx.effect() 在 fiber 销毁时执行 dispose,正是在工程上回应这一维。架构解读一文已说明:Cordis 的 effect 不是 React 组件里的「副作用」------在论文里,effect 指计算对环境产生的修改;Cordis 要求这类修改可追踪、可配逆操作,例如往 ctx.tools 注册工具、订阅事件,卸载时成组撤回。
Agent harness 的自我进化:论文里的第二类动机
VS Code 代表人工策展的插件生态:扩展由人发布,装卸频率相对低。论文1 §1.2.2 并列提出第二类场景:self-evolving agent harness(自我进化的 Agent 运行时)。现代 Agent 依赖 harness 在运行时组合工具集、执行环境、权限与沙箱、Session 持久化、上下文与记忆、多 Agent 编排,以及面向用户与自动化的接口------DeepSeek Harness 所覆盖的正是这一层,而不是模型 forward 本身。
论文在这一节里区分两个层次。完整愿景是:harness 在持续对外处理请求的同时,由 Agent 生成并部署对自身组件的修改------换 tool 实现、增删 capability、替换编排逻辑,都属于「改 harness 自身」。更窄、已可见的前驱是 model-synthesized reusable tools(论文引用 14):模型产出可复用工具并挂进运行时,相当于组件级自我修改的弱化版,尚未到整段替换 agent loop,但每一次挂载与卸载已经是一次 dynamic composition。
论文的论点是:自我进化若变成高频、低人工监督的操作,spatiotemporal composability 就从「卸载扩展要不要重启」升级为服务能不能连续、失败了能不能收回去。
缺少 temporal composability 时,每次自我修改往往只能整进程重启,进程内累积的状态(连接、缓存、进行中的 Turn)一并丢弃;修改越频繁,累计不可用时间越可观,在途任务反复被打断。更危险的是:一次有缺陷的自我修改可能把负责恢复的进程本身弄挂,连回滚入口都没有。缺少 spatial composability 时,各模块只能各自用临时办法感知依赖出现、消失或换身份;朴素的热替换代码可能静默弄坏下游,或在 reload 时才暴露循环依赖------论文 §1.2.2 把这类失败与 VS Code 缺乏结构化 inter-extension 依赖并列为空间维动机。
与粗粒度替代方案(§1.2.3)对照:操作系统在进程粒度提供时间维撤销(终止进程),容器编排器在服务粒度提供空间维依赖管理。多数系统靠重启进程或更换 Pod 凑合,但在自我进化场景下代价偏高------每次重启重建状态要秒级到分钟级,还要靠冗余副本维持可用性;容器级编排又无法表达同一地址空间内组件之间的细粒度依赖,本可本地函数调用的事被迫走网络。Cordis 针对的是与组件同粒度的 effect 追踪与 coeffect 解析,而不是再包一层进程或容器。
两个维度:时间上收得回,空间里跟得上
论文1 §1.1 把 spatiotemporal composability(时空可组合性)拆成两个正交维度------在静态组合已研究成熟的代数性质之外,动态组合还要单独满足下面两条。
Temporal composability(时间维):论文的表述是,组件移除时,其对共享环境的所有修改必须被 完全且安全地撤销 ------不是「尽量清理」,而是环境应回到该组件加入前的状态。实现上要追踪资源分配、事件注册、状态变更,并在移除时有次序回收。工程上对应 revertible effect(§3.1):每次对环境的状态变换都携带一个 显式逆变换(inverse) ,运行时追踪并复合这些逆变换,使卸载时能结构性恢复环境。
Spatial composability(空间维):论文的表述是,组件能以 结构化、可验证 的方式 声明、发现、解析 彼此依赖;并在依赖拓扑变化时协调生命周期。工程上对应 reactive coeffect(§3.2):组件用 coeffect specification(依赖键集合)声明需要从环境读取什么;共享 context 每次变化时,运行时按该 specification 把变迁分类为 activating(从不满足变为满足)、deactivating(从满足变为不满足)或 neutral,并据此驱动组件激活或撤销 effect。
两维正交:只做时间维,可能出现「卸干净但依赖未声明、装顺序仍错」;只做空间维,可能出现「依赖跟上了但卸载留下未追踪的全局状态」。Cordis 用 ctx.effect() 管时间维,用 inject 管空间维;架构解读里的 ctx.tools、ctx.llm 等服务键,以及插件加载顺序,都建立在这套语义之上。
effect 与 coeffect:论文里各指什么
论文从类型论借词,容易与前端俗语混淆。§2 的预备知识里,两个方向是对偶的:
Effect(效应):在 effect system 里,类型推导规则除了结果类型,还标注计算 可能产生哪些副作用 ------即计算 如何修改 其环境(写状态、注册回调、改共享 context 等)。经典 effect system 在 词法固定、编译期 的作用域里分析这一点。
Coeffect(共效应):对偶地,coeffect system 标注的是 context------计算 从环境需要什么前提 (要访问的资源、要持有的权限、要依赖的服务等)。论文1 §2.2 的概括是:effect 建模程序对世界的 影响 ,coeffect 建模世界对程序的 约束。
动态组合里,论文1 §2.3 把两维问题映射到这两个概念:时间维要求组件对共享环境的修改 可撤销(revertible) ;空间维要求组件间依赖 被声明并响应式管理(reactive) 。静态类型系统分析不了「部署后才加载的插件、运行时才出现的依赖」,所以 §3 把它们 提升为运行时机制。
| 论文术语 | 论文1 中的定义(要点) | Cordis / Harness 里的落点 |
|---|---|---|
| revertible effect | 对环境的状态变换 Γ→Γ,且每次变换携带显式 inverse;运行时追踪并复合 inverse,卸载时恢复环境(§3.1) | ctx.effect(() => { ...; return dispose });fiber dispose 时执行逆操作 |
| reactive coeffect | 组件声明 coeffect specification(依赖键集合);context 每次变化按 spec 分类为 activating / deactivating / neutral,驱动激活或撤销(§3.2) | inject(ctx => [ctx.agent, ...]);依赖就绪或变更时重建 fiber |
| component | 三元组:依赖声明 𝑑、provision 𝑝(可提供的 coeffect 键)、带 witness 的 effect 函数 𝑒(§4.1 Def. 43) | 一行 bundle 插件配置 + 其实现类 |
| fiber | component 的一次实例化,自带 lifecycle 状态(active / inactive 等)(§4.1 Def. 44) | 加载后运行在独立 lifecycle 单元里的插件实例;卸载即 dispose |
| context(Γ / ctx) | 统一承载 effect 与 coeffect 的运行时环境;effect context 含当前状态与 accumulator(§3.1--3.3) | ctx.sessions、ctx.tools 等键与事件总线 |
写 Harness 插件时只要记住:凡是通过框架 API 改共享状态、订事件、注册 tool 的路径,应走 ctx.effect() 或 ctx.on() 等可追踪 API,这样卸载时 Cordis 才知道要撤销什么;绕过框架直接改全局单例,时间维组合性就破了,也和 Session 日志、replay 那条线冲突------架构解读里强调过,模型可见状态必须进日志,不能只活在内存里。
一次典型注册长这样:ctx.effect(() => { const off = ctx.on('agent/pre-step', handler); return () => off(); })。括号里的函数在插件激活时执行,return 的函数在 fiber dispose 时执行------对应论文里 effect function 的「执行 𝑒 累积副作用 / 停用 apply accumulator 恢复 context」。若只 ctx.on(...) 而不包进 effect,监听可能不会在卸载时卸掉,等价于 VS Code 的 activate 与 deactivate 分离、难以验证清理完整(§1.2.1)。inject 声明 coeffect specification:「我需要 ctx.agent 与 ctx.sessions 等依赖键先在 context 里绑定」;loader 据此排序,依赖被 patch 掉或 provider fiber 撤销时,notify 会给出 deactivating,触发依赖方 fiber 重建,而不是让下游在缺失 binding 上乐观访问。
Cordis 分层:core、loader、应用
论文1 把 Cordis 划成三层,职责比「一个插件 DLL 系统」更细。
Core 层实现 fiber 生命周期、Context、effect 追踪与 inject 解析。每个插件实例在一个 fiber 里运行;effect 注册进 fiber 的 dispose 栈,inject 则在 fiber 创建或依赖变更时解析依赖图,保证被依赖的 Service 先就绪。
Loader 层读取组合配置(YAML patch 链,Harness 里即 Profile / Bundle / cordis.patch.yml),按依赖顺序实例化插件行,并支持 HMR(Hot Module Replacement):开发时改单个插件模块,可在不重启整个进程的情况下替换该 fiber 的实现,同时仍走 dispose / 重建路径。论文对比的是「整包重启」:Koishi 传统模式下改插件常要重启 Node 进程;Cordis v3 上启用 HMR 后,局部更新比例显著上升。
Applications 层是运行在 Cordis 上的产品:Koishi 是论文案例;DeepSeek Harness 是同一框架的另一应用。应用层决定有哪些 ctx.* 服务、事件名、默认 bundle 内容,但不改变 core 对两维组合性的承诺。
Loader 与 core 的分工,对应架构解读里的组合层与运行时主干:patch 决定「装哪些插件、什么顺序」,core 决定「装上去之后如何 dispose、依赖变了如何重载」。dsh --dump-config 看到的展开树,是 loader 解析后的结果;不是手工翻 YAML 就能完全推演行为,正是因为 inject 与 effect 在运行时才最终确定依赖与清理顺序。
对用户可见的差异是:改 home 目录下的 cordis.patch.yml、换 Profile、或增删一个 Bundle,loader 会 diff 出哪些 fiber 需要新建、哪些需要 dispose,能热更的走 HMR,必须整进程重建的才要求重启。论文在 Koishi 上统计的是「改插件后不必整进程重启」的比例;Harness 开发者日常也会感受到类似分界------改 TypeScript 插件源码与改 patch 组合,代价不在一个量级。HMR 省的是迭代时间,不是安全隔离:生产环境若要把 untrusted 插件关进沙箱,仍靠 subprocess / 远程 capability Provider,那是 §6.1 边界外的另一层设计。
系统边界:哪些变更不能指望「完美撤销」
论文1 §6.1 用 system boundary 界定 revertible effect 的适用范围:运行环境被分成内部与外部。(1)某位置在内部,当且仅当系统能独占修改它,且能把该位置恢复到修改前------对此类操作进入 Γ、被追踪,inverse 可执行。(2)某位置在外部,当上述任一能力不成立------在模型里等价于恒等变换,既不追踪也不恢复。
下列常见情况落在 boundary 外,或只能部分保证。
进程外状态:插件写的磁盘文件、外部数据库记录、已发出的 HTTP 请求,框架无法在你卸载插件时自动抹掉。effect 能撤销的是 Context 内注册的服务与订阅,不是进程外已发生的外溢影响。
原生模块与 OS 资源:打开的文件描述符、子进程、某些 native binding,若清理函数写得不完整,仍会泄漏;Cordis 提供 dispose 通道,但清理逻辑仍由插件作者负责。
非确定性或不可逆操作:例如随机数种子已消费、区块链交易已广播,时间维语义不要求物理世界回滚。
跨 fiber 的手动共享:两个插件若绕过 ctx 共享全局变量,inject 无法追踪,卸载其中一个时另一个仍可能持有失效引用------这与 VS Code 扩展留下全局 listener 是同一类失败。
Harness 把 Session JSONL 日志定为 agent 可见性的事实源,一部分原因是为了把可 replay 的状态锁在可审计、可 fork 的边界内;沙箱、远程 subprocess、LLM 调用则划在 capability 接口之后,换 Provider 不等于 Cordis 自动撤销旧 Provider 在外部云上的账单。你在 ctx.bash 或远程沙箱里启动的子进程、写出的临时文件,dispose 可以关掉本地句柄,但无法保证云端 VM 立即销毁------这类外溢影响要靠 Provider 契约与运维流程,不在 Cordis core 的数学保证里。理解边界后,不会误读「一切皆插件」为「装卸零成本、零残留」------而是在 Context 与配置层尽量做到可逆,在系统外显式承认不可逆。
对照 DeepSeek Harness
把 架构解读 与本文并起来读,可以得到一条从论文到产品的线。起点是 VS Code 反例与 §1.2.2 的自我进化动机:动态组合不只发生在人装插件时,也可能发生在 Agent 高频改自己的 harness 组件。机制层是 revertible effect 对应 ctx.effect() 与 dispose,reactive coeffect 对应 inject 与依赖变更后的重建。Loader 读 patch 链、实例化 fiber 树;HMR 缩短开发循环,但不等于生产环境可以跳过进程级隔离。Harness 用同一套 core 做 Agent Turn、tool、Session,并叠加 Profile / Bundle 与「模型可见性进日志」的产品约束。最后一条线是边界:Context 内可撤销不等于外部世界可撤销,插件作者仍需写对 dispose,仍需避免全局单例。
若你已在写 dsh 插件:优先检查所有注册是否包在 ctx.effect() 里、依赖是否用 inject 声明、是否在 dispose 里对称注销。若你在做 OpenClaw 式产品二次开发:架构解读里写的「旁挂插件 vs 换 bundle / 换 loop」决策,底层也是这两维------换 bundle 会触发 loader 重解析与 fiber 重建,时间维清理是否完整取决于各插件 dispose 是否写全。
收尾
Cordis 论文的价值,在于把「插件平台」从功能清单提到可组合语义:卸载后环境能否恢复、依赖变更后下游能否跟上。读论文有助于理解为什么官方要求注册走 ctx.effect()、为什么 inject 不是可选语法糖、为什么排障时值得执行 --dump-config。若你还没建立 Harness 的整体图景,可先读 《DeepSeek Harness 架构解读:三层组合与「一切皆插件」》。
参考
1 Yifan Shi, Wei Zhang, Tianyi Cui. A Programming Paradigm for Spatiotemporal Composability. Draft, 2026-08-13.