Configuring Providers
What you'll accomplish: Declare the closed set of LLM providers and models Hoziron is allowed to route to, set API keys, and understand why there is no runtime "add a provider" API.
The inventory is boot-time config, full stop (ADR-053)
Per ADR-053 (Provider/Model Inventory and Driver-Disposition Sovereignty), the set of usable providers and models is declared explicitly in config.toml and is the only set the routing gateway will ever consider. There is no auto-scan of a builtin catalog, no implicit "every provider the kernel knows how to speak to is available," and no runtime API that mutates the provider/model inventory. Adding a model means adding a config entry and restarting (or file-watch reload, where wired); adding a new provider type means a new kernel-shipped driver, never a config-only addition.
// crates/surfaces/hoziron-api/src/routes.rs
// Providers — read-only (ADR-053: the provider/model inventory is
// boot-time config.toml only, no runtime mutation API).
.route("/providers", get(list_providers))
// Models — read-only (ADR-053)
.route("/models", get(list_models))
GET /providers and GET /models are the only provider-inventory routes registered on the API — both read-only. The CLI still ships hoziron-cli providers create / enable / disable subcommands that POST/PATCH to /providers/..., but no such write route is registered on the server today — those commands will fail against a current build. Use hoziron-cli providers list (backed by GET /providers) to inspect the inventory; to change it, edit config.toml and reboot.
Config shape: [[provider]] / [[provider.model]]
Providers and models are each identified by an explicit id (not a TOML table key), and every provider names the driver it uses:
[[provider]]
id = "ollama.west" # arbitrary, operator-chosen, unique
driver = "ollama" # must match a kernel-shipped driver (closed set)
url = "http://10.4.2.7:11434/v1"
[[provider.model]]
id = "llama3.2" # must resolve in the catalog for this driver (else boot warning)
max_tokens = 2048
temperature = 0.1
[[provider.model]]
id = "qwen2.5"
[[provider]]
id = "big.licence"
driver = "anthropic" # driver class AlwaysCloud — never local, non-overridable
api_key_env = "ANTHROPIC_API_KEY"
[[provider.model]]
id = "claude-sonnet-4-5-20260929"
[[provider]]
id = "inhouse.shim"
driver = "openai-compatible" # driver class Ambiguous
url = "http://10.4.2.7:8000/v1"
sovereignty_attestation = "carrier confirms 10.4.2.7 is in-boundary" # required to treat as local
[[provider.model]]
id = "mistral-inhouse"
driver =is required and explicit on every provider — there is noid-implies-driver shorthand, because the driver→sovereignty link is load-bearing (see Local models and the hardening checklist) and must never be inferred from the instance name.- Provider-level fields:
id,driver,url,api_key_env,sovereignty_attestation(only meaningful forAmbiguous-disposition drivers). - Model-level fields:
id, plus per-model params (max_tokens,temperature,context_window, …). - There is no
enabledflag — presence in config is enablement. To disable a provider, delete or comment out its[[provider]]block. - Two instances may share one driver (
ollama.westandollama.east, bothdriver = "ollama", differenturl), each with its ownid.
There is no [default_model] field and no [providers.<name>] map-style section — model selection is exclusively the [[provider]]/[[provider.model]] inventory above (ADR-053).
The closed driver set
driver = must match one of the kernel's compiled-in drivers (hoziron-runtime::drivers::driver_disposition). An unrecognised driver string is a hard boot error — it never silently falls through to an OpenAI-compatible client:
| Disposition | Meaning | Drivers |
|---|---|---|
AlwaysLocal | Structurally local — no config or attestation involved | echo, ollama, vllm, lmstudio, lemonade |
Ambiguous | Speaks a public protocol; resolves Local only with an explicit sovereignty_attestation, else fails closed to Cloud | openai-compatible |
AlwaysCloud | Structurally never local — no attestation can change this | anthropic, openai, gemini, google, groq, openrouter, requesty, deepseek, together, mistral, fireworks, perplexity, cohere, ai21, cerebras, sambanova, huggingface, xai, replicate, github-copilot/copilot, codex/openai-codex, claude-code, qwen-code, moonshot/kimi/kimi2/kimi_coding, qwen/dashscope/model_studio, minimax, zhipu/glm/zhipu_coding/codegeex/zai/z.ai/zai_coding, qianfan/baidu, volcengine/doubao/volcengine_coding, chutes, venice, nvidia/nvidia-nim, novita/novita-ai, azure/azure-openai, vertex-ai/vertex/google-vertex, bedrock |
echo is AlwaysLocal because it never makes a network call — it's the deterministic test/fallback provider, always present in the inventory as ECHO_PROVIDER_ID.
An sovereignty_attestation set on a non-Ambiguous driver (e.g. trying to attest anthropic as local) is a hard boot error — you cannot attest a cloud driver as local, full stop.
Setting API keys
[[provider]]
id = "big.licence"
driver = "anthropic"
api_key_env = "ANTHROPIC_API_KEY"
export ANTHROPIC_API_KEY="sk-ant-..."
config.toml only ever stores the name of the environment variable — never the key value. Keys are resolved lazily at request time:
| Env var state | Auth status | Effect |
|---|---|---|
| Set, non-empty | Configured | Provider is usable |
| Unset or empty | Missing | Provider excluded from routing |
No api_key_env on the entry | NotRequired | Provider always usable (typical for local Ollama/vLLM) |
Adding or removing the env var takes effect on the next request — no daemon restart required for key rotation. GET /gateway/pools reports each provider's live auth_status alongside its resolved sovereignty and circuit-breaker reachability (see Complexity routing).
Listing the inventory
hoziron-cli providers list # → GET /providers
hoziron-cli models list # → GET /models
hoziron-cli models list --provider ollama.west
hoziron-cli models aliases
Next steps
Related: