基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
AIHT
内容
精选全部 AI 动态热点榜AI 日报收藏
分类
模型行业编程产品技术观点开源
更多
Agent 接入
返回首页开发者导航
AIHT
精选 / 资讯详情
8.0
热点
AI SCORE
技术实践2026-10-28 21:58

代理故障重试为何会重复退款

dev.to · AI#Agent#幂等#故障恢复
Editor brief · 编辑速览

文章以退款成功后、检查点保存前发生崩溃为例,说明工作流重试可能重复触发外部操作。核心问题是如何在至少一次执行的前提下保证副作用幂等,适用于支付、邮件和基础设施操作。

文章思维导图
Knowledge map
拖拽缩放
AI deep read

AI 深度解读

AI Agent 最危险的重试,是上一次操作已经成功,系统却没来得及记下来。退款到账后进程崩溃,恢复逻辑再执行一次,客户就可能收到两笔钱。工作流最终显示成功,业务却已经出错。

作者用一个 TypeScript 发布流水线,把这个问题做成了可复现的故障实验:暂停一个 worker,等另一个 worker 接管,再让原进程恢复,观察旧结果能否覆盖新结果。结论很明确:可靠恢复需要幂等、租约隔离和检查点保护一起工作,不能只依赖重试。

执行可以重复,业务结果为什么不能重复

原文区分了四个容易混在一起的概念:

  • 至少执行一次(At-least-once execution):失败恢复时,同一步可能运行多次。
  • 恰好一次效果(Exactly-once effect):同一个逻辑操作只对外部状态产生一次改变。
  • 幂等(Idempotency):重复提交同一个操作,不会增加额外效果。
  • 隔离旧持有者(Fencing):worker 失去任务所有权后,不能继续提交旧结果。

假设 Agent 批准了 $100 退款,支付成功后,worker 在保存检查点前崩溃。恢复系统看到步骤未完成,再调用支付工具,就可能多退 $100。

问题发生在两个提交之间:外部业务已经提交,工作流进度还没提交。

稳定的幂等键可以标识「这是同一笔操作」,但去重必须由业务边界原子地执行。两个 worker 如果都先查询 payment-123,都发现不存在,再分别支付,键的存在也救不了它们。

另一个问题是旧 worker 复活。Worker A 的租约过期后,Worker B 接管并完成任务,A 却仍握着内存里的旧结果。此时除了识别重复操作,还要判断 A 有没有写入资格。

幂等解决重复操作,fencing 解决过期身份,两者不能互相替代。

作者开发的 AGBoss 建在 LangGraph 之上,提供租约、检查点恢复、确定性的步骤幂等键,以及过期检查点写入保护。外部数据库、文件和 API 的一致性,还需要应用自己配合。

两次故障,分别验证补偿和接管

实验是一个包含三个模块的 AI 发布流水线,由协调器和两个独立 worker 进程执行。协调器通过 ChildPlacement.AnyInstance 分配子工作流,声明 maxConcurrency: 4、maxItems: 8,不指定某个模块必须交给哪个进程。

第一次故障发生在三个产物都已暂存之后。

初始配置是 --opt-level=3 --deterministic,但 payment-engine 缺少 --abi-compat=v2,集成 smoke gate 因此失败。这些参数是演示场景里的构建配置,并没有传给真实编译器;失败也是刻意注入的。

此时需要 Saga 补偿:删除暂存文件,把 SQLite 记录标记为 COMPENSATED,释放候选版本锁。AGBoss 用 compensateWith 和显式错误分支编排补偿,但真正删除文件、撤销业务操作、保证补偿可重试,都是应用的责任。

补偿后,ReleaseRecoveryPlannerAgent 通过 InspectFailedAttemptTool 读取实际诊断和补偿状态,再生成 RecoveryPlanDto。录制的在线实验使用 OpenRouter 上的 openai/gpt-4o-mini,进行了两次模型调用,消耗 1,705 个输入 token、178 个输出 token,补上了缺失参数。

第二次故障才是僵尸 worker 测试。

payment-engine 完成 Step 1 并保存检查点后,在 Step 2 被操作系统的 SIGSTOP 暂停。进程内存保留,但无法执行代码,也无法续租。真实的 30 秒租约到期后,另一个 worker 接管。

接管沿用同一个 executionId:

f9907abf-19fd-498b-8397-49152e25345f

leaseEpoch 从 1 升到 2,替代 worker 从已提交的检查点继续,Step 1 没有重跑,运行时发出 execution.recovered。

随后,SIGCONT 让原进程恢复。它仍以为自己持有 leaseEpoch = 1,继续尝试提交旧结果。

为什么检查点拦住了,外部写入还要再拦一次

AGBoss 的 Action 可以在 execute() 中执行外部 I/O,之后运行时才提交检查点。因此,即使框架拒绝旧检查点,外部文件或数据库也可能已经被改了。

实验为暂存步骤保留了稳定的幂等键:

f9907abf-19fd-498b-8397-49152e25345f:stage-module-artifact-action:1

它由执行、步骤和访问次数标识组成,恢复后不变。同时,Action 开始时捕获租约持有者和 epoch,把它们交给应用账本验证。

下面这段简化代码解决的是「旧 worker 还能不能写」:

if (
  params.capturedLeaseEpoch < activeLeaseEpoch ||
  (
    activeHolder !== params.capturedInstanceId &&
    params.capturedLeaseEpoch <= activeLeaseEpoch
  )
) {
  // 记录 ZOMBIE_WRITE_REJECTED
  return new StaleLeaseFenceError(/* ... */);
}

关键是第一行的 epoch 比较,以及原文强调的事务边界:真实实现把检查放进应用的 SQLite 事务中。脱离提交过程单独检查,仍可能留下竞争窗口。

僵尸 worker 的 epoch 为 1,当前为 2,账本拒绝外部写入并记录 ZOMBIE_WRITE_REJECTED。之后,LeasedCheckpointSaver 再拒绝它的检查点写入,抛出 ExecutionLeaseLostError,错误码为 execution.lease-lost。

业务状态和工作流状态,都需要在各自的提交边界检查所有权。

作者还用独立验证器读取文件系统、应用 SQLite 账本,以及 execution_leases、checkpoints、child_execution_queue。录制结果中,恢复任务的六个检查点形成单一的 parent_checkpoint_id 链,没有僵尸分支,也没有遗留子任务。

运行需要 Node.js 22.13+ 和 npm。基础复现命令如下:

git clone https://github.com/agbossjs/distributed-agent-recovery.git
cd distributed-agent-recovery

npm ci
npm run check
npm test
npm run demo:cluster
npm run verify

这里要看最后一行 npm run verify:工作流结束之后,还需要检查实际状态是否满足约束。

默认测试使用确定性的离线模型传输,不需要 LLM API key。真实时间实验先执行 npm run build,再运行 node dist/src/demo.js --cluster --real-clock;在线模型模式使用 OPENROUTER_API_KEY="..." node dist/src/demo.js --cluster --live --real-clock。容器版本入口是 npm run demo:compose。

我的判断

这篇最有价值的地方,是把模型推理和一致性机制分开了。LLM 负责读诊断、提修正计划;租约、去重、检查点和事务写入交给确定性的程序约束。靠提示词要求 Agent「不要重复操作」,无法处理支付成功后进程崩溃这样的故障。

实验也给出了比成功日志更强的证据。作者报告,独立 Node.js 进程、Docker Compose、单节点 Kubernetes kind 三项 CI 验证全部通过,并另外测试了 Docker worker 突然退出。SIGSTOP/SIGCONT 则专门覆盖旧进程恢复的情况,二者验证的故障不同。

但这些结果有明确边界。实验使用 SQLite WAL 和应用自己实现的事务账本;单节点 kind 也不能证明跨节点、跨地域或网络分区下的行为。把账本换成第三方支付、邮件或发布 API 后,仍要确认对方是否支持原子去重,以及如何阻止旧请求生效。

修复方案也不能据此推广为「AI 擅长自动修复发布」。缺失参数是预设故障,原文没有提供多种未知故障的成功率,也没有规则修复的对照数据。这个实验展示的是流程可以接入真实 LLM。

AGBoss 的 agboss check 能静态检查工作流组合、DTO 转换和依赖注入约束,但不能代替故障注入。读取内部表同样只是与被测框架版本绑定的诊断方法,不是推荐的公开集成 API。

此外,提供的原文在最后一节中途截断,不能替作者补全后续保证。

能拿来做什么

  • 给 Node.js Agent 的外部工具调用补上操作身份:优先使用稳定的业务键或执行步骤键,并确认去重与实际写入处于同一原子边界。
  • 如果任务允许多个 worker 接管,就同时检查业务写入和检查点写入的所有权,避免只保护工作流进度,却让旧进程修改业务数据。
  • 为发布流水线分别设计补偿测试和僵尸测试:前者检查失败后的暂存产物是否清理,后者检查旧 worker 恢复后能否覆盖新结果。
  • 验证恢复时检查落盘事实:执行 ID 是否保留、检查点有没有分叉、队列是否残留任务、产物是否重复,不能只看任务最终返回成功。
Original source

本文由 AI 通读原文后重新编写,观点部分为站内判断,细节以原文为准,来源 dev.to · AI,原文版权归原作者所有。

阅读原文
上一篇
用达标请求量衡量大模型服务容量
下一篇
AI 做游戏开始跨越玩法设计门槛

资讯速览

dev.to · AI · 2026-10-28 21:58

AI 评分 8.0 / 10

相关标签

#Agent#幂等#故障恢复

继续阅读

  • 单图生成热门短视频的工程流水线
  • 用达标请求量衡量大模型服务容量
  • AI 做游戏开始跨越玩法设计门槛
  • AI网关响应日志可能泄露开发凭证
  • 给 MCP 服务锁版本,避免工具悄然变更