
动作做完了,通知没送到:恢复时应该重做哪一步?
一项动作有了结果,后续执行者也已经被安排唤醒,原调用却在写入投递确认前中断。
恢复程序再次看到“尚未确认”,应该重做动作、再唤醒一次,还是只补上缺少的确认?
这不是一道只靠“重试要幂等”就能答完的题。动作完成、安排继续工作、确认投递,各自都有不同的提交点。恢复必须知道究竟缺了哪一份事实。
把长任务拆开,不等于把责任拆散
Paperclip 是一个组织 Agent 工作并管理工具访问的开源应用。已合并的 #13063把任务中的人工审批、动作结果与后续继续工作连接起来,同时保存独立的投递记录。
我们选择研究它的结果投递服务,而没有复跑整个审批产品。固定提交为 7a2314c7fc610e4ee28babcaddb5c87912166dd5。
这里最值得学的是一个积极设计:原执行轮次结束以后,已有动作结果仍能成为后续工作的依据。恢复对象是尚未完成的投递环节,不需要把整次工作从头再演一遍。
原作者报告的真人审批测试使用本地 MCP 夹具,并明确记录真实 Notion 接入尚未验证。这个限制同样保留;我们没有把作者的集成范围扩大成真实企业连接全部可用。
一次“提交后丢失返回”的对照
实验运行完整原 toolActionDeliveryService,将数据库查询和唤醒调用换成可控夹具。夹具先保存一条已经提交的唤醒记录,再抛出异常,模拟调用方没有拿到正常返回。
第一次投递调用因此中断,没有发出最终确认更新。第二次调用读取到同一条已提交记录,跳过新的唤醒,补发确认更新。
两次投递调用合计只调用一次唤醒接口。这不是“崩溃以后天然只执行一次”的证明:我们没有运行数据库事务、锁或真实进程重启。它验证的是原服务在给定持久化返回值下作出的分支选择。
这个窄观察仍有价值。它明确展示了恢复依据:不是上次调用是否正常返回,而是是否能查到已经提交的事实。
六组结果,既看补做,也看等待
| 场景 | 唤醒回调次数 | 投递确认的观察 |
|---|---|---|
| 正常新投递 | 1 | 确认 1 条 |
| 已有提交过的唤醒记录 | 0 | 补确认 1 条 |
| 唤醒调用返回,但查不到提交记录 | 1 | 不确认 |
| 原执行轮次仍在运行 | 0 | 等待,不确认 |
| 已有 10 条动作结果 | 1 | 内联 8 条,确认边界覆盖 10 条 |
| 提交唤醒后抛错,再次投递 | 两次合计 1 | 首次不确认,第二次补确认 |
图 1:依据本文已保存观测绘制的实验逻辑示意;箭头表示本图标明的处理关系,不是实际运行截图。来源:随文实验附件。
六组各在两个独立进程中运行,语义结果一致。原服务的查询条件也包含任务、公司、执行主体及相关记录约束;本实验没有实现 SQL 条件求值,不能把夹具返回的数据冒充数据库隔离验证。
探路时我们曾两次在正常场景失败:查询夹具在构造一个尚未执行的子查询时就消耗了数据,导致后续返回错位。修正为真正等待查询时才消耗数据后,正常对照恢复。这里修的是研究夹具,不能计为 Paperclip 的缺陷,更不能隐藏后只留下全绿截图。
十条被确认,不代表十条已经被读完
十条结果的场景提供了另一个容易误读的边界。原服务只把最多八条缩短后的内容内联送入继续工作的上下文,同时保留查询完整结果的引用和提交截止边界。
随后确认的可以是这十条结果所处的投递集合,而不只是八条内联内容。
这有助于控制上下文体积,也能在中断后识别哪些结果已经被提交给后续流程。但它不能证明新 Agent 已经拉取剩下两条,更不能证明十条结果都被理解、处理或验收。
因此,“引用已投递”“内容已读取”“业务已处理”仍需分开。若产品需要保证遗漏内容被处理,就要在后续执行或验收层寻找相应证据,而不是让投递确认承担它没有观察到的事实。
恢复时,先找缺失的那一段
从这个设计可以提炼一个有限原则:当已有可信提交事实时,恢复优先补缺失步骤,而不是重复前面的工作。
不过这个原则有前提。动作结果尚不确定时,不能凭“没看见返回”当成失败;唤醒记录没有提交时,也不能凭接口正常返回就确认送达。本实验的两个对照正好把这两类证据分开。
投递服务本身没有外部 provider 执行回调,所以这里没有证明外部动作严格一次。它只是消费已经存在的动作结果,再安排后续工作。完整业务链还需要动作端的身份、查询或幂等合同。
随文证据说明保留六组观测、固定上游源码与依赖替换范围。这是可以复核的恢复设计比较,不是本产品新增架构已获批准的声明。
长任务能够继续,不只是因为执行者记得上次聊到哪里。更因为系统能说清:哪一步已经留下可靠事实,哪一步仍欠着一次确认。