前两篇我们分别把 Quota Guard(熔断)和 Model Router(切换)挂进了 Worker 执行链路。 这一篇讲它们同时在 Worker 里工作时怎么分工、怎么配合,以及 Manager 侧如何消费 插件状态做任务编排------把"基础设施故障"变成"任务自动降级 + 人工精准介入"的完整闭环。
核心一句话:一个负责"停",一个负责"绕"。熔断是刹车,路由是变道。
一、开场:为什么需要两个插件而不是一个
8/19 故障的教训是"确定性错误被当超时无限重试"。当时我们做了 Quota Guard(熔断 fail-fast): 欠费 → 立即阻断 → 任务 BLOCKED → 通知人工。
但复盘时意识到一个更深的问题:熔断只是"停下来",不是"继续干活"。 配额耗尽时,如果集群配置了多个模型(A 欠费了还有 B、C),为什么不自己切过去?
于是有了第二个插件 Model Router:先切换,全挂才上报。
两者的哲学差异:
Quota Guard = 单服务熔断:确定性错误 → fail-fast 阻断空转 → 人工介入
Model Router = 多模型容灾:先自动切换可用模型,全挂才上报人工
一个负责"停"(刹车),一个负责"绕"(变道)
为什么不能合并成一个插件?因为两者的决策依据不同:
-
Quota Guard 判断"这个服务/账号是否还有救"(配额/权限状态),它管的是"要不要继续烧钱"
-
Model Router 判断"这个模型是否还能用",它管的是"换哪个模型继续干活"
-
触发时机也不同:Quota Guard 在熔断阈值/确定性错误时动作,Model Router 在每次调用失败时评估
二、Worker 侧协同:两个插件如何在同一执行链路上共存
2.1 Hook 布局(2026.4.14 实机)
两个插件都挂在 OpenClaw 的 LLM 调用必经之路上,但各自分管不同的 hook:
| Hook | Quota Guard | Model Router |
|---|---|---|
before_model_resolve |
--- | 改写模型为当前可用模型(路由) |
before_prompt_build |
熔断状态检查 + fail-fast 上下文注入 | 失败推断 + 探测 + 切换触发 |
llm_output |
输出统计(成功重置计数) | 成功确认(assistantTexts 非空才认) |
gateway_start/stop |
状态文件加载/持久化 | 状态文件加载/持久化 |
实际注册后 gateway ready 输出:9 plugins(双插件 + 内置 7 个),0 警告 0 错误。
2.2 状态文件体系:三个文件各管一段
/root/agentteams-fs/shared/clawforge/
├── quota-guard-state.json # 熔断状态:OPEN/CLOSED + incident + 预计恢复时间
├── model-router-state.json # 路由状态:activeModel + unavailable 列表 + switchCount
└── model-router-notification.json # 全挂通知:CRITICAL + 每个模型的故障明细
-
quota-guard-state.json:单一"总闸"状态,跨 worker 共享,任何 worker 探测到确定性错误都写这里
-
model-router-state.json:路由决策状态,跨 worker 共享,active 模型 + 哪些模型被标记不可用
-
model-router-notification.json:全挂时的告警载体(Manager 消费)
2.3 组合链路(故障时的完整剧本)
模型 A 欠费(400 Access denied)
│
├─ Model Router:探测 → QUOTA_EXHAUSTED → 标记 A 不可用 → active 切到 B
│ ↓ 状态文件:model-router-state.json(unavailable: [A])
│
├─ B 也欠费(超时形态)→ 连续失败计数 → 3 次后切 C
│
├─ C 也挂 → 全挂 → model-router-notification.json(CRITICAL)
│ ↓
└─ Quota Guard(兜底):如果错误是账号级(所有模型同账号),
探测发现全链路不可用 → 熔断 OPEN → 后续调用 fail-fast(毫秒级拒绝,不空转)
分工的价值 :Model Router 优先"绕"(能切就切,任务继续);只有绕不动了(全挂), Quota Guard 的熔断才真正拉闸,避免"全挂了还在反复探测"。两者不是竞争,是接力。
三、实机验证:Model Router 的"绕"已通过真实故障
2026-08-23 真实故障验证(两跳链路):
消息 1:qwen3.6-plus → HTTP 400 欠费
消息 2:探测 → QUOTA_EXHAUSTED → 🔴 切换至 MiniMax-M3 → 正常回复 ✅
状态文件:switchCount=1,unavailable=[qwen3.6-plus]
链式场景(欠费 → 不存在的模型 404 → 正常)同样验证通过,两次切换实锤。
Quota Guard 的熔断行为已通过 202 个单元测试 + 4 worker 注册验证; 真实故障触发的 fail-fast 演练留待后续(8/19 后的环境未再出现账号级配额耗尽)。
四、Manager 侧:消费插件状态做任务编排
插件负责"探测和动作",Manager 负责"编排和恢复"。完整流程(对应 docs/architecture/quota-guard-manager-integration.md):
4.1 检测熔断(轮询 / 事件订阅)
每 60s 检查共享状态文件:
IF quota-guard-state.json == OPEN 或 model-router-notification.json 存在
→ 进入"基础设施故障模式"
4.2 任务联动:RUNNING/QUEUED → BLOCKED
扫描 active_tasks:
对每个 RUNNING / QUEUED 任务:
→ 状态转换 quota_block(state-machine 的 BLOCKED 转换)
→ 标记 degradationFlags += QUOTA_BLOCKED
→ 写入错误信息:LLM quota/access error — waiting for human intervention
通知各 Worker:暂停当前任务执行(避免继续空转)
4.3 人工通知(CRITICAL)
标题:[ClawForge] LLM 服务故障:QUOTA_EXHAUSTED
正文:
- 故障类型 + 错误消息 + 检测时间 + 预计恢复时间
- 受影响任务列表(task_id × N)
- 已自动熔断:所有新 LLM 调用被阻断
建议动作:充值 / 切换备用模型 / 更新 API Key,处理后回复确认恢复
4.4 人工确认 → 恢复
收到人工确认(充值完成):
1. 通知各 Worker 插件执行恢复(quota-guard reset → HALF_OPEN)
2. Worker 探针成功 → CLOSED
3. 对 BLOCKED 任务执行 human_unblock 转换:
→ BLOCKED → RUNNING,从最后 checkpoint 续跑(不丢上下文)
4. 清除 QUOTA_BLOCKED 标记,恢复调度
4.5 状态机新增转换(已实现)
| 事件 | 转换 | 触发方 |
|---|---|---|
quota_block |
RUNNING/QUEUED → BLOCKED | Manager(检测到熔断) |
human_unblock |
BLOCKED → RUNNING | Manager(人工确认后) |
timeout_block |
BLOCKED → FAILED | Manager(BLOCKED 超时未恢复 → DLQ) |
决策原则:
-
确定性错误不重试(quota/权限/模型错误 → 短路人工,不消耗重试次数)
-
瞬时错误走重试链(429/5xx/timeout → retry-manager,不受影响)
-
fail-fast 优先:宁可挂起任务,不空转烧 token
-
恢复要人工确认:配额恢复时间确定(如 8/25 01:37),不自动探针
-
BLOCKED 有超时保护:长时间未恢复 → FAILED → DLQ(防无限挂起)
五、收尾:这一轮的"分工"清单
| 层 | 组件 | 职责 | 状态 |
|---|---|---|---|
| Worker 检测 | Quota Guard 插件 | 熔断(停) | ✅ 部署 + 202 测试 |
| Worker 切换 | Model Router 插件 | 路由(绕) | ✅ 部署 + 真实故障验证 |
| 状态共享 | 3 个状态文件 | 跨 worker 一致性 | ✅ 实机工作 |
| Manager 编排 | task-orchestration | BLOCKED 联动 + 通知 | 📝 设计完成(manager-integration.md),接入待联调 |
| 状态机 | state-machine | quota_block / human_unblock | ✅ 已实现 |
| 一键部署 | 2 个 deploy 脚本 | 环境适配 + 自动验证 | ✅ 实机可用 |
最深的感悟 :容灾设计不是"一个万能组件",而是一组职责正交的组件接力------ 熔断器管"停",路由管"绕",Manager 管"恢复",状态文件管"共识"。 每一层只回答一个问题,组合起来才覆盖"故障发生 → 止损 → 绕行 → 恢复"的完整生命周期。 单独看任何一个都不完整,串起来才是系统。
下一篇预告:AgentTeams 环境治理------版本对齐、配置真相源(CR vs MinIO vs 本地)与 "官方镜像不可定制"问题的源码编译路线,以及 ClawForge 如何在这些约束下演进。
本文为《OpenClaw 源码解读》系列第 22 篇 · 实战篇 配套文档:docs/architecture/quota-guard-integration.md、quota-guard-manager-integration.md 配套代码:plugins/clawforge-quota-guard/、plugins/clawforge-model-router/ 验证记录:docs/verification/element-web-verification-checklist.md