Claw Cost Router
Auto-route Claude models (Opus/Sonnet/Haiku) per message in OpenClaw — cut API costs without sacrificing skill quality.
Audits
PassInstall
openclaw plugins install clawhub:claw-cost-routerClaw Cost Router
Auto-route Claude models (Opus / Sonnet / Haiku) per message in OpenClaw — cut API costs without sacrificing quality.
Why this exists
OpenClaw runs every agent turn on a single configured model. If you default to Claude Opus for skill quality, you pay Opus pricing for trivial chat ("ok", "thanks", "what time is it?"). If you default to Sonnet to save money, your carefully-designed skills lose quality.
Model Router fixes this by inspecting each incoming message and choosing the right model dynamically:
| Trigger | Routed to | Why |
|---|---|---|
/heavy-skill ... | Opus 4.6 | Skills were authored at Opus quality; don't downgrade |
/lightweight-skill ... | Haiku 4.5 | Pure formatting / notify tasks — Haiku is 4× cheaper |
| Anything else (chat) | Sonnet 4.6 | The default. Plenty for conversation, ~5× cheaper than Opus |
In practice this cuts our monthly bill by 70-85% while keeping skill execution at Opus quality.
How it works
OpenClaw's plugin SDK ships a before_model_resolve hook — officially designed for file-aware model routing (e.g. "messages with images go to a vision model"). This plugin extends that idea to content-aware routing: peek at the user's message, detect a slash command, return a modelOverride.
api.on("before_model_resolve", async (event, ctx) => {
const prompt = extractUserMessage(event.prompt);
const skillName = prompt.match(/^\/([a-z][a-z0-9_-]*)/i)?.[1];
if (opusSkills.has(skillName)) return { modelOverride: "claude-opus-4-6", providerOverride: "anthropic" };
if (haikuSkills.has(skillName)) return { modelOverride: "claude-haiku-4-5", providerOverride: "anthropic" };
return { modelOverride: "claude-sonnet-4-6", providerOverride: "anthropic" };
});
Install
# 1. Clone the repo
git clone https://github.com/anthony1232123-create/claw-cost-router.git
cd claw-cost-router
# 2. Install as a linked plugin (development-friendly: edits reload on SIGUSR1)
openclaw plugins install --link .
# 3. Restart the gateway
openclaw gateway restart
Verify it loaded:
openclaw plugins list | grep claw-cost-router
# → Model Router | enabled | ./index.js
Configure
Edit ~/.openclaw/openclaw.json and add a config block under plugins.entries.claw-cost-router:
{
"plugins": {
"entries": {
"model-router": {
"enabled": true,
"config": {
"defaultModel": "claude-sonnet-4-6",
"opusSkills": ["my-creative-skill", "my-analysis-skill"],
"haikuSkills": ["my-notify-skill", "my-format-skill"],
"logging": true
}
}
}
}
}
After editing, reload the gateway:
kill -USR1 $(lsof -tiTCP:18789 -sTCP:LISTEN)
Config reference
| Field | Type | Default | Purpose |
|---|---|---|---|
enabled | boolean | true | Master switch |
defaultModel | string | claude-sonnet-4-6 | Fallback when no skill matches |
opusModel | string | claude-opus-4-6 | Model used for Opus-routed skills |
haikuModel | string | claude-haiku-4-5 | Model used for Haiku-routed skills |
opusSkills | string[] | (see defaults) | Slash commands that must run on Opus |
haikuSkills | string[] | (see defaults) | Slash commands fine on Haiku |
logging | boolean | true | Emit [plugins] claw-cost-router: ... info logs |
The model override only sets modelOverride and providerOverride (Anthropic). The base model stays whatever you've configured in agents.defaults.model.primary — if this plugin fails or is disabled, OpenClaw falls back to that base.
Observability
Every decision is appended to ~/.openclaw/plugins/claw-cost-router/decisions.jsonl:
{"time":"2026-05-13T01:30:07Z","channel":"slack:C0A...","trigger":"user","promptHead":"/recruit-creative 株式会社...","matchedSkill":"recruit-creative","modelChosen":"claude-opus-4-6"}
{"time":"2026-05-13T01:30:57Z","channel":"slack:C0A...","trigger":"user","promptHead":"おはよう","matchedSkill":null,"modelChosen":"claude-sonnet-4-6","note":"chat"}
Use this to:
- Audit routing decisions retroactively
- Discover skills that get used in unexpected ways
- Decide whether currently-unclassified skills should be promoted to Opus or demoted to Haiku
Quick analysis:
# Top 10 most-routed skills
jq -r '.matchedSkill // "(chat)"' ~/.openclaw/plugins/claw-cost-router/decisions.jsonl | sort | uniq -c | sort -rn | head -10
# Cost breakdown by model
jq -r '.modelChosen' ~/.openclaw/plugins/claw-cost-router/decisions.jsonl | sort | uniq -c
Prompt extraction across channels
OpenClaw wraps the user's message differently per channel. This plugin handles all of them:
| Channel | Wrapper pattern | Plugin extracts |
|---|---|---|
CLI (openclaw agent) | [Wed YYYY-MM-DD HH:MM GMT+9] /skill ... | strip timestamp |
| Slack | System (untrusted): [TS] Slack message in #ch from User: <@bot_id> (name) /skill ... | strip header, mention, formatting marks |
| Telegram | similar to Slack | same |
| Slack-formatted slash | * ` /skill arg ` * (Slack auto-wraps / messages) | strip leading/trailing markdown |
If your channel has a different envelope, add a regex in the extractUserMessage block of index.js.
Caveats
- Plugin failures fall through to base model. If the hook throws, OpenClaw uses
agents.defaults.model.primary. Configure this defensively — typically set base to Opus (safe quality fallback) or Sonnet (safe cost fallback) per your risk tolerance. - Session continuity. Each agent turn re-runs the hook; if a user mid-conversation switches from chat to a skill invocation, the model swaps mid-thread. OpenClaw's session machinery handles this cleanly.
- No regex on
event.attachmentsyet. File-aware routing (the original use case for this hook) isn't combined with our content rules — easy to add if you need it. - The default skill lists are opinionated. They reflect one user's recruiting + automation workspace. Override
opusSkills/haikuSkillsin config.
Roadmap
- Read rules from
openclaw.jsonconfig (✅ done) - Disk-based decision log (
decisions.jsonl) (✅ done) - Keyword-based rules (e.g. "if message contains 'implement' → Opus")
- Channel-scoped rules (e.g. "Slack
#designchannel → Opus") - Time-of-day rules
- Auto-suggest rule updates from decision log analysis
Development
Edit index.js, then signal the gateway to reload:
kill -USR1 $(lsof -tiTCP:18789 -sTCP:LISTEN)
Linked installs (--link) pick up edits immediately on reload — no reinstall needed.
Watch live decisions:
tail -f ~/.openclaw/plugins/claw-cost-router/decisions.jsonl | jq .
License
MIT — see LICENSE.
Credits
Built by Michael Pruneda with his AI pair Isaac (Claude Code). Inspired by needing to keep Claw running on Claude Opus quality without burning down the API budget.
If this saved you money, ⭐ the repo. If you find a bug or want a feature, open an issue.
