Governance · Cost management · Deep dive
The model proxy: one door to every model
Every agent in a Brightwire environment reaches every language model through one proxy. Provider keys live there and nowhere else; each caller carries its own key with its own budget; every call is metered, allow-listed and logged without its content. This is the piece that turns "we use several AI vendors" into something a company can control.
Why one door
The usual way to adopt several model providers is to hand out API keys. Within a quarter there are keys in laptops, in CI variables, in three departments' scripts, and nobody can answer the two questions that matter: who is spending what, and can we turn it off.
We put a proxy in the path instead, and made it the only path. In an Axle environment a spoke's network is closed by default: its agents may reach the model proxy, the tool gateway, the control API, their own databases and an outbound proxy with an allowlist, and nothing else. No provider key exists in any tenant's namespace. If an agent wants a model, it asks the proxy, with its own key.
The proxy is LiteLLM, an open-source gateway that speaks one API to many providers. It runs as one pinned, digest-locked deployment per environment with its own database. As with the boards our agents work on, the tool matters less than the conventions around it.
Who calls, with which key
A virtual key is an identity the proxy issues. It never reaches a provider. It carries a budget, an allowlist tier and tags, and it is what the spend log attributes to.
| Caller | Key | What it is for |
|---|---|---|
| A spoke's agents | tenant-<slug> | Every persona in that tenant's runtime. Tagged tenant:<slug> so spend rolls up to the tenant. |
| A spoke's chat | tenant-<slug>-chat | The end-user chat surface, when a tenant has one; its own budget, so a busy chat cannot starve the agents. |
| Hub agents | hub-agents | Brightwire's own platform personas. |
| Reviewer seat | reviewer | A second opinion on routine pull requests from a different model family than the author's, with a small budget of its own. |
| Coding runners | per run | A key minted for one job, capped for that job, carrying the card and runner id. |
What the proxy decides on every call
- Authenticate the key. Unknown key, no call.
- Allowlist. Each key is bound to a tier of models it may use. The tier is a file in the fleet repository; a control loop reconciles it onto every managed key every five minutes and writes each change to the audit log. Models marked not-for-tenants are simply absent from the tier.
- Budget. Each key has a hard cap per 30 days. Over it, the proxy answers with a budget error and the call never reaches a provider.
- Resolve the alias, with fallbacks. Agents ask for a name like
default,simple,mediumorcomplex, not a vendor's model id. The proxy maps the alias to a concrete model, and if that model is down or over its own limits it falls through a fixed chain to the next one, or tomedium. The alias is what lets us change vendors without touching an agent. - Log the spend. Model, tokens, cost, latency and tags. Not the prompt. Not the answer.
| Alias | Resolves to today | Meant for |
|---|---|---|
default | a fast frontier model (Gemini 3 Flash) | most agent turns |
simple · medium | the same fast model | classification, extraction, short answers |
complex | a large reasoning model (Grok 4.5), falling back to Claude Opus | planning, review, long code changes |
auto | the complexity router (see below) | letting the proxy pick the tier |
| embeddings | a text-embedding model | retrieval indexes |
What a budget stop looks like
Budgets are enforced where the money is spent, not reported after the fact. When a key crosses its cap the proxy refuses the call, and a webhook tells the control API, which writes a budget.exceeded event to that tenant's audit stream and posts a line in the hub's chat. Budgets reset on the first of the month.
We hit our own cap first. On 25 September our hub agents spent their entire monthly allowance in about four days. The proxy did exactly what it should: it returned a budget error and every agent stopped. What it exposed was a wording problem, not a control problem: the agent runtime treats every refusal of that kind as a rate limit, so people saw "the model request was rate-limited" instead of "this team is out of budget". The caps were raised the same day, deliberately, by a person; the message is being fixed at the runtime. (Source: our own platform logs and the incident write-up, 2026.)
proxy alerting: webhook → control-api /api/hooks/litellm
alert types: budget_alerts
mapping: budget_crossed → budget.exceeded
threshold_crossed,
soft_budget_crossed,
projected_limit_exceeded → budget.alert
each event: tenant audit stream (append-only) + one line in hub chat
caps today: hub agents 2,000 USD / 30 days · each spoke key 500 USD / 30 daysChanging a model or a vendor
Because agents only know aliases, a vendor change is a configuration change, and configuration changes go through the same door as code:
- A pull request in the fleet repository adds the catalog entry and, if tenants may use it, the allowlist entry. A person merges it.
- The environment syncs itself to the merged state; the control loop puts the new tier on every managed key; a one-token probe confirms the model answers.
- A new provider additionally needs its API key placed in the secret store. That is a human-only step, and it is the only place the key ever exists.
Removing a model is the same pull request in reverse. Today the catalog holds about sixty entries across five providers: Google Vertex AI (which needs no stored key at all), Anthropic, OpenAI, xAI and Fireworks. Adding a sixth is an afternoon; the agents never notice.
What is logged, and what is not
- Logged, per call: the key (by alias), model requested and model served, tokens in and out, cost, latency, the tenant tag, and the router's notes on what it did and what the alternative would have cost.
- Never logged: the prompt or the response. Prompt storage is switched off in the proxy and the runbook forbids turning it on. Evidence about what an agent did lives on the board and in the tool gateway's audit, not in the model log.
- Read-only operator views: health, the model catalog, the allowlist and the list of keys, shown by alias and never by value.
What we do not claim
- Routing by task complexity, today. The router exists (a classifier picks
simple,mediumorcomplex, with a rule that never sends a tool-using turn to a model that cannot handle tools, and a clamp tomediumonce a tenant has spent 80 % of its budget). It is switched off in every environment while its classifier is measured against a hold-back set; today theautoalias resolves to a fixed model. Aliases and fallbacks are what route calls now. - Spend by person or by persona. Attribution is per key: per tenant, per chat surface, per run. Per-persona keys and end-user attribution are the next phase of the budget work, not the current one.
- A soft landing before the cap. The design has warning thresholds and a gradual downgrade to cheaper models as a budget runs out. Today the stop is hard, and the warning threshold is not yet armed on the live keys.
- Per-tenant allowlists. There is one fleet-wide tier today; the mechanism supports more.
- A retention period for the spend log. We have not set one yet, so we do not quote one.
Soft next step: if you want to see what your own model spend looks like behind one door, start a conversation.