[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"consumer-news-detail-1009":3,"consumer-news-interaction-1009":39,"consumer-news-related-1009":42},{"detail":4,"item":35},{"card":5,"schemaVersion":22,"fields":23,"content":29},{"id":6,"kind":7,"targetType":8,"targetId":9,"subtype":7,"typeLabel":10,"title":11,"subtitle":12,"summary":13,"coverUrl":14,"badgeText":15,"href":16,"sourceName":12,"meta":17,"metrics":19,"tags":20,"resolved":21},"NEWS_ARTICLE:1009","news","NEWS_ARTICLE",1009,"资讯","Agent Harness 的热插拔难题：DeepSeek 论文揭秘 Cordis 如何管理可逆副作用与动态依赖","博客园","读《A Programming Paradigm for Spatiotemporal Composability》 今天的 Agent 已经不只是一个“模型 + Prompt”。一个可长期运行的 Agent harness，通常还要管理工具、记忆、权限、沙箱、会话状态、子 Agent 和任务编排。","https:\u002F\u002Fimg2024.cnblogs.com\u002Fblog\u002F139239\u002F202609\u002F139239-20260907090620065-646015531.png","","\u002Fnews\u002F1009",[18],"2026",{},[],true,"consumer-content-detail-v1",{"sourceName":12,"authorName":24,"summary":13,"description":13,"publishTime":25,"updateTime":26,"sourceUrl":27,"language":28},"葡萄城技术团队","2026-09-07T09:07","2026-09-11T15:22:03","https:\u002F\u002Fwww.cnblogs.com\u002Fpowertoolsteam\u002Fp\u002F22868171","中文",{"format":30,"policy":31,"normalized":21,"html":32,"text":33,"wordCount":34,"hasBody":21},"HTML","NEWS_CONTENT_V1","\u003Ch2>读《A Programming Paradigm for Spatiotemporal Composability》\u003C\u002Fh2>\n\u003Cp>今天的 Agent 已经不只是一个“模型 + Prompt”。一个可长期运行的 Agent harness，通常还要管理工具、记忆、权限、沙箱、会话状态、子 Agent 和任务编排。更进一步，未来的 Agent 可能会在服务请求的同时修改自己的工具链或运行时模块。\u003C\u002Fp>\n\u003Cp>这会立刻带来一个问题：\u003C\u002Fp>\n\u003Cblockquote>\n \u003Cp>如果组件可以在运行时被加载、卸载、替换和重配置，系统怎样保证状态不泄漏、依赖不失效、正在执行的任务不被无序打断？\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>论文《A Programming Paradigm for Spatiotemporal Composability》给出的答案，是把动态组合拆成两个维度，并把传统编程语言里的 effect 和 coeffect 提升为运行时机制。\u003C\u002Fp>\n\u003Cp>本文不复述论文中的全部证明，而是从工程角度解释它的核心模型、实现方式，以及它对 Agent harness 的启发。\u003C\u002Fp>\n\u003Ch2>一、动态组合缺的不是加载，而是可控的卸载\u003C\u002Fh2>\n\u003Cp>传统软件组合大多在编译期完成：函数调用、模块导入、类继承都形成相对固定的结构。插件系统和自演化 Agent 则要求组件在运行时到来和离开。\u003C\u002Fp>\n\u003Cp>论文认为，动态组合至少有两个正交维度。\u003C\u002Fp>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fi1.wp.com\u002Fimg2024.cnblogs.com\u002Fblog\u002F139239\u002F202609\u002F139239-20260907090620065-646015531.png?w=720&amp;quality=65&amp;strip=all\" alt=\"image\">\u003C\u002Fp>\n\u003Cp>很多系统采用粗粒度的替代方案：出问题就重启进程，服务依赖交给容器编排。这样当然能恢复，但代价是丢失进程内状态、打断进行中的请求，并把本来可以是进程内调用的依赖变成网络调用。\u003C\u002Fp>\n\u003Cp>对自演化 Agent 来说，频繁重启尤其昂贵。Agent 可能正处于一个长任务中，拥有尚未持久化的上下文、缓存和连接；如果一次自修改必须让整个 harness 重启，修改本身还可能破坏负责恢复系统的代码。\u003C\u002Fp>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fi1.wp.com\u002Fimg2024.cnblogs.com\u002Fblog\u002F139239\u002F202609\u002F139239-20260907090627533-137515014.png?w=720&amp;quality=65&amp;strip=all\" alt=\"image\">\u003C\u002Fp>\n\u003Cp>\u003Cem>图 1：动态组合同时关注组件生命周期和组件依赖拓扑。\u003C\u002Fem>\u003C\u002Fp>\n\u003Ch2>二、从静态 effect\u002Fcoeffect 到运行时上下文\u003C\u002Fh2>\n\u003Cp>经典 effect system 描述“一个计算会对环境做什么”，coeffect system 描述“一个计算要求环境提供什么”。论文的关键动作不是再加一层类型注解，而是把上下文本身变成运行时的一等实体。\u003C\u002Fp>\n\u003Cp>可以先把上下文 \u003Ccode>Γ\u003C\u002Fcode> 想成一个共享运行时环境：工具注册表、事件监听器、权限、连接、缓存和组件提供的服务都可以通过它访问。\u003C\u002Fp>\n\u003Cp>论文的统一方向是：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>effect 负责修改上下文，并且必须能撤销；\u003C\u002Fli>\n \u003Cli>coeffect 负责声明依赖，并且在依赖变化时触发响应；\u003C\u002Fli>\n \u003Cli>所有组件和环境的交互都经过同一个 context。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>这样，加载和卸载不再是两个容易漂移的生命周期回调，而是同一套上下文变换的正向和逆向。\u003C\u002Fp>\n\u003Ch2>三、可逆效果：每次修改都带着 undo\u003C\u002Fh2>\n\u003Ch3>3.1 基本模型\u003C\u002Fh3>\n\u003Cp>普通的环境变换可以写成：\u003C\u002Fp>\n\u003Cp>f : Γ -&gt; Γ\u003C\u002Fp>\n\u003Cp>论文要求一个 effect 同时返回新上下文和撤销函数：\u003C\u002Fp>\n\u003Cp>e : Γ -&gt; Γ × (Γ -&gt; Γ)\u003C\u002Fp>\n\u003Cp>也就是：\u003C\u002Fp>\n\u003Cp>e(oldContext) = (newContext, undo)\u003C\u002Fp>\n\u003Cp>例如，一个组件注册天气工具时，操作不是简单地执行：\u003C\u002Fp>\n\u003Cp>set(\"weather\", weatherTool)\u003C\u002Fp>\n\u003Cp>而是同时提供：\u003C\u002Fp>\n\u003Cp>undo(context) = delete(\"weather\")\u003C\u002Fp>\n\u003Cp>组件卸载时，运行时执行当时保存的 \u003Ccode>undo\u003C\u002Fcode>，而不是依赖开发者另外写一个可能遗漏清理动作的 \u003Ccode>deactivate\u003C\u002Fcode>。\u003C\u002Fp>\n\u003Ch3>3.2 运行时怎样累计撤销操作\u003C\u002Fh3>\n\u003Cp>论文用 effect context 保存两部分状态：\u003C\u002Fp>\n\u003Cp>∂Γ = Γ × (Γ -&gt; Γ)\u003C\u002Fp>\n\u003Cp>第二项是撤销累加器。假设组件依次执行：\u003C\u002Fp>\n\u003Cp>A：注册工具 -&gt; undo_A\u003C\u002Fp>\n\u003Cp>B：注册事件监听器 -&gt; undo_B\u003C\u002Fp>\n\u003Cp>累加器会保存组合后的撤销函数，卸载时按逆序执行：\u003C\u002Fp>\n\u003Cp>undo_B\u003C\u002Fp>\n\u003Cp>undo_A\u003C\u002Fp>\n\u003Cp>这和资源管理中的 LIFO 原则一致：后申请的资源先释放。\u003C\u002Fp>\n\u003Cp>如果用伪代码表达，核心大致是：\u003C\u002Fp>\n\u003Cp>async function effect(callback) {\u003C\u002Fp>\n\u003Cp>const inverses = []\u003C\u002Fp>\n\u003Cp>for await (const inverse of callback()) {\u003C\u002Fp>\n\u003Cp>inverses.unshift(inverse) \u002F\u002F 后发生的效果先撤销\u003C\u002Fp>\n\u003Cp>}\u003C\u002Fp>\n\u003Cp>return async function dispose() {\u003C\u002Fp>\n\u003Cp>for (const inverse of inverses) {\u003C\u002Fp>\n\u003Cp>await inverse()\u003C\u002Fp>\n\u003Cp>}\u003C\u002Fp>\n\u003Cp>}\u003C\u002Fp>\n\u003Cp>}\u003C\u002Fp>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fi1.wp.com\u002Fimg2024.cnblogs.com\u002Fblog\u002F139239\u002F202609\u002F139239-20260907090636983-142634269.png?w=720&amp;quality=65&amp;strip=all\" alt=\"image\">\u003C\u002Fp>\n\u003Cp>\u003Cem>图 2：effect 不只修改上下文，还把对应的撤销操作交给运行时追踪。\u003C\u002Fem>\u003C\u002Fp>\n\u003Cp>论文中的实现 \u003Ccode>ctx.effect\u003C\u002Fcode> 还支持异步迭代：每完成一个 effect，就把它的 inverse 放入累加器；如果组件在中途被要求停止，只撤销已经完成的部分。\u003C\u002Fp>\n\u003Ch3>3.3 为什么 inverse 必须在运行时产生\u003C\u002Fh3>\n\u003Cp>很多操作没有一个对所有状态都适用的固定逆函数。\u003C\u002Fp>\n\u003Cp>例如：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>分配资源后，逆操作需要知道本次分配得到的句柄；\u003C\u002Fli>\n \u003Cli>建立连接后，逆操作需要关闭这条具体连接；\u003C\u002Fli>\n \u003Cli>注册组件后，逆操作需要撤退刚刚生成的 fiber；\u003C\u002Fli>\n \u003Cli>创建临时文件后，逆操作需要删除这一个文件。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>所以论文允许 effect 在实际执行时，根据当前状态生成自己的 inverse。\u003C\u002Fp>\n\u003Ch3>3.4 可逆不等于可以随便删除\u003C\u002Fh3>\n\u003Cp>单个组件按自己的逆序撤销通常没有问题。多个组件交错修改同一环境时，就必须讨论 \u003Cstrong>independence，效果独立性\u003C\u002Fstrong>。\u003C\u002Fp>\n\u003Cp>论文要求两个独立 effect 满足更强的条件：\u003C\u002Fp>\n\u003Col>\n \u003Cli>双方的正向操作和逆操作可以交换；\u003C\u002Fli>\n \u003Cli>一个 effect 不会因为另一个 effect 改变状态，就生成不同的 inverse。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>如果两个组件都在修改一个有顺序的中间件链，先插入谁、后插入谁会改变行为，那么它们就不能被视为独立。此时只能依赖明确的顺序，不能任意卸载。\u003C\u002Fp>\n\u003Cp>这也是论文一个很实用的分工：\u003C\u002Fp>\n\u003Cblockquote>\n \u003Cp>effect 处理可以交换的修改；coeffect 处理必须保留的依赖顺序。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Ch2>四、反应式共效果：依赖变化会驱动生命周期\u003C\u002Fh2>\n\u003Ch3>4.1 依赖表和依赖规格\u003C\u002Fh3>\n\u003Cp>论文把 coeffect context 建模成一个带类型的有限表：\u003C\u002Fp>\n\u003Cp>Σ = {\u003C\u002Fp>\n\u003Cp>key_1 -&gt; value_1,\u003C\u002Fp>\n\u003Cp>key_2 -&gt; value_2,\u003C\u002Fp>\n\u003Cp>...\u003C\u002Fp>\n\u003Cp>}\u003C\u002Fp>\n\u003Cp>每个 key 对应自己的值类型。组件则声明一个依赖规格：\u003C\u002Fp>\n\u003Cp>d = { memory, search, permission }\u003C\u002Fp>\n\u003Cp>组件只有在所有 key 都存在时才满足依赖：\u003C\u002Fp>\n\u003Cp>σ ⊨ d &lt;=&gt; d 中的每个 key 都在 σ 中\u003C\u002Fp>\n\u003Cp>这比组件先启动、再在运行时调用一个不存在的服务安全得多。\u003C\u002Fp>\n\u003Ch3>4.2 三类通知\u003C\u002Fh3>\n\u003Cp>每次上下文发生变化，运行时都会根据组件的依赖规格重新分类：\u003C\u002Fp>\n\u003Cp>activating 依赖从不满足变为满足\u003C\u002Fp>\n\u003Cp>deactivating 依赖从满足变为不满足\u003C\u002Fp>\n\u003Cp>neutral 满足状态没有改变\u003C\u002Fp>\n\u003Cp>例如：\u003C\u002Fp>\n\u003Cp>A 提供 weather\u003C\u002Fp>\n\u003Cp>B 声明依赖 weather\u003C\u002Fp>\n\u003Cp>当 A 激活并提供 \u003Ccode>weather\u003C\u002Fcode> 时，B 进入激活流程；当 A 开始撤销 \u003Ccode>weather\u003C\u002Fcode> 时，B 被通知并进入停用流程。\u003C\u002Fp>\n\u003Cp>组件不会因为依赖暂时缺失而立刻报错，而是保持 inactive，等待依赖重新出现。\u003C\u002Fp>\n\u003Ch3>4.3 Provider 必须先停止提供，再等待消费者清理\u003C\u002Fh3>\n\u003Cp>这里有一个容易忽略的顺序问题：B 的 teardown 可能仍然需要访问 A 提供的 \u003Ccode>weather\u003C\u002Fcode>。\u003C\u002Fp>\n\u003Cp>因此论文把 provider 的停用拆成两个阶段：\u003C\u002Fp>\n\u003Col>\n \u003Cli>provider 先标记为 \u003Ccode>UNLOADING\u003C\u002Fcode>，从新的依赖解析中消失；\u003C\u002Fli>\n \u003Cli>所有依赖它的 consumer 开始停用，但仍能读取自己已经提交的依赖视图；\u003C\u002Fli>\n \u003Cli>consumer 全部完成 teardown 后，provider 才执行自己的 inverse，真正删除绑定。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>Cordis 的实现对应为：\u003C\u002Fp>\n\u003Cp>mark provider as UNLOADING\u003C\u002Fp>\n\u003Cp>notify dependents\u003C\u002Fp>\n\u003Cp>await all(dependents become INACTIVE)\u003C\u002Fp>\n\u003Cp>await provider.dispose()\u003C\u002Fp>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fi1.wp.com\u002Fimg2024.cnblogs.com\u002Fblog\u002F139239\u002F202609\u002F139239-20260907090650815-1541308188.png?w=720&amp;quality=65&amp;strip=all\" alt=\"image\">\u003C\u002Fp>\n\u003Cp>\u003Cem>图 3：deactivation 的关键是保留 consumer 已提交的依赖视图，并等待依赖方完成清理。\u003C\u002Fem>\u003C\u002Fp>\n\u003Cp>这比单纯调用一组同步 \u003Ccode>deactivate()\u003C\u002Fcode> 更强，因为异步清理也被纳入生命周期协议。\u003C\u002Fp>\n\u003Ch2>五、统一 Context：effect 和 coeffect 放在同一个运行时实体里\u003C\u002Fh2>\n\u003Cp>论文最终把上下文写成递归类型：\u003C\u002Fp>\n\u003Cp>Γ∞ = μΓ. Γ × (Γ -&gt; Γ) × Σ\u003C\u002Fp>\n\u003Cp>可以把它理解成一个三元组：\u003C\u002Fp>\n\u003Col>\n \u003Cli>当前上下文状态；\u003C\u002Fli>\n \u003Cli>能恢复这一层 effect 的累加器；\u003C\u002Fli>\n \u003Cli>携带依赖信息的 coeffect context。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>递归结构支持嵌套上下文。一个父组件可以创建子组件，父级累积子级的 effect；卸载父级时，子级会被逐层撤退。\u003C\u002Fp>\n\u003Ch3>5.1 为什么不是要求物理状态完全相同\u003C\u002Fh3>\n\u003Cp>论文很诚实地指出，恢复通常不能保证物理表示逐字节相同。\u003C\u002Fp>\n\u003Cp>例如：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Ccode>free\u003C\u002Fcode> 释放内存后，堆分配器内部布局不会恢复到完全一样；\u003C\u002Fli>\n \u003Cli>删除一个生成的名字后，下次生成可能得到不同名字；\u003C\u002Fli>\n \u003Cli>文件描述符可能被操作系统重新分配。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>因此论文采用 \u003Cstrong>observational equivalence，观测等价\u003C\u002Fstrong>：只要组件通过公开 coeffect 操作观察不到差异，就视为恢复成功。\u003C\u002Fp>\n\u003Cp>这比“内存快照恢复”更符合实际系统，也说明了恢复保证的边界：它针对系统可观察行为，而不是所有底层物理细节。\u003C\u002Fp>\n\u003Ch2>六、从局部机制到全局组件演算\u003C\u002Fh2>\n\u003Cp>前面的定义只说明单个组件怎样可逆、怎样响应依赖。第 4 节把它们组合成动态组合演算。\u003C\u002Fp>\n\u003Ch3>6.1 Component、Fiber 和 Registry\u003C\u002Fh3>\n\u003Cp>一个组件由三部分构成：\u003C\u002Fp>\n\u003Cp>Component = (dependencies, provisions, effects)\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Ccode>dependencies\u003C\u002Fcode>：组件需要读取什么；\u003C\u002Fli>\n \u003Cli>\u003Ccode>provisions\u003C\u002Fcode>：组件可能提供什么；\u003C\u002Fli>\n \u003Cli>\u003Ccode>effects\u003C\u002Fcode>：组件激活时执行的可逆效果。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>组件的一次实例化叫作 \u003Cstrong>fiber\u003C\u002Fstrong>。fiber 除了携带组件本身，还记录：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>父 fiber；\u003C\u002Fli>\n \u003Cli>当前生命周期状态；\u003C\u002Fli>\n \u003Cli>自己的撤销累加器；\u003C\u002Fli>\n \u003Cli>激活时实际绑定到哪些 provider；\u003C\u002Fli>\n \u003Cli>是否已经被退休。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>这里“实际绑定到哪个 provider”很重要。即使两个 provider 提供完全相同的值，只要 provider 身份变了，consumer 也能检测到依赖解析发生了变化。\u003C\u002Fp>\n\u003Ch3>6.2 生命周期状态\u003C\u002Fh3>\n\u003Cp>论文从简单的两态模型逐步扩展为：\u003C\u002Fp>\n\u003Cp>INACTIVE\u003C\u002Fp>\n\u003Cp>-&gt; RELOADING\u003C\u002Fp>\n\u003Cp>-&gt; ACTIVE\u003C\u002Fp>\n\u003Cp>-&gt; UNLOADING\u003C\u002Fp>\n\u003Cp>-&gt; INACTIVE\u003C\u002Fp>\n\u003Cp>还要处理：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>Iteration\u003C\u002Fstrong>：一次激活由多个 effect 步骤组成；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Asynchrony\u003C\u002Fstrong>：某一步已经发出但尚未完成；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Failure\u003C\u002Fstrong>：某一步失败时，必须撤销已经完成的前置步骤；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Withdrawal\u003C\u002Fstrong>：provider 等待所有 consumer 清理后才真正撤销服务。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>异步场景引入了 inertia：一个 transition 一旦开始，就必须先完成当前在途操作，再处理新的 target 变化。否则会出现一个 effect 按旧依赖启动、却在新依赖环境中落地的状态。\u003C\u002Fp>\n\u003Ch3>6.3 论文给出的全局性质\u003C\u002Fh3>\n\u003Cp>在若干条件成立时，论文证明了：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>Preservation\u003C\u002Fstrong>：生命周期转换保持系统结构和不变量；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Temporal composability\u003C\u002Fstrong>：组件结束后，它的环境贡献被撤销；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Spatial composability\u003C\u002Fstrong>：依赖总是在 provider 之后激活、在 provider 撤销之前完成停用；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Progress\u003C\u002Fstrong>：没有违反条件的循环依赖时，系统不会因为撤销 guard 永久死锁；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Confluence\u003C\u002Fstrong>：只要组件效果满足独立性，最终静止状态与组件经历过的动态加载顺序无关，等价于按最终配置从头组装。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>这里必须强调“在若干条件成立时”。论文依赖的关键前提包括：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>provider-consumer 依赖图无环；\u003C\u002Fli>\n \u003Cli>共享 key 的操作满足适当的可交换性；\u003C\u002Fli>\n \u003Cli>组件最终确实安装它声明的 provision；\u003C\u002Fli>\n \u003Cli>动态注册不会无限制地产生 fiber；\u003C\u002Fli>\n \u003Cli>所有需要纳入保证的共享位置都被 reify 成 context\u002Fcoeffect。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2>七、Cordis：把理论落成一组 API\u003C\u002Fh2>\n\u003Cp>论文用 Cordis 实现了这套模型。它不是一个面向 Web、数据库或 UI 的应用框架，而是一个提供动态组合语义的 meta-framework。\u003C\u002Fp>\n\u003Cp>核心 API 可以概括为：\u003C\u002Fp>\n\u003Cp>ctx.effect(callback) \u002F\u002F 运行可逆 effect，返回 dispose\u003C\u002Fp>\n\u003Cp>ctx.set(key, value) \u002F\u002F 提供一个依赖\u003C\u002Fp>\n\u003Cp>ctx.get(key) \u002F\u002F 读取依赖\u003C\u002Fp>\n\u003Cp>ctx.use(component, config) \u002F\u002F 实例化组件\u003C\u002Fp>\n\u003Cp>ctx.isolate(key, realm) \u002F\u002F 改变依赖解析域\u003C\u002Fp>\n\u003Cp>ctx.intercept(key, meta) \u002F\u002F 为依赖访问附加策略元数据\u003C\u002Fp>\n\u003Cp>实现上，所有 context mutation 都经过 \u003Ccode>ctx.effect\u003C\u002Fcode>，因此 \u003Ccode>ctx.set\u003C\u002Fcode>、组件实例化和其他修改都自动进入同一个撤销体系。\u003C\u002Fp>\n\u003Ch3>7.1 声明式配置和增量 reconciliation\u003C\u002Fh3>\n\u003Cp>Cordis 还提供组件 loader。编排器维护一棵声明式配置树，每个 entry 描述：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>组件模块 URL；\u003C\u002Fli>\n \u003Cli>稳定 ID；\u003C\u002Fli>\n \u003Cli>isolate 和 intercept 配置；\u003C\u002Fli>\n \u003Cli>组件 config；\u003C\u002Fli>\n \u003Cli>是否 disabled。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>配置发生变化时，loader 不会粗暴地重启整个系统，而是根据字段选择最小更新：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Ccode>id\u003C\u002Fcode> 或 \u003Ccode>url\u003C\u002Fcode> 变化：重建 fiber；\u003C\u002Fli>\n \u003Cli>\u003Ccode>isolate\u003C\u002Fcode> 变化：重分配解析域；\u003C\u002Fli>\n \u003Cli>\u003Ccode>intercept\u003C\u002Fcode> 变化：原地更新；\u003C\u002Fli>\n \u003Cli>\u003Ccode>config\u003C\u002Fcode> 变化：交给组件做 diff；\u003C\u002Fli>\n \u003Cli>\u003Ccode>disabled\u003C\u002Fcode> 变化：卸载或重新激活。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3>7.2 Hot Module Replacement\u003C\u002Fh3>\n\u003Cp>HMR 也可以被看作组件级的可逆替换：\u003C\u002Fp>\n\u003Col>\n \u003Cli>找出受修改模块影响的组件；\u003C\u002Fli>\n \u003Cli>dispose 旧 fiber，恢复旧组件安装的 effect；\u003C\u002Fli>\n \u003Cli>清理模块缓存并导入新模块；\u003C\u002Fli>\n \u003Cli>用新模块创建新 fiber；\u003C\u002Fli>\n \u003Cli>如果任何一步失败，恢复缓存并用旧模块重建。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>因此 HMR 不需要每个组件额外声明一套接受边界，组件生命周期本身就是替换边界。\u003C\u002Fp>\n\u003Ch2>八、Koishi 案例说明了什么\u003C\u002Fh2>\n\u003Cp>Koishi 是一个基于 Cordis 的开源聊天机器人框架，论文提到其生态已经积累了 4000 多个社区插件，覆盖 IM 适配器、数据库驱动、管理控制台和用户功能。\u003C\u002Fp>\n\u003Cp>这个案例主要说明三点：\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>可表达性\u003C\u002Fstrong>：只靠通用 context 原语，就能承载一个完整生产系统；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>通用性\u003C\u002Fstrong>：同一套机制既能用于服务端机器人，也能用于浏览器 Web 控制台；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>开放生态中的依赖协调\u003C\u002Fstrong>：不同作者编写的插件可以通过 coeffect 连接，而不需要共享内部实现。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>Koishi 中的插件可以被单独禁用，插件效果会在原地撤销；开发时，HMR 可以重新应用修改过的插件，同时保留系统其他部分的连接和状态。\u003C\u002Fp>\n\u003Cp>不过，论文也承认这不是一个受控的性能实验：证据来自单一生态和单一 TypeScript 实现，尚没有和传统插件框架做系统性的开销或开发效率对比。\u003C\u002Fp>\n\u003Ch2>九、把这套模型放进 Agent harness\u003C\u002Fh2>\n\u003Cp>可以把一个 Agent harness 抽象成下面的依赖图：\u003C\u002Fp>\n\u003Cp>memory-provider ─────┐\u003C\u002Fp>\n\u003Cp>search-provider ─────┼──&gt; planner \u002F executor\u003C\u002Fp>\n\u003Cp>permission-provider ─┘\u003C\u002Fp>\n\u003Cp>│\u003C\u002Fp>\n\u003Cp>└──&gt; tool registry \u002F sub-agent coordinator\u003C\u002Fp>\n\u003Cp>在这个模型里：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>工具注册、事件监听、会话句柄是 revertible effects；\u003C\u002Fli>\n \u003Cli>memory、search、permission 是 coeffects；\u003C\u002Fli>\n \u003Cli>planner 声明自己需要哪些 coeffects；\u003C\u002Fli>\n \u003Cli>provider 变化时，只有受影响的 Agent 组件进入 reload\u002Funload；\u003C\u002Fli>\n \u003Cli>permission 还可以通过 interception 附加只读、路径限制或租户信息；\u003C\u002Fli>\n \u003Cli>service broker 可以让多个工具 provider 共存，并在后台做负载均衡和滚动替换。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>一个更具体的例子是切换向量数据库：\u003C\u002Fp>\n\u003Col>\n \u003Cli>新数据库 provider 先加载并完成健康检查；\u003C\u002Fli>\n \u003Cli>依赖 \u003Ccode>memory\u003C\u002Fcode> 的组件解析到新 provider 后重新激活；\u003C\u002Fli>\n \u003Cli>旧 provider 进入 \u003Ccode>UNLOADING\u003C\u002Fcode>，停止接受新绑定；\u003C\u002Fli>\n \u003Cli>依赖旧 provider 的 teardown 完成；\u003C\u002Fli>\n \u003Cli>旧 provider 撤销连接、注册表和事件监听。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>整个过程不需要重启 Agent 进程，也不需要每个 consumer 自己实现一套“发现 provider 变化”的逻辑。\u003C\u002Fp>\n\u003Ch2>十、这套方案的边界\u003C\u002Fh2>\n\u003Ch3>10.1 外部副作用不一定真正可逆\u003C\u002Fh3>\n\u003Cp>论文把系统边界定义得很清楚：只有系统能独占修改、并且能恢复的资源，才属于 context 内部。\u003C\u002Fp>\n\u003Cp>例如：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>打开文件并记录文件描述符，通常可以跟踪；\u003C\u002Fli>\n \u003Cli>创建私有临时文件并删除，通常可以补偿；\u003C\u002Fli>\n \u003Cli>向外部邮箱发出的邮件，无法真正撤回；\u003C\u002Fli>\n \u003Cli>已经发出的支付请求或网络消息，不能靠普通 inverse 消除。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>对于越过边界的 emission，系统只能：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>延迟输出，直到内部状态确定提交；\u003C\u002Fli>\n \u003Cli>提供业务层 compensation，例如退款、撤销订单或发送更正事件。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>所以“完整恢复”不是对整个宇宙状态的承诺，而是对系统边界内、可被重新表示的行为的承诺。\u003C\u002Fp>\n\u003Ch3>10.2 依赖 key 还不够解决版本问题\u003C\u002Fh3>\n\u003Cp>形式化模型主要通过 key 身份连接 provider 和 consumer。这会遇到：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>provider 升级后接口漂移；\u003C\u002Fli>\n \u003Cli>不同包使用同名 key 表示不同接口；\u003C\u002Fli>\n \u003Cli>独立构建的组件缺少结构兼容性检查。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Cordis 当前更多依赖包管理器的 peer dependency。更彻底的方案可能是命名空间 key、版本约束或结构兼容性检查，但语言无关的结构兼容性并不容易定义。\u003C\u002Fp>\n\u003Ch3>10.3 依赖环和组件粒度\u003C\u002Fh3>\n\u003Cp>如果 A 依赖 B、B 又依赖 A，两者的 satisfaction predicate 都无法成立，最终会永久 inactive。工程上需要拆分出更细的 integration component，把双向交互拆成两个单向绑定。\u003C\u002Fp>\n\u003Cp>代价是组件数量和配置复杂度上升。论文建议用组件打包、约定式 wiring 和脚手架工具降低认知成本。\u003C\u002Fp>\n\u003Ch3>10.4 这不是完整的安全沙箱\u003C\u002Fh3>\n\u003Cp>依赖声明可以限制组件通过 context 能访问什么，但恶意代码如果直接拿到宿主运行时对象，语言级检查就可能被绕开。\u003C\u002Fp>\n\u003Cp>真正的不可信组件仍然需要隔离进程、独立运行时、WebAssembly 或容器；context 机制负责控制沙箱边界上的依赖桥接。\u003C\u002Fp>\n\u003Ch2>十一、我的总结\u003C\u002Fh2>\n\u003Cp>这篇论文最重要的贡献，不是提出一种新的 Agent 推理算法，而是提出了一种可以支撑动态 Agent 系统的运行时组织方式：\u003C\u002Fp>\n\u003Cp>Effect = 我对环境做了什么，以及如何撤销\u003C\u002Fp>\n\u003Cp>Coeffect = 我依赖环境提供什么，以及变化时如何响应\u003C\u002Fp>\n\u003Cp>Context = 让两者在同一个运行时实体中协作\u003C\u002Fp>\n\u003Cp>Component = 依赖 + 提供 + 可逆效果\u003C\u002Fp>\n\u003Cp>如果把 Agent harness 看作一个可以持续演化的程序，那么它需要的不只是“加载工具”的能力，还需要：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>工具卸载时不泄漏资源；\u003C\u002Fli>\n \u003Cli>provider 替换时不让 consumer 读到混合状态；\u003C\u002Fli>\n \u003Cli>异步 teardown 能被等待；\u003C\u002Fli>\n \u003Cli>部分失败能撤销已经完成的步骤；\u003C\u002Fli>\n \u003Cli>最终状态与从头组装的配置一致。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>这正是论文所谓的 \u003Cstrong>spatiotemporal composability\u003C\u002Fstrong>：\u003C\u002Fp>\n\u003Cblockquote>\n \u003Cp>在时间上，组件可以加入、离开并恢复自己的影响；在空间上，组件可以声明依赖并随依赖拓扑变化自动重组。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>论文最后也把自演化 Agent harness 列为下一步验证方向。真正有意思的实验是：让 Agent 自动生成和替换自己的工具、记忆或权限组件，然后观察 Cordis 这类机制能否在高频拓扑变化中维持状态恢复和依赖协调。\u003C\u002Fp>\n\u003Ch2>参考\u003C\u002Fh2>\n\u003Cul>\n \u003Cli>Yifan Shi, Wei Zhang, Tianyi Cui, \u003Cem>A Programming Paradigm for Spatiotemporal Composability\u003C\u002Fem>.\u003C\u002Fli>\n\u003C\u002Ful>","读《A Programming Paradigm for Spatiotemporal Composability》 今天的 Agent 已经不只是一个“模型 + Prompt”。一个可长期运行的 Agent harness，通常还要管理工具、记忆、权限、沙箱、会话状态、子 Agent 和任务编排。更进一步，未来的 Agent 可能会在服务请求的同时修改自己的工具链或运行时模块。 这会立刻带来一个问题： 如果组件可以在运行时被加载、卸载、替换和重配置，系统怎样保证状态不泄漏、依赖不失效、正在执行的任务不被无序打断？ 论文《A Programming Paradigm for Spatiotemporal Composability》给出的答案，是把动态组合拆成两个维度，并把传统编程语言里的 effect 和 coeffect 提升为运行时机制。 本文不复述论文中的全部证明，而是从工程角度解释它的核心模型、实现方式，以及它对 Agent harness 的启发。 一、动态组合缺的不是加载，而是可控的卸载 传统软件组合大多在编译期完成：函数调用、模块导入、类继承都形成相对固定的结构。插件系统和自演化 Agent 则要求组件在运行时到来和离开。 论文认为，动态组合至少有两个正交维度。 很多系统采用粗粒度的替代方案：出问题就重启进程，服务依赖交给容器编排。这样当然能恢复，但代价是丢失进程内状态、打断进行中的请求，并把本来可以是进程内调用的依赖变成网络调用。 对自演化 Agent 来说，频繁重启尤其昂贵。Agent 可能正处于一个长任务中，拥有尚未持久化的上下文、缓存和连接；如果一次自修改必须让整个 harness 重启，修改本身还可能破坏负责恢复系统的代码。 图 1：动态组合同时关注组件生命周期和组件依赖拓扑。 二、从静态 effect\u002Fcoeffect 到运行时上下文 经典 effect system 描述“一个计算会对环境做什么”，coeffect system 描述“一个计算要求环境提供什么”。论文的关键动作不是再加一层类型注解，而是把上下文本身变成运行时的一等实体。 可以先把上下文 Γ 想成一个共享运行时环境：工具注册表、事件监听器、权限、连接、缓存和组件提供的服务都可以通过它访问。 论文的统一方向是： effect 负责修改上下文，并且必须能撤销； coeffect 负责声明依赖，并且在依赖变化时触发响应； 所有组件和环境的交互都经过同一个 context。 这样，加载和卸载不再是两个容易漂移的生命周期回调，而是同一套上下文变换的正向和逆向。 三、可逆效果：每次修改都带着 undo 3.1 基本模型 普通的环境变换可以写成： f : Γ -> Γ 论文要求一个 effect 同时返回新上下文和撤销函数： e : Γ -> Γ × (Γ -> Γ) 也就是： e(oldContext) = (newContext, undo) 例如，一个组件注册天气工具时，操作不是简单地执行： set(\"weather\", weatherTool) 而是同时提供： undo(context) = delete(\"weather\") 组件卸载时，运行时执行当时保存的 undo，而不是依赖开发者另外写一个可能遗漏清理动作的 deactivate。 3.2 运行时怎样累计撤销操作 论文用 effect context 保存两部分状态： ∂Γ = Γ × (Γ -> Γ) 第二项是撤销累加器。假设组件依次执行： A：注册工具 -> undo_A B：注册事件监听器 -> undo_B 累加器会保存组合后的撤销函数，卸载时按逆序执行： undo_B undo_A 这和资源管理中的 LIFO 原则一致：后申请的资源先释放。 如果用伪代码表达，核心大致是： async function effect(callback) { const inverses = [] for await (const inverse of callback()) { inverses.unshift(inverse) \u002F\u002F 后发生的效果先撤销 } return async function dispose() { for (const inverse of inverses) { await inverse() } } } 图 2：effect 不只修改上下文，还把对应的撤销操作交给运行时追踪。 论文中的实现 ctx.effect 还支持异步迭代：每完成一个 effect，就把它的 inverse 放入累加器；如果组件在中途被要求停止，只撤销已经完成的部分。 3.3 为什么 inverse 必须在运行时产生 很多操作没有一个对所有状态都适用的固定逆函数。 例如： 分配资源后，逆操作需要知道本次分配得到的句柄； 建立连接后，逆操作需要关闭这条具体连接； 注册组件后，逆操作需要撤退刚刚生成的 fiber； 创建临时文件后，逆操作需要删除这一个文件。 所以论文允许 effect 在实际执行时，根据当前状态生成自己的 inverse。 3.4 可逆不等于可以随便删除 单个组件按自己的逆序撤销通常没有问题。多个组件交错修改同一环境时，就必须讨论 independence，效果独立性。 论文要求两个独立 effect 满足更强的条件： 双方的正向操作和逆操作可以交换； 一个 effect 不会因为另一个 effect 改变状态，就生成不同的 inverse。 如果两个组件都在修改一个有顺序的中间件链，先插入谁、后插入谁会改变行为，那么它们就不能被视为独立。此时只能依赖明确的顺序，不能任意卸载。 这也是论文一个很实用的分工： effect 处理可以交换的修改；coeffect 处理必须保留的依赖顺序。 四、反应式共效果：依赖变化会驱动生命周期 4.1 依赖表和依赖规格 论文把 coeffect context 建模成一个带类型的有限表： Σ = { key_1 -> value_1, key_2 -> value_2, ... } 每个 key 对应自己的值类型。组件则声明一个依赖规格： d = { memory, search, permission } 组件只有在所有 key 都存在时才满足依赖： σ ⊨ d \u003C=> d 中的每个 key 都在 σ 中 这比组件先启动、再在运行时调用一个不存在的服务安全得多。 4.2 三类通知 每次上下文发生变化，运行时都会根据组件的依赖规格重新分类： activating 依赖从不满足变为满足 deactivating 依赖从满足变为不满足 neutral 满足状态没有改变 例如： A 提供 weather B 声明依赖 weather 当 A 激活并提供 weather 时，B 进入激活流程；当 A 开始撤销 weather 时，B 被通知并进入停用流程。 组件不会因为依赖暂时缺失而立刻报错，而是保持 inactive，等待依赖重新出现。 4.3 Provider 必须先停止提供，再等待消费者清理 这里有一个容易忽略的顺序问题：B 的 teardown 可能仍然需要访问 A 提供的 weather。 因此论文把 provider 的停用拆成两个阶段： provider 先标记为 UNLOADING，从新的依赖解析中消失； 所有依赖它的 consumer 开始停用，但仍能读取自己已经提交的依赖视图； consumer 全部完成 teardown 后，provider 才执行自己的 inverse，真正删除绑定。 Cordis 的实现对应为： mark provider as UNLOADING notify dependents await all(dependents become INACTIVE) await provider.dispose() 图 3：deactivation 的关键是保留 consumer 已提交的依赖视图，并等待依赖方完成清理。 这比单纯调用一组同步 deactivate() 更强，因为异步清理也被纳入生命周期协议。 五、统一 Context：effect 和 coeffect 放在同一个运行时实体里 论文最终把上下文写成递归类型： Γ∞ = μΓ. Γ × (Γ -> Γ) × Σ 可以把它理解成一个三元组： 当前上下文状态； 能恢复这一层 effect 的累加器； 携带依赖信息的 coeffect context。 递归结构支持嵌套上下文。一个父组件可以创建子组件，父级累积子级的 effect；卸载父级时，子级会被逐层撤退。 5.1 为什么不是要求物理状态完全相同 论文很诚实地指出，恢复通常不能保证物理表示逐字节相同。 例如： free 释放内存后，堆分配器内部布局不会恢复到完全一样； 删除一个生成的名字后，下次生成可能得到不同名字； 文件描述符可能被操作系统重新分配。 因此论文采用 observational equivalence，观测等价：只要组件通过公开 coeffect 操作观察不到差异，就视为恢复成功。 这比“内存快照恢复”更符合实际系统，也说明了恢复保证的边界：它针对系统可观察行为，而不是所有底层物理细节。 六、从局部机制到全局组件演算 前面的定义只说明单个组件怎样可逆、怎样响应依赖。第 4 节把它们组合成动态组合演算。 6.1 Component、Fiber 和 Registry 一个组件由三部分构成： Component = (dependencies, provisions, effects) dependencies：组件需要读取什么； provisions：组件可能提供什么； effects：组件激活时执行的可逆效果。 组件的一次实例化叫作 fiber。fiber 除了携带组件本身，还记录： 父 fiber； 当前生命周期状态； 自己的撤销累加器； 激活时实际绑定到哪些 provider； 是否已经被退休。 这里“实际绑定到哪个 provider”很重要。即使两个 provider 提供完全相同的值，只要 provider 身份变了，consumer 也能检测到依赖解析发生了变化。 6.2 生命周期状态 论文从简单的两态模型逐步扩展为： INACTIVE -> RELOADING -> ACTIVE -> UNLOADING -> INACTIVE 还要处理： Iteration：一次激活由多个 effect 步骤组成； Asynchrony：某一步已经发出但尚未完成； Failure：某一步失败时，必须撤销已经完成的前置步骤； Withdrawal：provider 等待所有 consumer 清理后才真正撤销服务。 异步场景引入了 inertia：一个 transition 一旦开始，就必须先完成当前在途操作，再处理新的 target 变化。否则会出现一个 effect 按旧依赖启动、却在新依赖环境中落地的状态。 6.3 论文给出的全局性质 在若干条件成立时，论文证明了： Preservation：生命周期转换保持系统结构和不变量； Temporal composability：组件结束后，它的环境贡献被撤销； Spatial composability：依赖总是在 provider 之后激活、在 provider 撤销之前完成停用； Progress：没有违反条件的循环依赖时，系统不会因为撤销 guard 永久死锁； Confluence：只要组件效果满足独立性，最终静止状态与组件经历过的动态加载顺序无关，等价于按最终配置从头组装。 这里必须强调“在若干条件成立时”。论文依赖的关键前提包括： provider-consumer 依赖图无环； 共享 key 的操作满足适当的可交换性； 组件最终确实安装它声明的 provision； 动态注册不会无限制地产生 fiber； 所有需要纳入保证的共享位置都被 reify 成 context\u002Fcoeffect。 七、Cordis：把理论落成一组 API 论文用 Cordis 实现了这套模型。它不是一个面向 Web、数据库或 UI 的应用框架，而是一个提供动态组合语义的 meta-framework。 核心 API 可以概括为： ctx.effect(callback) \u002F\u002F 运行可逆 effect，返回 dispose ctx.set(key, value) \u002F\u002F 提供一个依赖 ctx.get(key) \u002F\u002F 读取依赖 ctx.use(component, config) \u002F\u002F 实例化组件 ctx.isolate(key, realm) \u002F\u002F 改变依赖解析域 ctx.intercept(key, meta) \u002F\u002F 为依赖访问附加策略元数据 实现上，所有 context mutation 都经过 ctx.effect，因此 ctx.set、组件实例化和其他修改都自动进入同一个撤销体系。 7.1 声明式配置和增量 reconciliation Cordis 还提供组件 loader。编排器维护一棵声明式配置树，每个 entry 描述： 组件模块 URL； 稳定 ID； isolate 和 intercept 配置； 组件 config； 是否 disabled。 配置发生变化时，loader 不会粗暴地重启整个系统，而是根据字段选择最小更新： id 或 url 变化：重建 fiber； isolate 变化：重分配解析域； intercept 变化：原地更新； config 变化：交给组件做 diff； disabled 变化：卸载或重新激活。 7.2 Hot Module Replacement HMR 也可以被看作组件级的可逆替换： 找出受修改模块影响的组件； dispose 旧 fiber，恢复旧组件安装的 effect； 清理模块缓存并导入新模块； 用新模块创建新 fiber； 如果任何一步失败，恢复缓存并用旧模块重建。 因此 HMR 不需要每个组件额外声明一套接受边界，组件生命周期本身就是替换边界。 八、Koishi 案例说明了什么 Koishi 是一个基于 Cordis 的开源聊天机器人框架，论文提到其生态已经积累了 4000 多个社区插件，覆盖 IM 适配器、数据库驱动、管理控制台和用户功能。 这个案例主要说明三点： 可表达性：只靠通用 context 原语，就能承载一个完整生产系统； 通用性：同一套机制既能用于服务端机器人，也能用于浏览器 Web 控制台； 开放生态中的依赖协调：不同作者编写的插件可以通过 coeffect 连接，而不需要共享内部实现。 Koishi 中的插件可以被单独禁用，插件效果会在原地撤销；开发时，HMR 可以重新应用修改过的插件，同时保留系统其他部分的连接和状态。 不过，论文也承认这不是一个受控的性能实验：证据来自单一生态和单一 TypeScript 实现，尚没有和传统插件框架做系统性的开销或开发效率对比。 九、把这套模型放进 Agent harness 可以把一个 Agent harness 抽象成下面的依赖图： memory-provider ─────┐ search-provider ─────┼──> planner \u002F executor permission-provider ─┘ │ └──> tool registry \u002F sub-agent coordinator 在这个模型里： 工具注册、事件监听、会话句柄是 revertible effects； memory、search、permission 是 coeffects； planner 声明自己需要哪些 coeffects； provider 变化时，只有受影响的 Agent 组件进入 reload\u002Funload； permission 还可以通过 interception 附加只读、路径限制或租户信息； service broker 可以让多个工具 provider 共存，并在后台做负载均衡和滚动替换。 一个更具体的例子是切换向量数据库： 新数据库 provider 先加载并完成健康检查； 依赖 memory 的组件解析到新 provider 后重新激活； 旧 provider 进入 UNLOADING，停止接受新绑定； 依赖旧 provider 的 teardown 完成； 旧 provider 撤销连接、注册表和事件监听。 整个过程不需要重启 Agent 进程，也不需要每个 consumer 自己实现一套“发现 provider 变化”的逻辑。 十、这套方案的边界 10.1 外部副作用不一定真正可逆 论文把系统边界定义得很清楚：只有系统能独占修改、并且能恢复的资源，才属于 context 内部。 例如： 打开文件并记录文件描述符，通常可以跟踪； 创建私有临时文件并删除，通常可以补偿； 向外部邮箱发出的邮件，无法真正撤回； 已经发出的支付请求或网络消息，不能靠普通 inverse 消除。 对于越过边界的 emission，系统只能： 延迟输出，直到内部状态确定提交； 提供业务层 compensation，例如退款、撤销订单或发送更正事件。 所以“完整恢复”不是对整个宇宙状态的承诺，而是对系统边界内、可被重新表示的行为的承诺。 10.2 依赖 key 还不够解决版本问题 形式化模型主要通过 key 身份连接 provider 和 consumer。这会遇到： provider 升级后接口漂移； 不同包使用同名 key 表示不同接口； 独立构建的组件缺少结构兼容性检查。 Cordis 当前更多依赖包管理器的 peer dependency。更彻底的方案可能是命名空间 key、版本约束或结构兼容性检查，但语言无关的结构兼容性并不容易定义。 10.3 依赖环和组件粒度 如果 A 依赖 B、B 又依赖 A，两者的 satisfaction predicate 都无法成立，最终会永久 inactive。工程上需要拆分出更细的 integration component，把双向交互拆成两个单向绑定。 代价是组件数量和配置复杂度上升。论文建议用组件打包、约定式 wiring 和脚手架工具降低认知成本。 10.4 这不是完整的安全沙箱 依赖声明可以限制组件通过 context 能访问什么，但恶意代码如果直接拿到宿主运行时对象，语言级检查就可能被绕开。 真正的不可信组件仍然需要隔离进程、独立运行时、WebAssembly 或容器；context 机制负责控制沙箱边界上的依赖桥接。 十一、我的总结 这篇论文最重要的贡献，不是提出一种新的 Agent 推理算法，而是提出了一种可以支撑动态 Agent 系统的运行时组织方式： Effect = 我对环境做了什么，以及如何撤销 Coeffect = 我依赖环境提供什么，以及变化时如何响应 Context = 让两者在同一个运行时实体中协作 Component = 依赖 + 提供 + 可逆效果 如果把 Agent harness 看作一个可以持续演化的程序，那么它需要的不只是“加载工具”的能力，还需要： 工具卸载时不泄漏资源； provider 替换时不让 consumer 读到混合状态； 异步 teardown 能被等待； 部分失败能撤销已经完成的步骤； 最终状态与从头组装的配置一致。 这正是论文所谓的 spatiotemporal composability： 在时间上，组件可以加入、离开并恢复自己的影响；在空间上，组件可以声明依赖并随依赖拓扑变化自动重组。 论文最后也把自演化 Agent harness 列为下一步验证方向。真正有意思的实验是：让 Agent 自动生成和替换自己的工具、记忆或权限组件，然后观察 Cordis 这类机制能否在高频拓扑变化中维持状态恢复和依赖协调。 参考 Yifan Shi, Wei Zhang, Tianyi Cui, A Programming Paradigm for Spatiotemporal Composability.",7565,{"id":6,"kind":7,"title":11,"summary":13,"image":14,"href":16,"meta":18,"badge":10,"author":12,"stats":-1,"accent":36,"coverRatio":37,"tags":38},"#2563eb","16 \u002F 10",[7,8],{"targetType":8,"targetId":9,"likedByMe":40,"likeCount":41,"commentCount":41,"contentLikeCount":41,"contentCommentCount":41,"sourceLikeCount":41,"sourceCommentCount":41},false,0,[43,52,58,64,73,80,86,93],{"id":44,"kind":7,"title":45,"summary":46,"image":47,"href":48,"meta":49,"badge":10,"author":12,"stats":-1,"accent":36,"coverRatio":37,"tags":50},"NEWS_ARTICLE:927","HelloCrab - 短视频开源爬虫，仅供学习参考","HelloCrab 基于 Avalonia、Playwright、AI与 FFmpeg 的跨平台桌面采集器，支持9大平台，以及 Android、iOS、Browser 远程控制端。 Made By ChatGPT &amp; Vincent with ❤ 平台 是否接入 哔哩哔哩 ✅ 抖音 ✅ 快手","https:\u002F\u002Fwww.cnblogs.com\u002Fhupo376787\u002Fp\u002FScreenshot\u002FWindows.jpg","\u002Fnews\u002F927","2026 · 软件开发",[51],"软件开发",{"id":53,"kind":7,"title":54,"summary":55,"image":15,"href":56,"meta":49,"badge":10,"author":12,"stats":-1,"accent":36,"coverRatio":37,"tags":57},"NEWS_ARTICLE:929",".NET 异常处理的\"暗门\"：代码里写满 catch，你依然能抓住它——从一个 AI Agent 运行时的源码说起","一个健壮的系统，必然到处都是有意的 catch；异常被消化不等于问题不存在。 观测与韧性，是一个硬币的两面——降级逻辑保证系统不崩，FirstChance 保证你能看见它为什么降级。","\u002Fnews\u002F929",[51],{"id":59,"kind":7,"title":60,"summary":61,"image":15,"href":62,"meta":18,"badge":10,"author":12,"stats":-1,"accent":36,"coverRatio":37,"tags":63},"NEWS_ARTICLE:928","架构师化繁为简，执行者化简为繁","新手改三天，你改三行——反而是你显得更不重要。因为化繁为简做得越纯熟，产出看起来越小。这篇聊聊两种能力的辩证关系，以及为什么'看不见'的那部分工作，恰恰是最难的部分。","\u002Fnews\u002F928",[7,8],{"id":65,"kind":7,"title":66,"summary":67,"image":68,"href":69,"meta":70,"badge":10,"author":12,"stats":-1,"accent":36,"coverRatio":37,"tags":71},"NEWS_ARTICLE:930","基于 vLLM+Nginx 构建负载均衡推理集群","企业内部私有环境部署大模型推理集群时，很容易遇到流量调度混乱、节点负载失衡、会话上下文丢失、接口缺少鉴权防护等一系列问题，单 vLLM 推理节点难以支撑并发请求。本文基于 Ubuntu 22.04 系统环境，搭建 Nginx + vLLM-Semantic-Router + vLLM-Router","https:\u002F\u002Fimg2024.cnblogs.com\u002Fblog\u002F1379525\u002F202609\u002F1379525-20260910165534385-2022408098.png","\u002Fnews\u002F930","2026 · 人工智能",[72],"人工智能",{"id":74,"kind":7,"title":75,"summary":76,"image":77,"href":78,"meta":70,"badge":10,"author":12,"stats":-1,"accent":36,"coverRatio":37,"tags":79},"NEWS_ARTICLE:932","2026年AI编程工具大全，33个主流工具一次看懂","事情是这样的，前两天看到一张图，是某个社区官网的「支持的工具」清单，我数了数，整整31个AI编程工具。 两年前这份清单撑死5个，现在直接31个，而且我居然每一个都认识。。。 干脆整理成一篇，顺手把最近字节的TraeWork和豆包工作也补了进来，凑成33个。 今天给大家推荐一遍，每个工具说说它是干什么","https:\u002F\u002Fimage.kjdaohang.com\u002Fimg\u002F20260909210838518.png","\u002Fnews\u002F932",[72],{"id":81,"kind":7,"title":82,"summary":83,"image":15,"href":84,"meta":18,"badge":10,"author":12,"stats":-1,"accent":36,"coverRatio":37,"tags":85},"NEWS_ARTICLE:931","SH 中文化样例数据使用手册","在数据库演示与 PoC 场景中，Oracle 自带的 SH 示例模式虽然经典，但英文维度数据往往让国内演示效果打折扣。笔者整理了一套方案：保留 SH 标准英文对象名，同时装载中文化维度数据，并按参数生成可复现的销售历史数据，方便个人测试与概念验证。 01 | 环境准备 使用前请确认满足以下条件： O","\u002Fnews\u002F931",[7,8],{"id":87,"kind":7,"title":88,"summary":89,"image":90,"href":91,"meta":49,"badge":10,"author":12,"stats":-1,"accent":36,"coverRatio":37,"tags":92},"NEWS_ARTICLE:933","写给 C++ 工程师的 OpenClaw.NET 上手指南：用你熟悉的 C++ 思维，跑起一个生产级 AI Agent","它像一个「基于 boost.asio + REST 端点的常驻服务」——对外是 HTTP \u002F WebSocket \u002F 各 IM 的 webhook，对内跑着一个能调工具、读写记忆、跨渠道对话的 AI Agent——只不过协程不用你手写 promise_type，内存不用你管 new\u002Fdelete。","https:\u002F\u002Fimg2024.cnblogs.com\u002Fblog\u002F510\u002F202609\u002F510-20260905070811424-1837144874.jpg","\u002Fnews\u002F933",[51],{"id":94,"kind":7,"title":95,"summary":96,"image":97,"href":98,"meta":70,"badge":10,"author":12,"stats":-1,"accent":36,"coverRatio":37,"tags":99},"NEWS_ARTICLE:935","[Agent Memory \u002F 强化学习] MemPO源码学习笔记 ---（1）--- 总体","[Agent Memory \u002F 强化学习] MemPO源码学习笔记 （1） 总体 目录[Agent Memory \u002F 强化学习] MemPO源码学习笔记 （1） 总体0x00 概要0x01 基础 &amp; 背景1.1 用RL训练记忆系统的要点1.2 主要难点1.3 主要思路1.4 RL训练方案1.","https:\u002F\u002Fimg2024.cnblogs.com\u002Fblog\u002F1850883\u002F202609\u002F1850883-20260906190854637-986949885.jpg","\u002Fnews\u002F935",[72]]