model-guard · diff
git:20261028.49dc8fc to git:20261028.7e77bad
4 added, 3 removed. Audit A to A.
---
name: model-guard
description: |
Budget guard for top-tier expensive models (Claude Code Fable, Codex astra): keeps one session from burning the whole plan quota and stalling every other session. Below the reserve line it is a soft CLAUDE.md/AGENTS.md rule (expensive model only thinks, plans and reviews; heavy work goes to cheap subagents); once 5h/7d remaining quota drops under a time-decaying reserve, a PreToolUse hook enforces the split. Triggers: install model guard, fable guard, 装模型预算闸, 贵模型保护, 更新贵模型列表, 改保留额, 卸载模型预算闸.
trigger: /model-guard
compatibility: Claude Code 2.1.301+(套餐用户), Codex CLI 0.163+(hook 需用户信任一次)
license: MIT
---
# model-guard:贵模型预算闸
使用方式始终一套:贵模型只做思考、方案、审阅,重活派便宜 subagent。套餐余量充足时这是 CLAUDE.md 里的软提示;余量越过保留线后,PreToolUse hook 把它变成硬约束(强制分工,不是禁用贵模型)。
## 机制
| 组件 | 作用 |
| --- | --- |
| `scripts/statusline-tap.sh` | 包在原 statusLine 外层:把 stdin 里的 `rate_limits` 落盘到 `~/.claude/model-guard/rate-limits.json`,再把原 stdin 交给原命令 |
| `scripts/guard.sh` | PreToolUse hook(matcher `*`),Claude Code 与 Codex 共用:当前模型是贵模型且越线时,只放行派便宜 subagent、提问、计划类工具(Claude Code 另放行 limit≤200 的 `Read`) |
| `scripts/install.sh` | 安装、更新、现状、自检、卸载;幂等,改 settings.json 前备份 |
| `~/.claude/model-guard.json` | 贵模型/便宜模型列表、保留额与保留线下限、Codex 便宜模型与工具名清单、被包住的原 statusLine |
触发线随时间走:对 5h、7d 窗口各算 `remaining = clamp(距重置秒数 / 窗口秒数, 0, 1)`,保留线 `= max(保留额 × remaining, min(下限, 保留额))`,余量 `100 - 已用%` 小于保留线即越线,任一窗口越线就进入强制分工。下限(`reserve_floor_5h` / `reserve_floor_7d`,默认 15%)防止临近重置时保留线趋近 0:没有下限时 7d 已用 96% 还剩 1 小时,保留线只有约 0.3%,闸不触发,额度照样被烧光。实际生效的下限是 `min(下限, 保留额)`,`--status` 与软提示显示的也是这个值;保留额设 0 仍等于关闭该窗口的闸。
- Claude Code 越线后 `Read` 只放行带 limit≤200 且不带 pages 的读取;用 offset 分段连续读仍可读完整文件,这是有意保留的小口子,大段阅读应派 subagent。模型判定取 transcript 最后一条 assistant 消息(实测首个工具调用时本条消息已写入);`/model` 切换后的第一次调用可能仍按上一条消息的模型判定。判定结果按 transcript 缓存在 `~/.claude/model-guard/model-cache/`(模型名、模型所在行起点、已扫到的偏移、这两点之间整段字节的 sha1)。缓存结论只依赖这段字节,命中时重算哈希,对不上(含等长重写)就退回整段倒读;之后每次只扫新追加的行,新行里有 assistant 消息就以最新的为准。文件变短、缓存损坏或要读的区间超过 8MB 时同样退回整段倒读。缓存防的是大 transcript 每次倒读超过 hook 的 5 秒超时(实测曾 5.2 秒)。
+ Claude Code 越线后 `Read` 只放行带 limit≤200 且不带 pages 的读取;用 offset 分段连续读仍可读完整文件,这是有意保留的小口子,大段阅读应派 subagent。模型判定取 transcript 最后一条 assistant 消息(实测首个工具调用时本条消息已写入);`/model` 切换后的第一次调用可能仍按上一条消息的模型判定。
宿主按 hook 输入自动识别:带 `model` 字段的是 Codex,模型直接取该字段,用量取本 session rollout 里最后一条 `token_count` 的 `rate_limits`(`window_minutes`≤360 按 5h 口径,否则按 7d);否则是 Claude Code,模型取 transcript 末尾 assistant 消息,用量取状态栏落盘文件。Codex 没有状态栏命令,不需要旁路。
用量数据缺失、超过 6 小时未更新、或非套餐用户(`rate_limits` 为 null)时一律放行。subagent 内的工具调用按 subagent 自己的模型判定,便宜 subagent 不受影响。
## 执行步骤
### 1. 展示现状
```bash
bash scripts/install.sh --status
```
把输出的现状表原样给用户:是否已装、模型列表、保留额、hook / 状态栏旁路 / 软提示是否就位、Codex 状态、当前用量是否已触发。`python3` 缺失时脚本直接报错,停止并提示安装。
### 2. 用 AskUserQuestion 一次问两件事
- **保留额**:推荐默认 5h 40% / 7d 50%(给其它 session 留的余量),保留线下限默认 5h/7d 各 15%;可自定 0-100。已安装时以现有值为推荐项。
- **贵模型列表**:默认 `fable`、`astra`(模型名子串,大小写不敏感);可增删。
用户只是"更新贵模型列表"或"改保留额"时,只问对应的一项。
### 3. 一次性请求授权
列出将改动的文件,等用户确认:
- `~/.claude/settings.json`:注册 PreToolUse hook、包一层状态栏(改前自动备份为 `settings.json.bak-<时间戳>`)
- `~/.claude/CLAUDE.md`:写入 `<!-- model-guard:start -->` 标记块
- `~/.codex/hooks.json`:仅当 `~/.codex` 存在时,注册同一个 guard.sh(改前备份)
- `~/.codex/AGENTS.md`:仅当 `~/.codex` 存在时;若 CLAUDE.md 软链到它,只写一次
### 4. 执行安装
```bash
bash scripts/install.sh --models fable,astra --reserve-5h 40 --reserve-7d 50 --reserve-floor-5h 15 --reserve-floor-7d 15
```
未传的参数沿用已有配置。重跑即更新:hook 与状态栏不会重复注册,标记块按块替换。
### 5. 自检
```bash
bash scripts/install.sh --selftest
```
确认 settings.json(及 Codex hooks.json)已注册可执行的 guard.sh,并在临时目录伪造贵模型 transcript 与越线用量,验证两种宿主下 Bash 被拒(exit 2)、派便宜子 agent 放行。任一项失败即如实报告,不得说已完成。
Codex 只执行用户信任过的 hook。`--status` 显示"已注册,✗ 未信任"时,告诉用户:下次启动交互式 Codex 会提示 Hooks need review,选 Review hooks 或 Trust all and continue;`codex exec` 不提示,未信任的 hook 静默不执行。不要替用户写信任登记。
### 6. 汇报
给出:改动的文件(绝对路径)、备份文件路径、最终模型列表、保留额与下限、自检结果、Codex 信任状态。提醒用户 Claude Code 要等状态栏下次刷新才有用量数据,Codex 要等每个 session 跑完第一轮,此前 hook 放行。
## 卸载
```bash
bash scripts/install.sh --uninstall
```
移除 settings.json 与 Codex hooks.json 中的 hook、还原原 statusLine、删除软提示标记块,保留 `~/.claude/model-guard.json` 供重装沿用。执行前同样列出改动文件请求确认。
## 被拦时怎么办
拒绝原因会回灌给模型,含当前模型、5h/7d 用量与剩余时间、越过了哪条线。按提示三选一:
- Claude Code:派 subagent,Agent 工具显式传 `model=opus|sonnet|haiku`,禁止 `subagent_type=fork`
- Codex:spawn_agent 显式传便宜模型(默认 sol/luna/terra/reserve),不传会继承贵模型
- `/model` 切到便宜模型继续
- `/compact` 减小上下文,降低每轮消耗
## 错误处理
| 情况 | 处理 |
| --- | --- |
| settings.json 不是合法 JSON | 安装拒绝修改并退出,请用户手工修复后重跑 |
| 保留额或下限超出 0-100、模型列表为空 | 安装拒绝,配置不变 |
+ | `model-guard.json` 里的保留额或下限被手改成非法值(字符串、布尔、null、越界) | 运行时按默认值生效,闸不失效;`--status` 显示生效值并标注「配置非法」;重装时提示用对应参数重新指定 |
| `--status` 显示"用量数据无或已过期" | 正常:等状态栏刷新;API key 用户永远没有数据,只有软提示生效 |
| 用户之后自己换了 statusLine | 重跑安装会把新命令当作原命令重新包住 |
| Codex hook 不生效 | `--status` 查信任状态;未信任时按上文在交互式 Codex 里信任一次 |
| Codex 工具名随版本变化 | 改 `model-guard.json` 的 `codex_allowed_tools` / `codex_agent_tools`;默认值按 codex-cli 0.163.0 实测(如 `collaborationspawn_agent`) |
## 测试
```bash
- bash tests/test_guard.sh # 规则矩阵:两种宿主的越线、窗口首尾与保留线下限、模型名缓存与 /model 切换、子 agent 规则、subagent 判定、fail open
- bash tests/test_install.sh # 临时 HOME:幂等、软链、状态栏旁路、Codex 注册与信任检测、更新、卸载还原
+ bash tests/test_guard.sh # 规则矩阵:两种宿主的越线、窗口首尾与保留线下限、子 agent 规则、subagent 判定、fail open
+ bash tests/test_install.sh # 临时 HOME:幂等、软链、状态栏旁路、Codex 注册与信任检测、更新、百分比配置与参数的坏值类别测试、卸载还原
```