方案文档里写了熔断、写了重试、写了人工兜底为什么故障发生时一个都没拦住本文记录一次真实的生产故障复盘AgentTeams 集群 token 配额耗尽code-fixer 空转 4.5 小时。从PPT 承诺 vs 现实差距出发最终把容错能力以OpenClaw Plugin形态真正接入 Worker 执行链路——并附实机部署验证全过程。一、开场PPT 里写的和现实发生的我们团队在做一个多 Agent 代码审查系统ClawForge底座是 AgentTeams OpenClaw。初赛 PPT 里我们写了两条痛点 Agent 卡死/循环/超时需要人工介入 Worker 故障无感知任务静默丢失以及对应的破局能力Harness Engine状态机 · 检查点 · 4层重试 · 熔断 · 循环检测 · 降级2026-08-19 上午这些能力一个都没生效。真实故障是这样的集群的 token-plan 配额耗尽网关持续返回insufficient_quota。但我们的 Worker 是怎么处理的[attempt 1/4] 调用 LLM → ❌ insufficient_quota ⏳ 未识别错误类型视为可重试等待 3000ms 后重试… [attempt 2/4] 调用 LLM → ❌ insufficient_quota ⏳ 等待 3000ms 后重试… [attempt 3/4] 调用 LLM → ❌ insufficient_quota ⏳ … [attempt 4/4] 调用 LLM → ❌ insufficient_quota ❌❌ 4 次尝试全部失败任务无进展真实场景 30min × ∞ 重试 ≈ 4.5 小时空转9:52 token配额还有Manager还可以正常回复但之后token消耗殆尽code fixer worker 、 manager都迟迟不回复或者仅仅只是回复之前重复的内容整个过程4-5小时里没有任何信息提示说token消耗完了需要充值真实情况更糟每次调用等满 30 分钟超时timeoutSeconds1800然后 delivery-mirror 无限重试。一个 run 硬扛了 4.45 小时才被 abort。4 个 Worker Manager 全部瘫痪任务目录里只有 spec.md 没有 plan.md——任务静默卡死人类完全不知情。这就是我们 PPT 里写的那句Agent 卡死/循环/超时需要人工介入的现场版。讽刺的是我们承诺了要解决它但它真实发生时我们毫无办法。二、差距分析为什么设计没有变成防线复盘下来三重根因每一层都对应一个设计 vs 现实的差距差距 1容错引擎是独立代码库没接进执行链路我们的 Harness Engine状态机、熔断器、循环检测器……写了几千行 TypeScript测试全绿。但它是独立运行的代码库——而 Worker 真正执行的是 OpenClaw 的 run-loopWorker 实际执行链路OpenClaw Channel 消息 → agent-run-handler(9阶段) → run-loop(LLM↔Tool 闭循环) → 回复投递 ↑ Harness Engine 在这里吗—— 不在。差距本质我们把容错能力写成了库而不是运行时。它没有挂到 (a) Worker 的 LLM 调用点、(b) Manager 的任务调度点、(c) 网关的请求入口——任何一个位置。所以故障发生时跑的是 OpenClaw 原生逻辑把确定性错误当普通超时无限重试。差距 2Token 预算 ≠ 账户配额检测维度错位循环检测器里有个checkTokenBudget检测的是单任务上下文窗口消耗比例currentTokens/maxTokens比如 150K 窗口用了 80%。而今天的故障是账户级 1 周 token-plan 配额耗尽——两个完全不同的层面LoopDetector 检测任务上下文用了多少 token相对 contextWindow 真实故障 账户 1 周配额用完相对计费周期8/25 才重置代码里根本没有账户配额这个概念自然无从检测。差距 3错误分类缺失insufficient_quota 被当成可重试熔断器配置了llm-api服务阈值 10 次失败/60 秒窗口。但今天的错误是确定性、持续性的配额要到固定时间才恢复每次调用 30 分钟超时 → 60 秒窗口内永远凑不齐 10 次失败 →熔断器永远不触发降级策略是 Switch to fallback model → 但配额是账户级的fallback 模型同样被卡实测 kimi 未购买、deepseek 不存在重试管理器把insufficient_quota当普通错误 → 走 L1→L2→L3 重试链 → 全部白费一句话总结差距PPT 承诺了错误分类 熔断 人工介入但代码里没有确定性错误这个概念更没有把它短路到人工的路径。三、补差距从复用 Harness 逻辑到 OpenClaw Plugin复盘结论很明确基础设施级故障要在入口拦截不能等 Agent 自己发现。而 Worker 是 OpenClaw runtime天然支持插件机制——那就把容错能力做成OpenClaw Plugin挂到每次 LLM 调用的必经之路上。3.1 先补错误分类DETERMINISTIC vs TRANSIENT核心洞察错误要分两类处理路径彻底分离TRANSIENT瞬时: 429 / 5xx / timeout / 网络抖动 → 走重试链现状不变 DETERMINISTIC确定: insufficient_quota / AccessDenied / ModelNotFound / 401 → 重试无意义 → 立即熔断 → 短路人工识别规则很简单正则匹配错误消息const DETERMINISTIC_PATTERNS [ { failureType: QUOTA_EXHAUSTED, patterns: [ /quota\shas\sbeen\sexhausted/i, /insufficient_quota/i, /token[-_ ]?plan/i, ]}, { failureType: ACCESS_DENIED, patterns: [/* access denied / unpurchased / forbidden */]}, { failureType: MODEL_NOT_FOUND, patterns: [/* model not exist */]}, // ... ];还有个加分项从错误消息里解析预计恢复时间Your token-plan 1-week quota has been exhausted. The quota will reset at 08-25 01:37:00 UTC. ↑ 提取出来 → 通知人类时告诉TAV8 有个坑new Date(08-25 01:37:00 UTC)会把无年份日期解析成2001 年。解法是先匹配 year-less 格式用Date.UTC(当前年, ...)构造若已过期则自动 1 年。3.2 插件核心三个 Hook 完成探测 → 熔断 → 阻断Worker 的 OpenClaw 版本是2026.4.14这是关键约束——它没有model_call_endedhook那是更新版本才有的。查了该版本的 hook 类型定义// /opt/openclaw/dist/plugin-sdk/src/plugins/hook-types.d.ts llm_input | llm_output | before_agent_reply | before_model_resolve | before_prompt_build | gateway_start | gateway_stop | ...于是用before_prompt_build每次 Agent 准备调 LLM 前都会触发承担核心逻辑before_prompt_build每次模型调用前 │ ├─ 情况 1已熔断stateOPEN │ → 注入上下文「LLM 服务已熔断QUOTA_EXHAUSTED │ 停止所有调用和重试标记 BLOCKED报告 Manager」 │ → Agent 看到后不再发起 LLM 调用 → fail-fast ✅ │ └─ 情况 2未熔断但有连续错误consecutiveErrors ≥ 2 → 主动探测网关发一个 max_tokens1 的请求10s 超时 → 拿到真实错误体 → 分类 → DETERMINISTIC → 熔断 OPEN 写状态文件 人工通知 → TRANSIENT → 重置计数网关其实是通的另外两个辅助 hookllm_output观察输出统计连续错误正常输出则重置计数gateway_start启动时加载共享熔断状态文件跨 Worker 同步熔断状态写入共享文件quota-guard-state.json所有 Worker 都能看到{ state: OPEN, incident: { failureType: QUOTA_EXHAUSTED, errorMessage: Your token-plan 1-week quota has been exhausted..., detectedAt: 2026-08-19T02:26:00.000Z, estimatedRecoveryAt: 2026-08-25T01:37:00.000Z } }恢复路径人工充值 →/quota-guard reset→ HALF_OPEN允许探针→ 探针成功 → CLOSED → 任务从 checkpoint 续跑。3.3 主动探测的必要性为什么不能只看 hook 事件有个设计细节值得记录llm_output拿到的是 sanitized 输出assistantTexts错误路径下根本没有错误消息。而model_call_ended在 2026.4.14 上不存在。所以插件采用主动探测连续 2 次错误后自己发一个最小请求到网关拿真实错误体来分类。这有个探测放大保护30 秒间隔 最多 3 次防止故障本身被探测放大。四、部署实录一路踩坑一路补差距写完插件只是开始。真实环境部署时连续踩了 6 个坑每一个都是文档没写、源码里藏着的细节坑 1WSL 的 docker CLI 连不到 Windows Docker Desktop 的容器docker ps在 WSL 里是空的但端口明明在监听。查了半天发现容器跑在 Windows 侧的 Docker Desktop。解法脚本里自动探测不行就退到 PowerShell 包装DOCKER_RUN() { powershell.exe -NoProfile -Command docker $*; }坑 2hooks.allowConversationAccess配置被拒按文档加了hooks.allowConversationAccess: true热重载直接报[reload] config reload skipped (invalid config): plugins.entries.clawforge-quota-guard.hooks: Unrecognized key2026.4.14 的配置校验不接受这个 key。解法移除——反正探测机制不依赖 llm_output 的完整访问。坑 3共享目录 mode777 → 插件被安全门拦截这是最有价值的一个坑。OpenClaw 对插件加载有安全检查architecture-internals.md里写了但没看仔细[plugins] plugin: blocked plugin candidate: world-writable path (/root/agentteams-fs/shared/knowledge/clawforge/plugins/clawforge-quota-guard, mode777)MinIO 同步出来的目录是 777而 OpenClaw 拒绝从 world-writable 路径加载插件防止恶意写。解法拷贝到 Worker 本地非共享目录/root/clawforge-plugins/。坑 4tar 保留了宿主 uid1000 → suspicious ownershipblocked plugin candidate: suspicious ownership (/root/clawforge-plugins/clawforge-quota-guard, uid1000, expected uid0 or root)tar 打包时把宿主的 uid 带进去了。解法解压时tar --no-same-ownerchown -R root:root。坑 5load.paths指向父目录 → plugin not foundplugins.entries.clawforge-quota-guard: plugin not found: clawforge-quota-guard对照内置插件就明白了/opt/openclaw/extensions/matrix的basenamematrix就是插件 id。所以load.paths要指向插件目录本身不是父目录load: { paths: [ /opt/openclaw/extensions/matrix, // 内置插件basenameid ✅ /root/clawforge-plugins/clawforge-quota-guard // 自定义必须指向插件目录本身 ✅ ]}坑 6worker 的 openclaw.json 会被 MinIO 覆盖本地改 worker 配置 → 重启 → 配置被同步回去覆盖。因为MinIO 的agents/worker/openclaw.json才是配置源。解法用mc直接改 MinIO 上的配置mc cp agentteams/agentteams-storage/agents/code-fixer/openclaw.json /tmp/ocfg.json # python 注入 plugins.entries load.paths mc cp /tmp/ocfg.json agentteams/agentteams-storage/agents/code-fixer/openclaw.json最终验证4 个 Worker 全部注册成功[plugins] [quota-guard] ClawForge Quota Guard plugin registered ✅ [gateway] ready (8 plugins: acpx, browser, clawforge-quota-guard, device-pair, matrix, memory-core, phone-control, talk-voice; 128.9s)最新一次启动0 警告、0 错误。五、收尾这一晚的差距清单#差距现实解法1容错引擎没接进执行链路做成 OpenClaw Plugin挂在before_prompt_build必经之路上2Token 预算 ≠ 账户配额新增错误分类器识别insufficient_quota等确定性错误3确定性错误走重试链分类后短路人工熔断 BLOCKED CRITICAL 通知4文档 hook 与版本不符查 2026.4.14 的 hook 类型定义用兼容的 hook 实现同等效果5插件加载的安全门world-writable / suspicious ownership / load.paths 语义逐个实测确认最深的感悟设计文档里的熔断器重试人工兜底这些词离真正挡住一次故障之间隔着执行链路的接入、版本兼容、安全策略、配置源管理这一整条现实鸿沟。PPT 上写4 层重试 熔断 降级只需要一行字让它真正生效需要把这些差距一个个填平。下一篇预告从“错误分类“到“模型路由“多 Agent 集群的容灾进化。本文为《OpenClaw 源码解读》系列第 23 篇 · 实战篇配套代码C:\Users\ThinkPad\clawforge\plugins\clawforge-quota-guard