OpenClaw 源码解读——入门与破局9 双插件协同:Quota Guard 负责“停“,Model Router 负责“绕“

前两篇我们分别把 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)

决策原则

  1. 确定性错误不重试(quota/权限/模型错误 → 短路人工,不消耗重试次数)

  2. 瞬时错误走重试链(429/5xx/timeout → retry-manager,不受影响)

  3. fail-fast 优先:宁可挂起任务,不空转烧 token

  4. 恢复要人工确认:配额恢复时间确定(如 8/25 01:37),不自动探针

  5. 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.mdquota-guard-manager-integration.md 配套代码:plugins/clawforge-quota-guard/plugins/clawforge-model-router/ 验证记录:docs/verification/element-web-verification-checklist.md

上一篇:从"报错不切换"到"秒级自动切换":模型路由插件五次迭代实录

相关推荐
成愈秀1 小时前
Supervlint:一个测量“人类监督者是否还具备接管能力“的工具
ai编程
CHPCWWHSU1 小时前
llama.cpp + DeepSeek-Harness 构建本地大模型推理与Agent智能体系统
llama
deli0070071 小时前
汉诺塔益智小游戏:浏览器里说句话,码道 WebUI 一键生成+部署上线
前端·ai编程
pqpo2 小时前
Agent Team 的上下文工程设计:如何组织和共享上下文
agent·ai编程
zhangfeng11332 小时前
HiDevLab vCANNLab(昇腾两个云端WebIDE)安装codebuddy
人工智能·ai编程·算子开发
leeyi2 小时前
Agent 间 Transfer 交接:用户在不同 Agent 间无缝切换(第93篇-E79)
人工智能·aigc·agent
这就是佬们吗2 小时前
治幻觉,先治检索:RAG 系统防幻觉的完整工程指南
python·langchain·embedding
爱奥尼欧2 小时前
14.输出解析器-Pydantic与JSON
人工智能·学习·langchain·json
李剑一2 小时前
前端转AI要了解的技术,其他人不用看。前端架构基础之:让Js的计算运行在GPU上
前端·aigc·ai编程