Claw Cost Router

Auto-route Claude models (Opus/Sonnet/Haiku) per message in OpenClaw — cut API costs without sacrificing skill quality.

Audits

Pass

Install

openclaw plugins install clawhub:claw-cost-router

Claw Cost Router

Auto-route Claude models (Opus / Sonnet / Haiku) per message in OpenClaw — cut API costs without sacrificing quality.

License: MIT OpenClaw

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:

TriggerRouted toWhy
/heavy-skill ...Opus 4.6Skills were authored at Opus quality; don't downgrade
/lightweight-skill ...Haiku 4.5Pure formatting / notify tasks — Haiku is 4× cheaper
Anything else (chat)Sonnet 4.6The 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

FieldTypeDefaultPurpose
enabledbooleantrueMaster switch
defaultModelstringclaude-sonnet-4-6Fallback when no skill matches
opusModelstringclaude-opus-4-6Model used for Opus-routed skills
haikuModelstringclaude-haiku-4-5Model used for Haiku-routed skills
opusSkillsstring[](see defaults)Slash commands that must run on Opus
haikuSkillsstring[](see defaults)Slash commands fine on Haiku
loggingbooleantrueEmit [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:

ChannelWrapper patternPlugin extracts
CLI (openclaw agent)[Wed YYYY-MM-DD HH:MM GMT+9] /skill ...strip timestamp
SlackSystem (untrusted): [TS] Slack message in #ch from User: <@bot_id> (name) /skill ...strip header, mention, formatting marks
Telegramsimilar to Slacksame
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.attachments yet. 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 / haikuSkills in config.

Roadmap

  • Read rules from openclaw.json config (✅ done)
  • Disk-based decision log (decisions.jsonl) (✅ done)
  • Keyword-based rules (e.g. "if message contains 'implement' → Opus")
  • Channel-scoped rules (e.g. "Slack #design channel → 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.