20 min · 先讲清 agent 的失败和传统软件有什么本质不同,再讲对症策略
🎒 预备知识:完成上一课「A12 安全」(零基础入口:第 0 课)
传统软件的失败是确定性的:同样的输入,bug 每次都在同一行炸出同样的异常—— 所以有单元测试、断言、异常处理这套成熟武器。agent 引入三种全新的失败形态:
| 新形态 | 是什么 | 例子 | 传统武器为什么失效 |
|---|---|---|---|
| 非确定性失败 | 同样输入,这次对下次错 | 采样温度下模型这次路由对了下次选错工具 | 单次测试通过 ≠ 稳定,要统计性地评(A11) |
| 软失败 | 不抛异常,只是结果变差 | 模型自信地编了个不存在的 API 参数 | 没有异常可 catch——只能靠校验和评测发现 |
| 组合爆炸的失败面 | 失败点 = 每次模型决策 × 每个工具 × 每个外部服务 | 十轮循环、五个工具,失败面是乘积 | 不可能枚举所有路径,只能分层设防 |
这三点决定了 agent 韧性的思路:不追求"不失败",追求"失败了能恢复、能定位、能诚实上报"。 所以 A3 铁律①(失败是消息)是这里的地基——先让失败可被看见和消费,才谈得上处理。
| 层 | 典型错误 | 策略 |
|---|---|---|
| Provider 层 | 429 限流、5xx、超时、上下文溢出 | 429=退避等待;5xx=有限重试;溢出=压缩后重试(A3/正课 L18) |
| 工具层 | 脚本失败、选择器失效、文件缺失 | 错误回填给模型自我纠错(A3 铁律①);连败设止血线 |
| Harness 层 | gateway 死了、进程崩、流中断 | 健康检查 + 回退链 + 会话自愈 |
| 业务层 | 结果未核实、质量不达标 | 不掩盖:标注 unverified / 返工一次 / 上报用户(A11/A12) |
红线 1:指数退避 + 抖动。为什么退避?429 限流是服务在说"我满了", 立刻重试 = 往满杯里再倒水,还会和所有其他被限流的客户端一起挤下次窗口(惊群)。 指数等待(1s、2s、4s…)+ 随机抖动打散重试时间,是分布式系统的标准礼仪。
红线 2:幂等才可自动重试。幂等 = 同一操作执行两次和执行一次效果相同。 "读文件"幂等(重读无害);"发布内容"不幂等(重试可能发出两条)。 所以发布失败的正确第一步是对账(readback 查到底发出去没有), 确认未成功才允许重试——A14 断点思想的微观版。
红线 3:重试有预算。有限次数 + 总时长上限,超了就走回退或诚实失败。 (OpenClaw 外层重试预算 32–160 次,A3 提过。)无预算重试 + 软失败 = 静默烧钱循环。
回退链:主路径坏了走备用。Easel 的传输层:HTTP 直连网关(快)→ 不可用时回退 CLI spawn(慢但可用)。但注意它的精细之处:回退发生在"新会话"粒度, 同一会话中途绝不换边——因为两条路径写不同的 transcript,换边=历史劈叉 (正课 L10 的钉子文件)。教训:回退决策也要考虑状态一致性,不只是可用性。
熔断:某依赖连续失败就暂时跳过它,别让每个请求都去撞墙。 Easel 的问答桥接:旧版本 gateway 没有 question RPC,连都不连(版本门), 失败后进程级熔断只提示一次——避免每轮都触发一次注定失败的配对请求。 熔断三态记一下:闭合(正常走)→ 打开(连续 N 次失败,直接走备用)→ 半开(冷却后试探一次)。
失败时默认放行还是默认拦截?按后果分:
fail-closed(默认拦):安全闸门——content_guard 崩了 → 宁可发不出去(exit 7 精神,A12)
fail-open (默认放):体验功能——热点榜 API 挂了 → 跳过热榜继续写文案,说明数据缺失
判据:这个功能出错时,最坏后果是什么?不可逆 → fail-closed;可逆 → 可 fail-open
韧性的一半在事前:Easel 的 doctor(环境体检:依赖/配置/网关/技能同步逐项检查) + ping(真实跑一次最小请求)——把"上线后才发现配置错"消灭在启动时。 另一半在事后:session_heal 每轮启动前清洗损坏的会话文件(上游 bug 下游门口修, 正课 L11)。你的 agent 至少要有这两个:启动自检 + 损坏可修复。
坑 1:把 429 当 5xx。限流是"别打我",重试要退避等待;当 5xx 立即重试会更糟。 坑 2:副作用操作静默重试。发布超时≠发布失败——先对账再决定。 坑 3:吞错。except: pass 把失败藏起来,下游拿到空数据错得更深——失败要传达到能决策的层。
给"发布流水线"(生成文案→生图→发布→对账)设计失败处理矩阵:每个环节列出 典型错误、是否自动重试、重试前检查什么、最终失败时用户看到什么。 发到对话里我批改。