Model Registry
Agents don't name a model. They name a registry key, and the key points at
the model. That one layer of indirection is what makes provider churn survivable:
when Anthropic, OpenAI or Google renames or retires a model id โ which happens
continuously โ you edit one entry in ~/.mur/models.yaml and every agent
pointing at that key moves with it. No per-agent migration.
mur model connect anthropic # one key, many models (see below)
mur model list # every registered key
mur model show claude_opus # provider, model, effective cost, context window
mur model doctor # offline audit โ see below
API keys are stored as SecretRefs (env:, keychain:, file:, cmd:), never
written to config in plaintext.
Connecting a provider
You rarely want to register models one at a time. mur model connect onboards a
credential, then lets you pick from that provider's models:
mur model connect anthropic # prompts for the key โ macOS Keychain,
# lists the models, you pick
mur model connect openai --all # take everything, no prompt
mur model connect deepseek --base-url https://api.deepseek.com --pick 1,3-5
mur model connect # no vendor: probe local runtimes
# (Ollama ยท MLX ยท LM Studio)
Picked models are written with pricing and context window already filled in. If the provider already has a key in the registry, connect reuses that reference instead of asking again.
Where the model list comes from depends on what the endpoint can answer:
| You connect | List comes from | Why |
|---|---|---|
A known cloud vendor (anthropic, openai, deepseek, โฆ) | the models.dev catalog | A vendor's configured endpoint is often a chat-only proxy that forwards /v1/models untouched โ probing it returns an auth error even with a valid key |
A custom endpoint (--base-url, unknown vendor) | live GET /v1/models | Only that endpoint knows which models it actually serves |
| No vendor at all | local runtime probe | Ollama, MLX and LM Studio answer on well-known ports |
A vendor that isn't anthropic or openai needs --base-url, because those two
are the only wire protocols the runtime speaks natively. Everything else is
registered as provider: openai โ the protocol โ with the vendor recorded
alongside it:
deepseek_deepseek_chat:
provider: openai # how MUR dials it
vendor: deepseek # who makes it โ what pricing looks up
model: deepseek-chat
base_url: https://api.deepseek.com
Two fields because they answer two questions. provider: picks a client, and
the runtime ships four (local, ollama, anthropic, openai); putting a
vendor name there gives you an entry nothing can dial. vendor: is what the
models.dev catalog is keyed by, and it is why the entry above arrives priced.
It is written only when the two differ โ for Anthropic, OpenAI and Ollama the protocol already names the vendor. Entries written before the field carry neither problem nor benefit: the vendor is inferred from the endpoint host instead, so older registries keep pricing correctly.
Pointing an agent at a key
An agent's profile.yaml carries model_ref โ the registry key it resolves at
run time:
model_ref: claude_sonnet
fallback_chain: [claude_sonnet, gpt5] # tried in order when the primary fails
Renaming a model is therefore a registry edit, not a fleet-wide change:
# provider renamed claude-sonnet-4-6 โ claude-sonnet-5
mur model add claude_sonnet --provider anthropic --model claude-sonnet-5
# every agent with model_ref: claude_sonnet now runs the new id
mur model doctor
An offline, read-only audit of the registry and every installed agent. It runs against the cached price catalog and makes no network calls.
mur model doctor
# ok โ 6 registry entries, 27 agents, nothing inconsistent
It reports three kinds of divergence:
| Finding | Level | Meaning |
|---|---|---|
model_ref '<key>' is not in models.yaml | error | the agent cannot dial at all |
profile.yaml model: says X but model_ref resolves to Y | warn | the ref is what runs; the legacy block is stale |
<id> is not in the price catalog under <vendor> | warn | likely a typo, or provider: naming the wrong vendor |
It never rewrites a model id. Which model an agent runs is a cost and behaviour decision you made, and silently "upgrading" a renamed id would change your spend and your output quality without asking. The doctor tells you; the edit stays yours.
What the catalog check does not prove
It is not a deprecation check. The models.dev catalog keeps historical
entries โ at the time of writing it lists claude-sonnet-4-6 and
claude-sonnet-5 side by side โ so an id still appearing there says nothing
about whether the provider still serves it. Answering that would need a live
/v1/models call against every endpoint, which this command deliberately does
not make.
What it does catch is an id the catalog has never carried: a typo, a
copied-wrong id, or a provider: field used as a vendor name when it is really
a wire protocol. That last case is common โ DeepSeek is reached over the OpenAI
protocol, so its entry reads provider: openai while the catalog files it under
vendor deepseek. The check reads the entry's recorded vendor: first, falls
back to inferring one from base_url, and only then tries provider, so
OpenAI-compatible third parties are not flagged wholesale.
Two blind spots worth knowing:
- An entry pointed at a local proxy (such as the
Model Gateway on
127.0.0.1:8088) is exempt from the catalog check, because the proxy hides which vendor is upstream. A retired id behind one is invisible here. - A local runtime (Ollama, MLX, LM Studio) is in no public catalog by definition, so it is never flagged for being absent from one.
Pricing
Cost per 1k tokens and the context window are auto-filled from the models.dev catalog when you add an entry:
mur model add gpt5 --provider openai --model gpt-5.2 # auto-filled
mur model add local --provider openai --model qwen --no-fetch # skip the lookup
mur model prices refresh # refresh the cache
mur model prices show
Set them by hand with --input-cost / --output-cost when the catalog has no
entry. A model with no price shows no price in the agent CLI footer rather
than a blank โ "we cannot price this" and "this cost nothing" are different
things, and the footer says which one you are looking at.
Moving a registry to another machine
models.yaml holds secret references, never key material, so the file is safe
to copy, commit, or sync. Setting up a second machine is two steps:
scp ~/.mur/models.yaml newmachine:~/ # the "export" is a copy
mur model import ~/models.yaml # on the new machine
Import merges models and roles into whatever is already there. It never deletes,
and it skips keys that already exist locally unless you pass --force. Because
the keys themselves did not travel, it finishes by listing the secret references
that do not resolve on this machine โ those are exactly what you still need
to re-key, with mur model connect <vendor> or by recreating the env var or file
the reference points at.
Hub Model Library
The MUR Hub desktop app manages all of this without YAML: connect a cloud
provider (the key goes to the macOS Keychain), auto-detect local runtimes,
discover their models via /v1/models, and add them as registry keys. It shares
the Keychain service with mur model connect, so a key stored by either surface
is visible to the other.