models.providers — the provider registry¶
This page is the block reference. For the provider-neutral model — which roles accept which URI, the three provider families, credentials per backend — read Model providers.
models.providers declares how a <provider>:/<model> URI on any role (models.skill,
subagent, hook, judge, a per-judge model:, the --model / --subagent-model
flags, and the Harbor runner's --judge-model, which applies to task generation only) is
served. It lives under models because it exists only to resolve those URIs. One provider
kind exists, openrouter (the block, the direct agent transport, the routing snapshot
and the audit since v1.50.0; the full per-slug preflight, audit-aware pooling and a working
enforcement: key-guardrail since v1.51.0; Kubernetes and EvalHub delivery since
v1.53.0), and the block is optional: an openrouter:/… judge
works with the defaults and OPENROUTER_API_KEY exported, and an openrouter:/… skill
model activates the direct agent transport with the defaults too. A declared block is inert until a role names it — a base eval.yaml can
hold the shared routing table while its roles stay on Anthropic, and a
profile flips models.skill to openrouter:/… to activate it.
For the narrative (what the transport does, what each enforcement level guarantees, cost provenance, secrets) read the OpenRouter guide; the models page covers the roles and their precedence.
The openrouter block¶
models:
skill: openrouter:/z-ai/glm-5.2:exacto
providers:
openrouter:
kind: openrouter # optional; must equal the block name
api_key_env: OPENROUTER_API_KEY # variable holding the inference key (never the value)
management_key_env: OPENROUTER_MANAGEMENT_KEY # variable holding the management key (key-guardrail only)
base_url: https://openrouter.ai/api # no /v1; https unless loopback
attribution: { referer: https://…, title: agent-eval-harness, run_id_header: false }
background_model: null # the haiku slot + the default hook model; null = the model under test
preflight: strict # strict | warn | off
cli_budget_inflation: 50 # execution.max_budget_usd × this → --max-budget-usd
budget: { run_usd: null, dedicated_key: false }
routing:
defaults: { allow_fallbacks: true }
models:
z-ai/glm-5.2: { order: [novita], allow_fallbacks: false, quantizations: [fp8] }
policy: strict # strict | warn
enforcement: audit # audit | key-guardrail
guardrail: { key_name: "agent-eval {run_id}", providers: pinned, revoke_on_exit: true, settle_s: 20 }
judge:
concurrency: 4
max_retries: 3
timeout_s: 300
extra_body: {}
inherit_pins: false
| Key | Default | Meaning |
|---|---|---|
kind |
openrouter |
Must equal the block name. openai-compatible is rejected with a pointer to openai:/… plus OPENAI_BASE_URL (the loader's message reads "not implemented in this release"); any other block name is rejected as an unknown provider. |
api_key_env |
OPENROUTER_API_KEY |
Name of the environment variable holding the inference key. The key itself may never be authored in a config or on any env: surface; a value that looks like a key is rejected. On Kubernetes the same key also lives in the credentials Secret under this name. |
management_key_env |
OPENROUTER_MANAGEMENT_KEY |
Name of the variable holding the management key, read only at enforcement: key-guardrail to provision the per-run key. Never forwarded anywhere. |
base_url |
https://openrouter.ai/api |
Origin the agent (ANTHROPIC_BASE_URL) and the harness clients talk to. No /v1 suffix (the harness appends it). Plain http is allowed for a loopback host only. |
attribution.referer / .title |
null / agent-eval-harness |
HTTP-Referer / X-OpenRouter-Title sent by the judge client and, as ANTHROPIC_CUSTOM_HEADERS, by the agent. Single line each. |
attribution.run_id_header |
false |
Adds x-eval-run-id: <run id> to the agent's headers (OpenRouter activity-page tagging only; cost attribution comes from generation ids). |
background_model |
null |
The haiku slot (ANTHROPIC_DEFAULT_HAIKU_MODEL) and the default hook model. null = the model under test, so a second model never appears silently in per_model_usage. |
preflight |
strict |
Catalog, key and eligibility checks before any spend (see the guide). strict fails the run; warn continues degraded; off skips and writes no snapshot. |
cli_budget_inflation |
50 |
execution.max_budget_usd × cli_budget_inflation is what --max-budget-usd receives — the CLI enforces that cap on its Anthropic-priced estimate, 2 to 60 times the real OpenRouter cost. Must be ≥ 1; a cap of 0 omits the flag. |
budget.run_usd |
null |
The whole-run real-dollar pool: enforced after the run at audit (budget.exceeded: run, --strict-cost exits 2) and server-side, in flight, at key-guardrail (it becomes the per-run key's limit; required and > 0 there). |
budget.dedicated_key |
false |
The operator's assertion that nothing else spends on the key during the run; raises a key-usage-only cost_confidence from low to medium. Implied at key-guardrail. |
routing.defaults / routing.models |
{} |
OpenRouter provider-routing declarations (order, only, ignore, allow_fallbacks, quantizations, require_parameters, sort, data_collection, zdr, max_price, plus fallbacks for the models array). models is keyed by the bare slug; one entry covers every :variant. |
routing.policy |
strict |
What a failed routing audit (checked after the run) does to the run: strict marks it degraded (--strict-routing exits 2), warn flags it. |
routing.enforcement |
audit |
audit: preflight plus an audit after the run; the agent holds the operator key. key-guardrail: a per-run key with a provider allow-list and a real-dollar limit, provisioned through the management API and revoked at run end. |
routing.guardrail.key_name |
agent-eval {run_id} |
Name of the per-run key ({run_id} substituted). |
routing.guardrail.providers |
pinned |
The per-run key's allow-list: pinned = the union of the pinned sets of the routing keys the agent roles use, or an explicit list of provider slugs. A guardrail with no provider restriction is a validation error. |
routing.guardrail.revoke_on_exit |
true |
Always true; false is rejected at load. |
routing.guardrail.settle_s |
20 |
Key-usage settle before the run-end read; a shorter value warns (the counter settles in about 20 s). |
judge.concurrency |
4 |
Concurrent OpenRouter judge requests. |
judge.max_retries |
3 |
Retries on 429 (Retry-After), 502/503 and provider-unavailable replies. |
judge.timeout_s |
300 |
Per judge request. |
judge.extra_body |
{} |
Static request additions, merged last. |
judge.inherit_pins |
false |
Judges send order/only/quantizations only when true (or when a judge carries its own provider_options.routing). Rejected when routing is absent. |
Agent path vs judge path¶
Claude Code cannot put a provider object in its requests, so on the agent path only
order, only, ignore, quantizations and fallbacks mean anything: pins are checked
by the preflight and audited after the run, and the :variant on the model id
(:exacto, :nitro, :floor) is the only in-request routing control.
require_parameters, sort, data_collection, zdr and max_price on a routing key an
agent role uses raise a load-time warning and are ignored there. Judges that inherit pins
receive the full declaration. See judges → OpenRouter judges
for what a judge sends and the tool_choice fallback rule.
Validation¶
Fail fast at load, one consolidated error per category: unknown keys; reserved keys
(transport, direct, proxy, gateway, generation_backfill, key_exposure_ack,
budget.max_unpriced*) named as unsupported; key-guardrail without budget.run_usd
or without pins / an explicit allow-list; revoke_on_exit: false; judge.inherit_pins
without a routing table; with the block declared but no plan active, a bare agent id
that has a routing entry or is not an Anthropic id ("bare-id footgun" — write
openrouter:/<id> or drop the pins); subagent / hook on a
different provider kind than the skill; runner.type other than claude-code for an
openrouter:/ skill model (also per step); an agent: judge whose model is
openrouter:/…. The managed-key ownership rule applies while a plan is active:
ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN and ANTHROPIC_CUSTOM_HEADERS are rejected
on presence on every env surface (execution.env, runner.env, runner.settings.env,
the per-step variants), static managed keys must equal the plan's values, a non-empty
ANTHROPIC_API_KEY warns, and OPENROUTER_API_KEY / OPENROUTER_MANAGEMENT_KEY may
never be authored anywhere, plan or no plan.
Profiles: extends: and this block¶
The usual pattern keeps the routing table in the base eval.yaml (roles on Anthropic,
block inert) and activates it from a profile:
# eval-profiles/openrouter-glm-5.2.yaml
extends: ../eval.yaml
models:
skill: openrouter:/z-ai/glm-5.2:exacto
providers:
openrouter:
routing:
models:
z-ai/glm-5.2: { order: [novita], allow_fallbacks: false }
The overlay is deep-merged over the base by the single raw loader: mappings merge key by
key, so a profile can add or change one knob without restating the block; lists (an
order, quantizations, a fallbacks array) replace the base list — use the
!replace tag on a mapping to swap it wholesale instead of merging. Paths in the overlay
(extends: itself, dataset.path, prompt_file, …) resolve relative to the file they
appear in; eval_params.config_chain records the chain, and
python3 -m agent_eval.config --print <profile> shows the merged result with provenance.
See extends.
Where the key goes, per backend¶
| Backend | How the agent gets ANTHROPIC_AUTH_TOKEN |
Host variables needed |
|---|---|---|
local claude-code |
the per-run settings overlay (.eval-overlay.json, 0600, removed at exit) |
OPENROUTER_API_KEY (+ OPENROUTER_MANAGEMENT_KEY at key-guardrail) |
| Harbor podman | value-free --agent-env carriers (the value in the harbor child env) |
same |
| Harbor Kubernetes / OpenShift | valueFrom.secretKeyRef from the credentials Secret (AGENT_EVAL_K8S_CREDENTIALS_SECRET, key OPENROUTER_API_KEY) at audit; from the per-run Secret agent-eval-<run_id>-openrouter at key-guardrail |
audit: OPENROUTER_API_KEY on the host (preflight, backfill, key usage) and in the credentials Secret; key-guardrail: OPENROUTER_MANAGEMENT_KEY on the host, OPENROUTER_API_KEY only for openrouter:/ judges (then also in the credentials Secret) |
| EvalHub | the pod's own environment (credentials injected by the cluster), through the same overlay in the pod | OPENROUTER_API_KEY in the job pod (+ OPENROUTER_MANAGEMENT_KEY at key-guardrail) |
The management key is never forwarded to any of them. The cross-provider version of this table — Anthropic direct, Vertex, Bedrock, OpenRouter and OpenAI on every backend — is Model providers → Credentials by backend.