Smart Routing
Smart routing runs an agent's background turns — scheduled tasks, companion outbox generation, anything the runtime starts on its own — on a cheaper model, and escalates to the agent's own model if that fails. It never touches a turn you are watching.
It is off by default. Turn it on when you want it:
mur model smart on # global; cheap model auto-picked
mur model smart on --cheap deepseek_v4_flash # or pin the cheap model
mur model smart off
mur agent smart researcher on # this agent, regardless of the global
mur agent smart researcher off
mur agent smart researcher follow # clear the override, inherit the global
What it will not do
A router may only substitute a model that can serve the request. Price orders the eligible set; it does not decide who is in it.
That rule exists because the alternative is silent. A scheduled image-recognition
turn once got handed to a cheap text-tier model: the picker ranked candidates by
cost and never asked whether they could see. Nothing downstream catches it —
escalation triggers on a malformed reply, and a confidently wrong recognition
is perfectly well-formed — and the routing caption only renders in Hub chat,
which a scheduled turn never reaches. (You can now read those decisions back
with mur agent routing, which is the other half
of the fix.)
So before substituting, MUR reads what the request actually needs:
| The request carries | The substitute must |
|---|---|
| an image | declare vision in its registry capabilities |
| a tool list | not have declared capabilities that omit tools |
The two are treated differently on purpose, and the difference follows how each one fails:
- Vision — silence disqualifies. A model that cannot see answers with confident nonsense. That is silent and unrecoverable for that turn, so an entry that says nothing about vision is assumed not to have it. No provider catalog fills this in yet, which means image requests currently find no cheap candidate at all and simply run on the agent's own model.
- Tools — silence is permitted. A model that cannot call tools is rejected
by the provider, loudly, and the fallback chain advances on its own. Treating
silence as incapacity would drop every entry written before
capabilitiesexisted — most of a real registry — out of the chain of every tool-carrying turn.
An entry that does list capabilities is taken at its word either way:
enumerating what you can do and leaving tools out is a statement, not silence.
What is never filtered
Your explicit choices. The agent's model_ref, the global models.default, and
a pinned re-run all go through untouched — if one of them cannot serve the
request, it fails loudly rather than being quietly swapped. Filtering applies
only to models MUR chose: the Smart cheap model, a difficulty-routed pick,
and fallback-chain members.
If filtering leaves nothing, the request fails with the real error. A router that answers an image question with a blind model is worse than one that stops.
Seeing what the router did
The capability gate stops the failure MUR can recognise: a model that cannot serve the request. It cannot tell you the cheap model was simply worse at something — no automated check can, without paying to run the turn twice. For that you need to be able to look.
mur agent routing researcher # newest 20 decisions
mur agent routing researcher --downgrades-only # only turns Smart chose for you
mur agent routing researcher --limit 100
TIME INTENT MODEL REASON OUTCOME TASK
2026-09-01T13:55:0… background/scheduled ↓deepseek-v4-flash smart-background ok the scheduled turn's prompt…
2026-09-01T02:00:0… background/scheduled ↓deepseek-v4-flash smart-background ok …
1850 routing decisions · ↓ 4 chosen for you by Smart, on deepseek-v4-flash
↓ marks a model MUR chose. fallback-advance and explicit are not
downgrades — those are your own configuration taking effect after a failure or
a pin.
The summary line counts every decision on disk, not the rows printed. A "no downgrades" line that spoke only for the last twenty turns would be the same kind of half-truth the command exists to remove.
This reads ~/.mur/agents/<name>/telemetry/*.jsonl, which the runtime has been
writing all along. Nothing new is recorded; what was missing was a way to read
it outside the Hub chat caption that background turns never reach.
Per-agent inheritance
An agent has three states, and follow is a real one rather than something
inferred from a blank field:
mur agent smart <name> … | Effect |
|---|---|
follow | inherit models.smart (the default for every agent) |
on | Smart on for this agent, whatever the global says |
off | Smart off for this agent, whatever the global says |
Overrides merge field by field, so setting one field never resets the
others: pinning a cheap model for one agent does not silently disable Smart for
it. The same applies to the difficulty-routing override in models.routing.
The Hub shows the same three states under an agent's model settings.
Configuration
# ~/.mur/config.yaml
models:
smart:
enabled: false # opt-in
cheap: null # null = auto-pick the cheapest eligible chat model
max_escalations: 1 # escalate at most once on a structural failure
# ~/.mur/agents/<name>/profile.yaml — every field optional, absent = inherit
smart:
enabled: true
Profiles written before this field existed carry the override nested under
routing.smart. That location is still read, permanently — exported
.muragent bundles contain it — and the first time you set the value through
the CLI or Hub it is migrated up, so the setting ends up living in one place.
Related
- Model Registry — where
capabilitiesand pricing live - Per-Stage Model Routing — a different axis: which model each pipeline stage uses