错误的框架:一个撤销按钮
为智能体"添加一个撤销按钮"的请求预设了一个并不成立的模型。它假定存在单一的可逆事务,就像文本编辑器对待一次按键那样。一次智能体运行不是一次事务。它是一系列跨系统的异构写入,这些系统具有不同的一致性模型:数据库中插入的一行、发布到队列的一条消息、写入对象存储的一个文件、触发下游工作流的一次 HTTP 调用、通过某个提供商发送的一封邮件——而该提供商已经把字节交给了远程 MTA。这些操作各自对"撤回写入"意味着什么都有自己的理解,而其中若干操作根本没有这种概念。
撤销按钮是一种 UI 可供性,它掩盖了一个状态问题。在只有一个数据存储的单用户应用中,状态问题小到足以被掩盖。在跨越多个系统的智能体中,状态问题就是设计问题。按钮是你在解决该问题之后剩下的东西,而不是你为解决它而构建的东西。
效果并非同质
按逆转它们的代价来区分效果会有所帮助,而不是按它们在 trace 中如何呈现来区分。
有些效果逆转起来代价很低。一份草稿文档、一次预发环境写入、临时 schema 中的一行、在测试环境中切换的一个功能开关。逆转就是另一次同类写入,针对同一权限,代价是有界的。
有些代价高昂。一封已发送的邮件无法被撤回;只能随后发送一封更正,而更正本身就是一次新的通信,有自己的受众和自己的后果。一笔付款可以退款,但退款是一笔新交易,涉及手续费、时间安排和对账影响。一条被删除的记录有时可以从备份中恢复,但恢复是一次局部的、时间点操作,可能会覆盖无关的变更。
有些则不可能。一个已泄露的机密无法被收回。一旦凭证、客户记录或内部文档离开了信任边界,没有任何补偿操作能让世界回到先前的状态。相关的控制手段是预防,而不是逆转。
因此,设计问题不是是否提供撤销。而是哪些工具可以在没有逆转路径的情况下被调用,以及在什么前置条件下。
对工具效果进行分类
一个可行的分类法有三类,每个工具声明都应恰好携带其中一类。

可逆。 该工具暴露一个与原始操作具有相同权限的补偿操作。补偿是对称的:相同的主体、相同的范围、相同的一致性保证。一次数据库插入配以按主键匹配的删除是可逆的。一次预发部署配以回滚到固定产物是可逆的。检验标准是:补偿调用能否由同一智能体身份在无需新审批的情况下发出,以及其结果对于本次运行所关心的目的而言是否等同于调用前的状态。
可补偿。 逆转存在,但它是一个新动作,有自己的副作用和自己的审批。退款是可补偿的:它逆转了一笔扣费的经济效果,但它创建了一条新的账本条目,可能产生手续费,并且超过阈值时可能需要人工授权。一条已被消费者处理过的已发布消息,只有在"可以发布更正"这一意义上才是可补偿的;原始的消费无法被撤销。可补偿工具正是大多数有趣工程所在之处,因为补偿本身是一个一等操作,它自己需要前置条件、审批路径和记录。
不可逆。 不存在补偿操作。发送外部邮件、泄露机密、删除没有备份的记录、调用第三方未暴露逆转方式的第三方动作。这些需要事前授权,而不是事后回滚。控制手段是调用之前的闸门,而不是其后的程序。
分类是按工具的,而不是按智能体的。一个大部分可逆的智能体仍会包含一个不可逆工具,而正是这一个工具决定了整个运行的自主权上限,除非规划器被约束为将其隔离。
让可逆性成为一等字段
分类法只有在机器可读时才有用。每个工具声明都应携带一个可逆性类别,并在存在补偿操作时携带其标识符。在 schema 中,这看起来就是与工具名称、参数和权限范围并列的一小组字段。
{
"name": "send_customer_email",
"reversibility": "irreversible",
"compensating_operation": null,
"preconditions": ["recipient_verified", "template_approved", "human_gate_passed"]
}
{
"name": "create_invoice",
"reversibility": "compensable",
"compensating_operation": "void_invoice",
"compensation_requires_approval": true
}
{
"name": "write_staging_record",
"reversibility": "reversible",
"compensating_operation": "delete_staging_record",
"compensation_requires_approval": false
}
由此产生两个机制。第一,规划器受类别约束。不可逆工具不能被投机性地调用,不能被放在并行分支中——在那里某个兄弟分支的失败会让部分效果悬置——也不能在失败原因不明确时重试,除非有显式的对账步骤。可补偿工具可以被调用,但它们的补偿本身是一次工具调
rver-rack-network-cables-6c483a-2ef0de.jpg)
第二,运行记录将补偿操作与效果一并存储。当一个带有补偿操作的工具成功时,运行日志会同时写入效果标识符和补偿标识符,以及调用补偿所需的参数。回滚于是变成一次查找,而不是一次重建。响应事故的运维人员不必推断智能体做了什么并猜测一个逆转;记录会指明该操作及其参数。这就是有界恢复程序与开放式调查之间的区别。
这带来了什么
第一个收益是非对称自主权。智能体可以在可逆表面上被授予高自主权——在那里一个错误只代价一次补偿调用——而在不可逆表面上设置严格的闸门——在那里一个错误代价一次泄露或一段客户关系。这比单一的全局自主权级别更有用,后者要么过度限制可逆工作,要么限制不足不可逆工作。可逆性类别正是自主权应当随之变化的轴。
第二个收益是事故响应。当每个效果都连同其补偿被记录时,恢复程序就是一份清单,而不是一次搜索。运维人员知道哪些效果可逆、哪些可补偿且需要审批、哪些不可逆因而需要完全不同的响应:通知、遏制和披露,而不是回滚。
第三个收益是评估。如果工具声明携带可逆性,评估框架就可以测量一次运行在其前置条件满足之前伸手去够不可逆工具的频率。这是过度急切自主权的一个领先指标,而且无需等待生产事故即可测量。它还让评估能够在同一任务上比较规划器变体:那个将不可逆调用推迟到前置条件满足的变体,是否同样频繁地完成任务,代价是多少步?
诚实的权衡
这种设计以吞吐量为代价。约束规划器隔离不可逆工具,并要求对可补偿工具进行显式的补偿调度,这移除了约束较少的规划器会使用的并行性和投机执行。在大部分可逆的任务上,这一约束几乎无代价。在混合可逆与不可逆效果的任务上,规划器会围绕不可逆调用串行化,运行会比不受约束时更慢、更长。这一权衡是刻意的:替代方案是一次运行,它很快,直到它变得不可恢复。
第二个代价是维护。可逆性类别不是工具在抽象意义上的属性;它是工具在部署状态下的属性,连同其当前权限、当前备份态势和当前下游消费者。一个在其后端存储具有时间点恢复时可逆的工具,当该恢复窗口被缩短时会变成可补偿的。分类必须与工具一同审查,而不是在声明时设定一次。
结论
可逆性是每个工具的状态属性,而不是叠加在智能体之上的一种可供性。工作在于:对每个工具的效果进行分类,在存在补偿操作的地方记录它,按类别约束规划器,并非对称地授予自主权。撤销按钮,如果它出现的话,是这项工作的表面。它不是这项工作的替代品。
封面照片:panumas nikhomkhai / Pexels。
