Skip to main content
Ziro talks to LLMs through a provider registry. Three providers ship live, any OpenAI- or Anthropic-compatible endpoint can be registered as a custom provider, and provider and model always resolve together as one coupled pair.

The built-in providers

Each provider reads its own live source of truth for capabilities (OpenRouter’s /models, Anthropic’s /v1/models, OpenAI’s /v1/models), cached on disk with a 24-hour TTL. So context windows, max output tokens, and reasoning support stay current without a Ziro release. A catalog miss stays conservative rather than guessing wrong.
An unknown provider name fails fast with an error. There is no silent fallback to a default provider, because a silent fallback produces a confusing 404 from the wrong endpoint instead of a clear message.

Global default

ZIRO_LLM_PROVIDER sets the provider an agent falls back to when it has no pin of its own. It defaults to openrouter.
An unrecognised value here degrades to openrouter rather than crashing at launch. Cross-provider knobs use the ZIRO_ prefix: Each has an OPENROUTER_* back-compat fallback for older configurations.

Per-agent pinning

An agent pins both axes in its meta.yaml:
Omit provider to use ZIRO_LLM_PROVIDER. Omit model (or set it to null) to use that provider’s env default model. Change a pin without editing YAML:

The coupling rule

Provider and model are one coupled pair, never two independent axes. Treating them independently is what produces a provider switch that 404s: you keep the old provider’s model id and point it at the new provider’s endpoint. Resolution works like this:
1

An explicit model choice wins, and carries its provider

If you pick a model directly, its provider comes with it. A heuristic backstop infers the provider from the id shape when needed: an id containing / implies openrouter, a claude-* id implies anthropic.
2

Otherwise, provider first

The provider decides. The agent’s pinned model is honoured only if it is compatible with that provider; otherwise the provider’s default model is used.
In the TUI, switching provider mid-thread via /model or /settings overrides the agent’s pinned model for that thread, persists to threads.json, and is restored when you resume the thread.
/model lists the active provider’s catalog, not a merged list across all providers. Switch provider first if you want to see another provider’s models.

Custom providers

Any OpenAI- or Anthropic-compatible endpoint becomes a first-class provider by adding a label to ~/.ziro/custom_providers.json. Each label reuses a native adapter, pointed at your base_url.
style
"openai" | "anthropic"
required
Which native adapter to reuse. This is the wire protocol your endpoint speaks, not the vendor behind it.
base_url
string
required
Your endpoint’s base URL.
headers
object
Extra headers sent on every request.
models
string[]
A typed fallback model list, used when the endpoint’s live model list cannot be fetched.
The API key lives in the env file, never in the JSON. The variable name is derived from the label, uppercased with non-alphanumeric runs collapsed to underscores:
A custom label works everywhere a built-in does: in meta.yaml’s provider:, as ZIRO_LLM_PROVIDER, and in the provider picker. Ziro fetches the endpoint’s live model list (/models for openai-style, /v1/models for anthropic-style), caches it for 24 hours, and falls back to your models list if unreachable. Other capabilities stay permissive: your endpoint decides. The whole mechanism is tolerant by design. A missing or malformed file, a bad entry, or a label that collides with a built-in name is skipped with a warning. It is never fatal.
You do not have to write the JSON by hand. The setup wizard has an add custom provider flow that validates your input, writes the file, and persists the API key for you. Run ziro setup.

Model tiers

Different jobs deserve different models. Rather than pinning one model per agent and living with it, Ziro defines three role-based tiers, each resolved per provider:
A tier that is unset collapses to <PROVIDER>_MODEL, so partial configuration is fine.

The ${TIER_MODEL} token

Anywhere a model: field is accepted, you can write a tier token instead of a literal model id. It must be the whole value, not embedded in a longer string.
The token resolves against whichever provider is active at build time. That is what makes a subagent portable: the same definition runs on OpenRouter for you and on Anthropic for a colleague, each picking up their own tier configuration. Tiers are also reachable in the TUI under /settings → Planning / Execution / Fast Execution Model, prefilled from your env or picked from the live provider catalog.

Subagent model resolution

A subagent inherits the session’s active provider. It is never hardcoded to a global one. Its model candidates resolve in order:
1

The subagent's own pin

A model: in its *.agent.md frontmatter.
2

The parent's per-child default

model_defaults[<agent_id>][<provider>] in the parent’s subagent_policy.yaml.
3

The parent's generic default

model_defaults["default"][<provider>].
4

The tier fallback

${EXECUTION_MODEL} for the active provider.
That produces a candidate list, not a single choice. If a call fails, Ziro fails over to the next candidate on a fresh thread. Recursion-limit errors are excluded from failover: a child stuck in a loop is a bug to surface, not a model to swap.

Switching in chat

/think warns rather than blocks when the current model does not advertise reasoning support, so you are told without being stopped.

Next steps

Environment variables

Every provider and tier variable in one place.

Configuration

How meta.yaml resolves across config layers.

Compaction

How model context windows drive the compaction budget.