Agent 会干活,但不会收拾残局:UndoBench 测出 83.54% 的任务完成率,故障恢复只有 46.72%

83.54% 和 46.72%:同一个 Agent 的两张脸
榜单越来越挤,任务完成率一个比一个好看。可几乎没人在问另一件事:它干到一半崩了,谁来收拾。
UndoBench 问的就是这个。论文编号 arXiv:2610.05622,2026 年 10 月 4 日提交,作者是 Dolly Sah、Tanmay Sah、Harshul Jain、Tanya Sah。
他们在 12 个留出测试工作流上,用 2 个开源权重模型、2 个框架、3 种恢复范式,跑了 5,760 次执行,也就是 2,880 组配对试验。
结果是这样:标称任务完成率 83.54%,条件恢复成功率 46.72%。
同一个 Agent,顺风的时候像老手,出事的时候像第一天上班。
先划个适用范围。如果你只拿 Agent 做只读检索、查资料、写草稿,这组数字跟你关系不大。如果你在让 Agent 调支付、下单、建单、发邮件这些接口,这一节你得看完。
换个问法:不是能不能干完,而是崩了会不会收拾残局
它的评测方式和常见榜单不一样。不是丢个任务让它跑,比谁跑得快,而是反事实配对试验:同一个随机种子,一条跑道不注入故障,一条跑道注入故障,两条对着看。
更关键的是它看什么。UndoBench 不看 Agent 自己汇报了什么,而是用线级 effect-history 加环境状态 oracle,直接记录 Agent 到底对外部世界做了什么。工具被调用几次、状态改没改、邮件是否真的出去了,这些才是被记下来的东西。
这个思路有个现成的对照:微软开源的 ThinkingBox,干的事很接近。Agent 说办好了,数据库说没有。谁对?数据库对。
边界也说在前面。主实验聚焦的是「确认丢失(lost acknowledgment)」这一类故障,不是全部故障类型。12 个工作流的样本量不算大,83.54% 与 46.72% 是作者自测口径,暂无第三方独立复现。别急着把它当行业定论。
一半以上的重试,会把钱付两遍
先看最刺眼的那个数字。naive retry,也就是无脑重试,在 53.33% 的试验里产生了重复的外部副作用:重复的付款、重复的邮件、重复的工单。
根因不复杂。第一步已经写进去了,第二步超时,Agent 没拿到确认。它并不知道「没拿到确认」和「没执行」是两件事,于是从头再来一遍。
重试不是修复。重试是第二次下注。
这笔账很好算。一个客服 Agent 一天处理 200 张工单,超时率哪怕只有 5%,按这个比例滚下去,一天就凭空多出十来条重复记录。工单还能删,付款不能。
而且这不是小模型的毛病。作者用商用 API 模型复现出了同样的能力到恢复的鸿沟。模型变强,没把这个坑填平。

三个阶段,三条完全不同的曲线
这篇论文最值钱的一句话在这儿:恢复不是一项统一的能力,它是分阶段的。同一个方法,在这个阶段满分,换个阶段零分。
- 变更发生前:各种方法表现接近,有能力完成的试验里基本不产生重复副作用。
- 部分变更中:naive retry、单次调用幂等、零权限日志,在评测的复合工作流上全部崩掉。
- 已提交但确认未到达:校验(verification)与服务端幂等显著提升安全性。
把这三行连起来看,规律很清楚。越靠近「写真的已经发生了」,越不能指望 Agent 自己判断;越靠近「对端能不能帮我记住」,越有可能救回来。
第三阶段恰恰最容易被忽略。不少人以为超时等于没成功,其实超时只是不知道成没成功。

别指望提示词:答案在对端工具上
结论先放这儿:解法不在提示词里,在对端工具能不能告诉 Agent「这件事你已经做过了」。
这就能解释单次调用幂等为什么会崩。幂等状态记在调用这一侧,调用一断、进程一重启,记录跟着没了,等于没记。零权限日志也是同一个毛病,本地记得再全,它没有权限去问对端「你那边到第几步了」,在复合工作流上一样救不了场。
真正管用的是服务端幂等。状态记在对端,同一个幂等键第二次进来,直接把上次的响应重放一遍,不再产生副作用。
工具这一层怎么接,是另一件大事。MCP 现在已经是通用的工具接入层,但它解决的是「能不能调」,解决不了「重试会不会重做」。两件事,别混着算。
还有一层容易被忽略。恢复的成本不该压在模型身上。模型负责想下一步,对端负责记住已经做过什么,两边的活别混着派。
幂等键长什么样:一段通用工程写法示意
下面这段只是通用工程模式的示意,既不是 UndoBench 项目代码,也不是某个具体 SDK 的接口。真要用,请以仓库 README 和你自己服务端的文档为准。
# 写操作带上一个稳定的幂等键
POST /v1/payments
Idempotency-Key: run-<agent_run_id>-<tool>-<step>-<sha256(args)>
# 服务端:先查表,再决定要不要真的执行
if exists(key):
return replay_previous_response(key) # 重放上次的响应,不再产生副作用
else:
resp = really_do_work()
store(key, resp)
return resp三处细节决定它有没有用。幂等键必须能从外部推出来,同一笔业务的同一个步骤,重试算出来的 key 要一样,不能每次随机生成。存储要跟业务写操作在同一个事务里,不然还是会有竞态。响应和副作用要分开存,重放的是结果,不是重做。
再配一个查询或对账接口给 Agent 用:超时之后先查,查不到再判定为没执行。这一步比什么都省事。
三个坑:现象、根因、解法
坑1:把重试当兜底
现象:超时就重跑,重复付款和重复邮件反复出现。根因:把「没收到确认」当成了「没执行」。解法:超时先走查询或对账接口,写接口不裸重放,所有写操作必须带幂等键。
坑2:本地记完就以为有日志了
现象:Agent 侧的 journal 记到第 3 步,重启后照着重放,可外部世界早就变了。根因:本地日志没有对端授权,也读不到对端真实状态。解法:状态交给服务端持有,本地只留索引和指纹。
坑3:把提示词写厚当可靠性
现象:system prompt 加了三页「动手前先确认」「务必不要重复」,重复副作用一点没少,53.33% 这个数字照样摆在那儿。根因:模型不知道外部世界的真实状态,再严谨的自我提醒,本质还是猜。解法:把「这件事到底做过没有」这个事实,从服务端返回给 Agent,别让它自己推断。
顺带一句,工具也不是接得越多越好。多接一个工具就可能让 Agent 变笨,而且每多一个写接口,就多一个「重试会不会重做」的问题。
写在最后
回到开头那组数字。83.54% 到 46.72%,中间差的不是模型智商,是工程。
以后给 Agent 接工具,先问一句「它被重试了会怎样」,再问「它能把活干完吗」。顺序别搞反。
能干完活的 Agent,现在到处都是。出了事收拾得干净的,还没几家。
边界再念一遍:论文聚焦确认丢失这一类故障,样本是 12 个工作流,2 个开源权重模型加商用 API 模型复现,属作者自测口径。想自己跑,仓库是 GitHub 上的 tradertanmay/undobench,非常早期,只有约 2 颗星,动手前先读 issue。
关注圈圈,持续带你玩转 AI 工具。
暂无评论内容