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 no id-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 for Ambiguous-disposition drivers).
  • Model-level fields: id, plus per-model params (max_tokens, temperature, context_window, …).
  • There is no enabled flag — presence in config is enablement. To disable a provider, delete or comment out its [[provider]] block.
  • Two instances may share one driver (ollama.west and ollama.east, both driver = "ollama", different url), each with its own id.

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:

DispositionMeaningDrivers
AlwaysLocalStructurally local — no config or attestation involvedecho, ollama, vllm, lmstudio, lemonade
AmbiguousSpeaks a public protocol; resolves Local only with an explicit sovereignty_attestation, else fails closed to Cloudopenai-compatible
AlwaysCloudStructurally never local — no attestation can change thisanthropic, 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 stateAuth statusEffect
Set, non-emptyConfiguredProvider is usable
Unset or emptyMissingProvider excluded from routing
No api_key_env on the entryNotRequiredProvider 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: