Agent 可靠性工程:超时、重试、幂等与恢复
Agent 系统最危险的错误,不一定是“工具返回失败”,而是“系统不知道刚才到底成功没有”。
发送邮件的请求超时,可能是服务根本没收到,也可能邮件已发出、只是响应丢了。此时简单重试,可能把周报发两遍;不重试,又可能漏发。
可靠性工程处理的正是这种现实:网络会断、进程会重启、模型会选错动作、外部状态会变化。系统不能依靠“再试一次”的直觉,而要用错误分类、幂等、检查点、补偿和终态验证控制不确定性。
一、先区分五类失败
Section titled “一、先区分五类失败”1. 模型决策失败
Section titled “1. 模型决策失败”选错工具、参数错误、错误理解目标或陷入循环。解决手段包括更清楚的 Tool Schema、状态约束、Eval 和验证器,不应靠无限重试同一 Prompt。
2. 确定性业务失败
Section titled “2. 确定性业务失败”余额不足、订单已发货、权限不允许、Schema 不通过。重复请求不会改变结果,应进入失败、降级或人工处理。
3. 暂时性基础设施失败
Section titled “3. 暂时性基础设施失败”限流、短暂网络中断、服务过载。适合在预算内指数退避,并加入随机抖动,避免大量任务同时重试。
4. 永久性技术失败
Section titled “4. 永久性技术失败”接口已删除、凭据无效、参数协议不兼容。自动重试只会放大压力,应快速失败并告警。
5. 未知外部状态
Section titled “5. 未知外部状态”请求可能已产生副作用,但响应丢失。这一类不能按普通网络错误盲目重试,必须通过幂等键或查询接口确认。
可靠系统的第一步不是“增加重试”,而是把错误分到正确的恢复路径。
二、Retry Policy 必须有边界
Section titled “二、Retry Policy 必须有边界”一个完整重试策略至少说明:
- 哪些错误允许重试;
- 最大尝试次数;
- 单次超时和总截止时间;
- 退避算法与抖动;
- 每次尝试是否使用同一幂等键;
- 重试前是否需要查询外部状态;
- 预算耗尽后的终态。
attempt 1 -> 429 -> wait 1.2sattempt 2 -> 429 -> wait 2.6sattempt 3 -> success固定间隔会形成“惊群”:上千个 Agent 同时失败,又同时重试。指数退避加随机抖动能分散压力,但仍要受全局截止时间约束。
模型重试也要改变量。若工具 Schema 始终不通过,可以把具体错误返回给模型一次;同样输入、同样上下文连续生成十次,通常只是消费预算。
三、幂等到底保证什么
Section titled “三、幂等到底保证什么”幂等意味着相同业务请求重复执行,不会产生额外副作用。常见做法是调用方生成稳定 idempotency_key,服务端保存键与结果。
例如:
key = tenant + order_id + operation + business_version相同键重复请求,服务端返回第一次结果,而不是再次退款。
幂等键不能随重试变化
Section titled “幂等键不能随重试变化”如果每次尝试生成新 UUID,服务端会把它们当成不同业务请求。
幂等范围必须清楚
Section titled “幂等范围必须清楚”“发送周报”按周和收件组幂等;“发送提醒”可能允许每天一次。键的设计来自业务语义,不是技术层随意拼接。
查询接口同样重要
Section titled “查询接口同样重要”当响应丢失,调用方应能按幂等键查询结果。只有写接口、没有状态查询,未知状态很难恢复。
四、Exactly Once 通常是业务效果,不是网络魔法
Section titled “四、Exactly Once 通常是业务效果,不是网络魔法”分布式系统很难保证网络层真正只发送一次。更现实的目标是:消息可以至少传递一次,但业务处理通过幂等记录只产生一次效果。
因此,“Exactly-once Effect”通常由以下组合实现:
- 稳定业务 ID;
- 原子写入或事务;
- 去重记录;
- 可查询结果;
- 消费者幂等;
- Outbox/Inbox 等消息模式;
- 环境终态验证。
Agent 只是调用方的一部分,不能用 Prompt 承诺 Exactly Once。
五、Checkpoint 保存什么,何时保存
Section titled “五、Checkpoint 保存什么,何时保存”第 12 讲介绍了 Durable Execution。可靠性角度看,检查点至少要在副作用边界前后保存:
READY_TO_SEND -> checkpoint(intent + idempotency_key) -> external side effect -> checkpoint(observed result) -> VERIFIED检查点应包含:
- 当前状态和状态版本;
- 已完成步骤;
- 外部调用的幂等键与资源 ID;
- 尝试次数与截止时间;
- 工件引用和哈希;
- 当前代码、Policy 和 Workflow 版本;
- 下一步恢复动作。
不能只保存聊天历史。恢复需要的是机器可执行状态,而不是让模型重新猜刚才进行到哪里。
六、补偿不是“撤销按钮”
Section titled “六、补偿不是“撤销按钮””有些副作用无法真正回滚:邮件已被阅读、通知已发送、外部报价已暴露。Saga 模式中的补偿动作只是尽量减轻影响,例如退款的补偿可能是重新扣款或人工复核,并不等价于时间倒流。
设计补偿时应明确:
- 原动作是否可逆;
- 补偿由谁授权;
- 补偿失败怎么办;
- 用户是否需要收到说明;
- 审计记录是否保留原始事实。
对不可逆、高影响动作,更好的策略通常是提交前预览和人工批准,而不是依赖事后补偿。
七、Circuit Breaker、Bulkhead 与 Backpressure
Section titled “七、Circuit Breaker、Bulkhead 与 Backpressure”当下游持续失败时,Agent 不能继续制造调用风暴。
Circuit Breaker
Section titled “Circuit Breaker”连续错误达到阈值后临时断路,快速失败;冷却后允许少量探测,再决定恢复。
Bulkhead
Section titled “Bulkhead”为不同任务、租户或工具隔离并发池。一个慢网页抓取服务不应耗尽所有退款任务资源。
Backpressure
Section titled “Backpressure”队列超过容量时拒绝、延迟或降级,不让任务无限堆积。长任务应向用户显示排队和预计状态,而不是一直“思考中”。
这些模式不是模型特有,却是 Agent 进入生产后不可回避的基础设施。
八、预算是可靠性控制,不只是费用控制
Section titled “八、预算是可靠性控制,不只是费用控制”每次运行需要多维预算:
- 最大模型调用次数;
- 最大 Tool Calls;
- 最大 Token 或费用;
- 墙钟截止时间;
- 最大并发 Worker;
- 最大恢复次数;
- 人工审批等待期限。
预算耗尽应进入明确状态,例如 BUDGET_EXHAUSTED 或 NEEDS_HUMAN,并保存已有工件。不要让模型用一句“任务已基本完成”掩盖非成功终态。
Loop Engineering 的一个核心价值,就是把继续、重试、验证和停止条件放入可审计控制面,而不是全部留在 Prompt。[1]

图 1:旧连载调研中的 LoopX 页面强调显式状态内核;2026-07-20 复核时原仓库已返回 404,因此只作为历史截图,不列为当前可用项目。
九、CI Sweeper 案例:为什么自动修复必须是有界 Loop
Section titled “九、CI Sweeper 案例:为什么自动修复必须是有界 Loop”一个常见 Coding Agent 场景是:读取 CI 失败、修改代码、运行测试、再次修复,直到通过。

图 2:CI Sweeper 是把 Issue/CI、Coding Agent 与 GitHub 工作流连接的实例。图片来自项目页面,来源见文末。
这类 Loop 至少要限制:
- 最大修复轮数;
- 允许修改的路径;
- 测试命令与超时;
- 每轮 Diff 和 Commit 绑定;
- 禁止自动修改秘密、发布和分支保护;
- 测试必须针对当前树重新运行;
- 连续相同错误触发停止,而非死循环。
“测试通过”也不能只取 Agent 的自述,应读取真实退出码、报告和当前 Commit。
十、SLO 应围绕用户任务,而不是模型调用
Section titled “十、SLO 应围绕用户任务,而不是模型调用”模型 API 99.9% 可用,不代表 Agent 任务 99.9% 成功。端到端还经过检索、工具、队列、审批和验证。
可定义的 SLI 包括:
- 任务在截止时间内达到已验证终态的比例;
- 高风险动作零越权率;
- 重复副作用率;
- 恢复后成功率;
- 人工接管等待时间;
- 每个成功任务成本。
例如:“过去 28 天,95% 的只读研究任务在 5 分钟内完成并附带可访问来源;任何发布动作必须 100% 获得绑定参数的人工批准。”
不同任务风险不同,不能共用一个 SLO。
十一、真实实验:副作用已发生,响应却超时
Section titled “十一、真实实验:副作用已发生,响应却超时”本讲的 fault_injection_idempotency.py 模拟邮件服务:第一次调用已经把邮件写入外部状态,但故意抛出 response_lost_after_side_effect。
Loop 没有立即重发,而是按相同幂等键查询。查询确认邮件已存在,因此终态成功,外部发送计数仍为 1。
{ "external_send_count": 1, "deduplicated": true, "terminal": "success"}完整结果见 experiment-result.json。
实验中的 example.invalid 是保留域名,不会发送真实邮件。它验证了关键恢复原则:副作用状态未知时先查询,不盲目重试。
十二、20:40 分钟扩展实践
Section titled “十二、20:40 分钟扩展实践”为实验加入三种故障:
- 请求在服务处理前超时;
- 请求处理成功但响应丢失;
- 服务返回确定性业务拒绝。
分别设计策略:第一种可用相同幂等键重试;第二种先查询;第三种直接失败。再增加两次进程重启,把意图和观察结果写入检查点,确认恢复后仍只发送一次。
最后加入截止时间和最大尝试次数,确保服务持续不可用时进入 NEEDS_HUMAN,而不是永久运行。
十三、故障注入比“等线上出错”更便宜
Section titled “十三、故障注入比“等线上出错”更便宜”上线前主动测试:
- 工具 429、500、超时和畸形 JSON;
- 模型返回空输出或未知工具;
- 检查点写入失败;
- 进程在副作用前后崩溃;
- Worker 重复消费同一消息;
- 用户在审批期间取消;
- Policy 或工具版本在恢复前变化。
每次注入都检查领域终态、重复副作用、Trace 和恢复时间。可靠性不是“没有报错”,而是报错时系统仍能进入受控状态。
十四、这一讲的结论
Section titled “十四、这一讲的结论”Agent 可靠性工程的核心,是承认外部世界存在失败和未知状态。
错误分类决定能否重试;幂等和查询控制重复副作用;检查点支持恢复;补偿处理不可避免的部分完成;预算、断路器和背压防止故障扩散;终态验证则决定任务是否真的完成。
下一讲讨论安全:当外部内容能够影响模型,而模型又能调用工具时,Prompt Injection 怎样变成真实权限攻击,系统又该如何防御。
[1] Peter Steinberger, Just Talk To It. https://steipete.me/posts/just-talk-to-it
[2] Temporal, Building Reliable Applications with Durable Execution. https://pages.temporal.io/download-building-reliable-apps-durable-execution
[3] AWS, Retry with backoff pattern. https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/retry-backoff.html
[4] Microsoft Azure Architecture Center, Compensating Transaction pattern. https://learn.microsoft.com/en-us/azure/architecture/patterns/compensating-transaction
[5] CI Sweeper. https://github.com/sweepai/sweep
[6] LoopX 历史仓库,2026-07-20 复核时已不可访问;本文仅保留旧截图用于概念演进说明。