TP
TaskPilots

面向生产环境的智能体平台。

预约演示
4 条产品线,一套运行底座
智能体系统 1775235172 3m45s

部分成功以后怎么回滚:补偿路径比重试更重要

围绕可靠多智能体工作流构建的研究与运营笔记。

TP

TaskPilots 编辑部

AI 系统研究

更新日期

1775235172

部分成功以后怎么回滚:补偿路径比重试更重要

围绕可靠多智能体工作流构建的研究与运营笔记。

很多团队在长任务恢复里默认的第一反应是“再试一次”。这在纯计算步骤里有时足够,但一旦流程已经部分成功,盲目重试往往会把问题放大。外部系统可能已经写入了一半,通知可能已经发出,库存可能已经占用,人工审批也可能已经走到中途。此时系统最需要的往往不是更激进的重试,而是一条清楚的补偿路径。

所谓补偿,并不是简单回滚数据库,而是为每一种已发生的副作用设计“如何撤销、如何对冲、如何转人工”的恢复分支。Microsoft 的 Durable Functions、OpenAI Agents SDK 的运行模型,以及 Temporal 面向 AI 工作流的持久执行实践都在提醒团队:长时运行系统最危险的问题不是失败本身,而是部分成功之后还继续无脑重试。对 TaskPilots 这类支持独立运行 Agent 与恢复机制的系统来说,真正成熟的恢复设计,一定把补偿路径前置到和重试同等重要的位置。

为什么这个问题重要

部分成功是最昂贵的失败形态

完全失败通常反而容易处理,因为系统只需要决定是否重试或终止。真正棘手的是“已经做成一半”。例如任务已经创建了外部工单、发出了一封邮件、扣减了一次额度,却在后续步骤失败。此时如果系统只会重新执行,就可能制造重复工单、重复通知和重复扣费。对用户来说,损失不是一次失败,而是一次失败外加一次错误恢复。

Durable Functions 和 Temporal 都把这类场景视为持久工作流的核心难题,因为系统必须知道哪些动作可以安全重放,哪些动作需要补偿,哪些动作必须等待人工接管。OpenAI Agents SDK 的运行模型虽然更贴近执行单元,但同样说明运行状态和副作用边界需要被显式记录。只要系统里存在外部写入和跨步骤状态,补偿就不再是“高级特性”,而是基础安全能力。

如果不处理会怎样

如果团队继续把所有异常都交给重试,最先暴露的问题通常有四类。第一,重复副作用,外部系统被反复写入。第二,状态错乱,本地以为重试成功,外部其实已经有一份旧状态残留。第三,人工解释成本上升,因为运营和客服要花时间向用户说明为什么会收到两次通知或看到两份记录。第四,恢复逻辑越来越黑箱,没人能说清系统到底是成功、失败,还是“成功了一半”。

再往后,重试本身会变成风险放大器。一次原本只影响单笔任务的故障,可能因为自动重试和异步 callback 叠加,扩散成批量错误。到这一步,团队往往才意识到自己缺的不是更多重试策略,而是明确的补偿分支和停止条件。

适用场景

谁最需要这套方法

最适合采用这套方法的,是那些会写入外部系统、等待外部事件、跨天运行,或需要人工审批链路的流程。比如订单处理、工单流转、客户通知、额度更新、审批放行、跨系统数据同步等。只要一次任务可能在多个步骤里对真实世界产生副作用,就值得优先考虑补偿,而不是默认重试。

尤其是那些“先做一部分,再等下一步信号”的链路,更容易因为局部成功而变得危险。比如系统先创建草稿,再等人工确认;先占库存,再等支付回调;先发送申请,再等第三方审核。这类流程里,每一步都可能成为后续补偿的起点。

什么时候先不要这么做

如果当前流程仍然是纯读操作、无副作用、无外部写入,而且失败后重跑的成本极低,那么先做复杂补偿体系可能得不偿失。此时基础重试、超时处理和错误监控往往已经足够。

另一个不适用边界,是你还没定义清楚“哪些动作算不可逆副作用”。如果业务本身对撤销、重试和终止的语义都不明确,技术层面的补偿设计很快会和真实流程脱节。先和业务一起厘清动作类型,再谈补偿结构,会更稳。

推荐系统结构

重试、补偿与人工接管要并列设计

比较稳的结构,是把恢复机制拆成三类分支。第一类是安全重试,只用于幂等、无外部副作用或已被幂等键保护的步骤。第二类是补偿分支,用于撤销、对冲或标记已发生的外部动作。第三类是人工接管分支,用于那些既不能安全重试,也无法自动补偿的高风险情况。只有这三类边界清楚,系统才知道何时可以自动继续,何时必须换策略。

在持久状态层,至少应记录已发生副作用、对应幂等键、可执行补偿动作、补偿状态以及最后一次人工决策。这样恢复时,系统不是简单问“要不要再试”,而是先判断“上一步到底做成了什么、如果失败了该怎么撤、如果撤不了该交给谁”。这也是 Durable Functions 和 Temporal 在长期工作流里一再强调编排状态重要性的原因。

与 TaskPilots 的映射

映射到 TaskPilots,可以把独立运行 Agent 视为会逐步产生副作用的执行单元,把恢复机制视为围绕每个关键动作维护检查点、幂等键、补偿路径和人工升级点的控制面。也就是说,TaskPilots 不该只知道“任务失败了”,还应知道“它失败前已经完成了哪些动作、哪些动作可以补偿、哪些动作只能人工收尾”。

在这种结构里,补偿路径不是错误后的附加备注,而是正式的工作流分支。相关文章如 唤醒器、定时器和外部回调,谁负责恢复执行 讨论的是恢复触发器,而本文进一步强调:恢复之后走哪条路,往往比“恢复这件事有没有发生”本身更重要。

风险与失效点

常见失控方式

第一类失控,是把所有失败都丢给重试器,导致同一个副作用被执行多次。第二类失控,是名义上有补偿,但补偿只在文档里存在,没有真正进入运行状态机。第三类失控,是补偿动作本身没有幂等保护,结果系统在“撤销失败”时又制造了新的副作用。

第四类失控,是补偿语义定义得太晚。技术上以为“删除记录”就是回滚,业务上却认为还需要通知、记账或人工确认。第五类失控,则是过度自动化,把本应人工判断的高风险撤销动作交给系统直接执行,最终让补偿本身成为新事故来源。

需要人工兜底的地方

凡是涉及资金、权限、法律承诺、对外通知、客户状态改变或不可逆写入的场景,都应该为补偿保留人工兜底。因为这类动作即使技术上可以执行“反向操作”,也不代表业务上就等于真正恢复到原状态。

另外,当系统无法确认某个动作是否已经完成、外部系统状态与本地状态明显冲突、或者补偿动作本身失败时,也应立即升级人工。补偿路径的价值之一,就是让系统知道什么时候不该再自动往前冲。

验证指标

上线前怎么验证

上线前建议至少做三类验证。第一,部分成功演练:故意让流程在副作用之后、完成之前失败,检查系统是否会走补偿而不是直接重试。第二,补偿重放测试:重复执行同一补偿动作,确认它不会再次制造副作用。第三,人工接管验证:检查那些无法自动补偿的失败,是否真的能把上下文、已发生动作和待处理项完整交给人。

如果要更严格一些,可以增加“外部状态冲突演练”。也就是故意制造本地状态与第三方状态不一致,再验证系统是否能识别冲突并进入安全停止或人工升级,而不是继续盲目重试。

上线后怎么持续判断

上线后建议长期跟踪至少四个指标:恢复时间、重复执行率、补偿成功率与人工接管成本。前两项反映系统是否真正减少了副作用扩散,第三项衡量补偿路径是否可用,第四项则帮助团队判断自动恢复的复杂度是否被转移给了人工。

此外,再补两个更贴近本文主题的指标会很有帮助:部分成功后直接重试率,以及补偿后遗留工单率。前者越高,通常说明系统还在用重试掩盖缺失的补偿设计;后者越高,则说明补偿虽然执行了,但并没有真正把业务状态收干净。

下一步 / FAQ

下一步建议

最实际的第一步,不是一次性给所有任务都补补偿逻辑,而是先挑一条最容易产生外部副作用、且历史上出过重复执行问题的高价值链路。把它拆成步骤后,明确写下:哪些动作可重试、哪些动作必须补偿、哪些动作只能人工处理。只要这张表画出来,很多恢复争议会立刻变得可讨论。

第二步再为其中最关键的一个副作用动作补一条真实可执行的补偿路径,并做演练。第三步才是把这些规则沉淀成更通用的恢复框架,而不是反过来先追求平台完美。

FAQ

是不是所有失败都应该优先补偿? 不是。对纯计算步骤或已完全幂等的动作,重试通常更简单。补偿主要针对已经发生外部副作用的场景。

有幂等键是不是就不需要补偿了? 不一定。幂等键能减少重复执行,但不能替代对已发生副作用的撤销、对冲或人工收尾。

补偿是不是等于完全回滚? 不一定。很多业务动作无法真正回到初始状态,补偿更常见的目标是把损失控制住、把状态拉回可管理区间。

组织上最容易卡在哪里? 最常见的卡点是技术和业务对“恢复完成”的定义不同。没有统一语义,系统很容易以为自己已经补偿成功,业务却仍然认为任务没有真正收尾。