Files
deepseek-harness/website/zh-CN/design/revertible-effects.md
T
2026-07-09 16:07:58 +08:00

4.5 KiB
Raw Blame History

可逆作用

可逆作用 (Revertible Effects) 是 Cordis 实现时间可组合性的核心机制。

  • 在单子作用的基础上增加可逆性约束
  • 提供面向长时运行程序的作用系统
  • 确保程序可以在插件粒度上回到任意状态

副作用的封装

现实中的程序需要与各种副作用打交道。假设一个不纯函数:


f_\text{impure}: \text{X}\to\text{Y}

我们将所有可能的副作用用类型 \mathcal{C} 封装,函数变为:


f: \mathcal{C}\times\text{X}\to\mathcal{C}\times\text{Y}

对于长时运行程序,忽略函数本身的入参和出参,f 属于函数空间 $\mathfrak{F}=\mathcal{C}\to\mathcal{C}$。

从幺半群到群

任何函数 f: \mathcal{C}\to\mathcal{C} 都是状态空间到自身的变换。在组合 \circ 下构成幺半群

  1. 封闭性:f\circ g 也是 \mathcal{C}\to\mathcal{C}
  2. 结合律:(f\circ g)\circ h=f\circ (g\circ h)
  3. 单位元:$\text{id}$,使得 f\circ\text{id}=\text{id}\circ f=f

如果额外要求每个 f 存在逆元 $f^{-1}$(即副作用可回收),\mathfrak{F} 升级为

副作用都可逆吗?

观察计算机中的副作用模式:

操作 占用资源 逆操作
打开文件 文件描述符 关闭文件
创建子进程 进程号 杀死进程
监听端口 端口 取消监听
添加回调函数 事件槽位 删除回调
分配内存 内存区块 回收内存

副作用就是对资源的占用。 计算机的资源天然设计为可重复使用,因此这些副作用一定是可逆的。

追踪和回收副作用

Cordis 通过 \text{effect}\text{restore} 函子追踪和回收逆函数。

effect 函子


\begin{array}{}
\text{effect}&:&
\left(\mathcal{C}\to\mathcal{C}\right)&\to&
\mathcal{C}\times\left(\mathcal{C}\to\mathcal{C}\right)&\to&
\mathcal{C}\times\left(\mathcal{C}\to\mathcal{C}\right)\\
\text{effect}&=&f&\mapsto&\left(c, h\right)&\mapsto&\left(f(c), h\circ f^{-1}\right)
\end{array}

直觉:执行 f 产生的副作用记入状态 $c$,同时将逆操作 f^{-1} 追加到回收链 h 中。

同态性证明

\text{effect} 是从 \mathcal{C}\to\mathcal{C}\mathcal{C}\times(\mathcal{C}\to\mathcal{C})\to\mathcal{C}\times(\mathcal{C}\to\mathcal{C}) 的同态:


\begin{aligned}
\text{effect}\ (f\circ g) \left(c, h\right)
&=\left((f\circ g)(c), h\circ (f\circ g)^{-1}\right)\\
&=\left(f(g(c)), h\circ g^{-1}\circ f^{-1}\right)\\
&=\left(\text{effect}\ f\right)\left(g(c), h\circ g^{-1}\right)\\
&=\left(\text{effect}\ f\right)\circ\left(\text{effect}\ g\right) \left(c, h\right)
\end{aligned}

这意味着:组合两个操作后再追踪 = 分别追踪后再组合。副作用追踪与执行顺序无关。

restore 函子


\begin{array}{}
\text{restore}&:&
\mathcal{C}\times\left(\mathcal{C}\to\mathcal{C}\right)&\to&
\mathcal{C}\times\left(\mathcal{C}\to\mathcal{C}\right)\\
\text{restore}&=&\left(c, h\right)&\mapsto&\left(h(c),\text{id}\right)
\end{array}

直觉:将回收链 h 应用到当前状态,一次性回收所有已追踪的副作用。

在 Cordis 中的实现

理论映射到 API

数学概念 Cordis API 说明
\text{effect}(f) ctx.effect(() => { ...; return dispose }) 注册副作用并返回清理函数
\text{restore} fiber.dispose() 执行 Fiber 的整个回收链
f^{-1} dispose 返回值 / cleanup 函数 逆操作
export function apply(ctx: Context) {
  // effect: 创建资源,返回其逆操作
  ctx.effect(() => {
    const server = startServer(8080)     // f: 占用端口
    return () => server.close()          // f⁻¹: 释放端口
  })

  // 框架 API 内部已封装 effect
  ctx.on('event', handler)               // 内部: effect(addListener, removeListener)
  ctx.tools.register(myTool)             // 内部: effect(addTool, removeTool)
}
// 当此插件被卸载时,restore 自动按逆序执行所有 f⁻¹

为什么 Agent 需要可逆作用

在 Harness 场景下,可逆作用直接支撑:

  • 热替换 LLM 适配器:卸载旧适配器(回收注册)、加载新适配器,无需重启
  • 动态 tool 管理:根据对话上下文动态添加/移除 tool,不泄漏
  • 子 Agent 生命周期:子 Agent 完成后,其注册的所有临时 tool 和监听器自动清理
  • 优雅关闭:进程退出时所有插件按依赖逆序 dispose,确保资源完全释放