Reactive Coeffects:空间维度的依赖响应
论文用 Reactive Coeffects 解决 spatial composability——组件依赖出现、消失或更换后,系统能否自动调整生命周期和依赖连接。核心思想:组件声明自己需要什么(coeffect specification),运行时在上下文变化时按规格通知组件。
Coeffect Context 与 set 即 effect
【论文原文结论 §3.2.1】Coeffect context Σ ≔ (k : K) ⇀ 𝒱_k 是依赖键到类型化值的有限偏函数。get 和 set 是两个核心操作(Def 23),且 set(k, v) 本身就是一个 effect——coeffect 操作就是 effect,coeffect provision 自动继承可跟踪性和可恢复性。这是 Revertible Effects 与 Reactive Coeffects 的协同点:声明一个依赖(provide)的同时就在做一个可撤销的 effect。
Coeffect Specification 与三态变化
【论文原文结论 §3.2.2】Coeffect specification d ⊆ K 是组件声明的依赖键集合;satisfaction predicate σ ⊧ d ≔ ∀k ∈ d. k ∈ dom(σ)。任何把 σ 变为 σ' 的 effect 可按 d 分类为 activating / deactivating / neutral(Def 26):
- activating:变化前 σ 不满足 d,变化后 σ' 满足——组件应被激活;
- deactivating:变化前满足,变化后不满足——组件应被卸载;
- neutral:变化前后都满足或都不满足——组件无需切换状态(但依赖实例可能已换)。
数据库 A + RAG B 完整案例
具体案例(贯穿全文的标准例子):
插件 A(数据库插件):set('database', dbService) 提供依赖
插件 B(RAG 插件):声明 d_B = {'database'} 需要依赖- A 出现:A 通过
ctx.set('database', dbService)提供数据库服务;σ 中'database'键出现。B 声明的d_B = {'database'}由不满足变为满足——activating,B 被通知并自动激活,从ctx.get('database')拿到 dbService。 - A 消失或被替换:A 的 fiber 进入 Unloading,
'database'键从 σ 移除。B 的d_B由满足变为不满足——deactivating,B 被通知并自动卸载(其自身的 inverse accumulator 被应用,清理 B 占用的资源)。 - 新 A 就绪:新版数据库插件 A' 通过
ctx.set('database', newDbService)提供。B 的d_B再次由不满足变为满足——activating,B 使用新依赖重新激活,拿到 newDbService。
整个过程中,B 的代码不需要写"如果 A 不在了就……"的主动检测——运行时按 coeffect 规格自动通知。
Committed View vs Target View
【论文原文结论 §4.1, §4.2】Cordis 实现中,有两份关键的依赖解析视图:
- Committed View(ω):当前已提交的依赖解析,记录 fiber 名到 provider 的映射。组件在整个 episode 中读到的是这份视图,保证一个组件的本次生命周期内依赖不变(参见 Ordering 定理)。
- Target View:期望 fiber 应处于的依赖解析状态,由
refresh在配置变化时重算。当 target 与 committed 不一致时,触发生命周期转换(激活、卸载或重载)。
【论文原文结论 §4.2】关键细节:target view 记录的是 provider identity(fiber uid),而不是 value 是否相等。论文明确指出:"makes the comparison usable, since a different fiber providing an equal value would otherwise compare equal"——如果只比 value,提供相同 value 的不同 fiber 会被误判为"依赖没变",从而不触发重新激活,导致 consumer 用着旧 fiber 的快照而不知 provider 已换。fiber.target 在 Cordis 实现中存的是依赖解析结果的 uid 摘要(§5.1.3)。
Isolation 与 Interception
【论文原文结论 §3.2.3】两个正交的派生机制:
- Isolation:通过 isolation realm table
ρ : K ⇀ R,同一 key k 在不同 context 解析到不同 realm r,从而绑定到不同值。ctx.isolate(k, r)派生一个新 context,仅覆盖 ρ 在 k 处的映射(derived realization,不修改父表)。 - Interception:在 coeffect 上挂元数据
ι : K → ℙ_k,provider 函数接收 metadata 并据此调整行为。ctx.intercept(k, ν)派生 context 合并 ν 到 k 处的元数据。
两者区别:Isolation 替换"key 解析到什么 binding"(换实例),Interception 修改"binding 被使用的方式"(加约束,不换实例)。Isolation 是 derived realization(不需要 inverse,丢弃 child context 即可),而 set 是 in-place realization(需要 inverse 来撤销)。
场景对照
| 场景 | 用 Isolation | 用 Interception |
|---|---|---|
| 多租户 / 不同 Workspace | 同一 key 'db' 各租户解析为各自实例 | — |
| 子 Agent 独立环境 | 同一 'filesystem' 各子 Agent 独立实例 | — |
| 测试替身 | 用 mock realm 覆盖 | — |
| 文件系统只读权限 | — | 挂 readonly metadata |
| 不同插件用不同模型 | 'model' key 各自隔离 | — |
| 社区插件严格访问策略 | — | 挂 capability 限制 metadata |
Reactive Coeffects 解决了"依赖变化的感知",但组件实例自己的加载/卸载/重载过程仍需一个状态机来管。见 生命周期与卸载顺序。