Model providers
packs:ai-agents
A model provider is a first-class catalog record — beside identity
providers and secrets providers — holding the brokered model credentials
AI agents reason with. The custody posture: rather than share API keys
with each agent, an agent references a provider by code and the
platform's model gateway does the heavy lifting — the gateway resolves
the record, injects the stored key server-side and streams the model's
answer through, so no model key ever lands on an agent host. A
compromised host yields a revocable agent session, not your Anthropic or
OpenAI key, and rotation happens here, in one place. The hands-on
walkthrough is opschain docs getting-started/12-6-model-providers-and-budgets;
the custody modes and the wider AI-agent lane are
ai-agents. This page is the component reference.
The kind set
| Kind | Serves | Default endpoint | Credential injection |
|---|---|---|---|
anthropic | Anthropic-hosted Claude models | https://api.anthropic.com | x-api-key |
openai | OpenAI-hosted models | https://api.openai.com/v1 | Authorization bearer |
azure-foundry-anthropic | Claude deployed in Azure AI Foundry (the Anthropic wire — agents keep the Claude adapter unchanged) | none — the record requires your resource endpoint | x-api-key |
azure-openai | GPT-family models on Azure OpenAI (the chat-completions wire) | none — the record requires your deployment URL (an api-version may ride in the endpoint string) | api-key |
openai-compatible | A model you host yourself speaking the OpenAI wire format (vLLM, Ollama, TGI, llama.cpp server, LiteLLM and similar) | none — the record requires your endpoint's base URL | Authorization bearer when a credential is stored; no auth header otherwise |
The kind picks the default endpoint, where one exists, and how the
gateway injects the stored credential upstream. The Azure kinds address
a per-resource (and per-deployment) URL in the customer's own tenancy,
so their records cannot resolve anywhere until they carry an endpoint;
authentication to them is by API key in this release, with Microsoft
Entra ID a planned follow-up. The openai default carries the /v1
segment because an OpenAI-SDK caller's base URL includes it, and the
gateway forwards the caller's path remainder verbatim.
Self-hosted models: the openai-compatible kind
The openai-compatible kind brings a model you host yourself under the
same governance as the vendor kinds — the brokered gateway, transcripts,
token budgets and the emergency stop all apply unchanged. Three things
differ from the hosted kinds:
- The endpoint is required — there is no platform default for an
endpoint only you can reach. Register your serving stack's base URL
(include the
/v1segment where your stack serves it there). - The credential is optional. Internal endpoints are often
unauthenticated or fronted by their own transport auth; with a
credential stored the gateway injects it as
Authorization: Bearer, without one it injects no auth header at all. - TLS trust is configurable per record (
ssl_verify,ssl_ca_cert, below) for endpoints behind a private certificate authority.
The gateway stays a faithful byte proxy: requests and responses are
forwarded untouched and the wire format is never parsed or translated,
so protocol fidelity is the hosting stack's responsibility — a stack
that deviates from the OpenAI dialect surfaces its own answers and
errors verbatim to the calling SDK. The endpoint is operator
configuration, set by an administrator on an admin-gated record, and the
data_handling_approved review marker works unchanged — for a
self-hosted model the review is your own sign-off on where the content
flows.
TLS trust (ssl_verify, ssl_ca_cert)
Every record carries two TLS trust attributes for its endpoint connection, defaulting to ordinary verification against the system roots:
ssl_verify(defaulttrue) — setfalseas an explicit opt-out for a self-signed endpoint; the opt-out is recorded on the entry and audited like any other change.ssl_ca_cert(default empty) — a CA trust anchor: a PEM block inline, or a certificate file path on the server host. A trust anchor that cannot be loaded refuses the call loudly rather than silently falling back to the system roots.
The provider-level model budget (tokens_per_<window>)
Per-agent token caps live on trust grants and bound each agent; nothing
in them bounds the provider as a whole, so the sum of grants can exceed
what the estate is willing to spend with one provider. The provider
record therefore carries its own token ceiling per window — the same
six windows a grant has: tokens_per_minute (a sliding burst minute)
and tokens_per_day, tokens_per_week, tokens_per_month,
tokens_per_quarter, tokens_per_year on the estate's reporting
calendar (usage.timezone, usage.fiscal_year_start). A window at 0
(or never set) has no ceiling; there is no estate default — a
ceiling is a fact of one provider's contract, set on that record alone
(opschain admin model-providers create|update --tokens-per-<window>,
or the Token budget group of the provider dialog in the GUI).
How it is enforced:
- The gateway keeps one shared counter row per provider and window in the usage store — replica-safe and restart-safe, every agent and every call through the provider adding to the same row — with the per-replica in-memory fallback when the store errors, exactly as the grant counters.
- Pre-flight, after every grant window passes, every provider window is
checked; the first over its ceiling refuses by name:
model provider <code>: tokens per <window> ceiling of <n> reached — resets at <local boundary>. The refusal is audited aswarn:trust:cap_refusedwithscope: provider,provider_code,window,limit,used,timezoneandresets_at, and counted as a refused call in the provider's usage. Nothing queues at the ceiling — a budget that does not refuse is a report, not a budget. - At
model_usage.cap_warning_fractionof a window (default 80%)warn:trust:cap_approachingwithscope: providerfires once per window per period. - Every served call's observed tokens are added to the grant counters and the provider counters.
Reading the budget: the provider's operational dashboard
(/admin/providers/model/<code>) shows a gauge per set window — spend
against ceiling with the local reset boundary — beside its token rows;
opschain admin providers usage --kind model --code <code> prints the
ceiling, the spend and the remaining budget per window (token_budget
in -o json); the providers overview carries the same windows on the
provider's row; and the OTel export ships
opschain.provider.token_budget_remaining as a gauge with
provider.kind=model, provider.code and window attributes on every
enabled OTLP connection (provider-usage).
The record, attribute by attribute
| Attribute | Meaning |
|---|---|
code | The estate code the provider is addressed by: agent manifests reference it, and the gateway path carries it. Immutable after create. |
display_name | The human name admin surfaces render. |
kind | The provider kind (table above). Immutable after create. |
endpoint | Endpoint override; empty means the kind's default where one exists — the Azure kinds and openai-compatible require one. |
credential_set | Whether a credential is stored — the value itself is write-only: no response, audit row or error ever echoes it. For openai-compatible the credential is optional. |
credential_ref | The canonical secret-vault:// reference the credential lives at — an address, never a value. Empty on a grandfathered record whose inline encrypted credential predates reference-first custody. |
data_handling_approved | The estate's one-time data-handling review marker (below). |
ssl_verify | Whether the endpoint's TLS certificate is verified (default true; the TLS trust section above). |
ssl_ca_cert | A CA trust anchor for the endpoint connection — a PEM block inline or a certificate file path on the server host; empty uses the system roots. |
enabled | Whether the gateway serves calls for this provider. |
tokens_per_minute, tokens_per_day, tokens_per_week, tokens_per_month, tokens_per_quarter, tokens_per_year | The provider-level model budget (section above): a ceiling on model tokens per window across every agent and call through this provider. 0 is no ceiling on that window. |
Custody is reference-first: --credential-ref pins an existing
secret-vault:// reference, resolved at write time through a registered
server-resolved secrets provider (an unresolvable reference refuses by
name) and clears any at-rest value, while an inline credential is held
encrypted at rest in the server's database on the record itself — never
written into a vault — and clears the reference. A server on PostgreSQL
with no secrets provider registered therefore still registers a model
provider. Records that pinned the earlier
model-providers/<code>/credential vault path keep resolving through the
vault until their next credential write.
Lifecycle
- Create registers a provider (requires
packs:ai-agents): code and kind immutable, the API key via environment variable or no-echo prompt (never argv), exactly one of the inline credential or acredential_refcarrying it (openai-compatiblealone may carry neither). - Update partially updates — a supplied credential rotates the
stored key for every agent at once; a supplied
credential_refre-pins the reference. Enabling a disabled provider requirespacks:ai-agents, exactly as create does; disabling is never gated. - Delete removes a provider, ungated. Agents whose manifest references the deleted code keep their sessions, but every gateway call refuses until the provider is re-created or the agent re-registers without it.
Compositions without the model-provider plane answer not-found, the nil-domain convention, and listings order by code ascending.
Which agents use this provider
An agent is bound to exactly one provider — the catalog code its
manifest names (OPSCHAIN_MODEL_PROVIDER on the agent host) — and the
binding is by code, never by kind: two anthropic entries are two
different providers, and an agent bound to one never falls back to the
other. To see who references an entry, read the fleet:
opschain admin agents list carries a PROVIDER column (the code —
--filter provider=<code> narrows to its agents), opschain admin agent show renders the MODEL PROVIDER row with the entry's kind and
standing (demo-anthropic (anthropic, enabled),
… (anthropic, disabled), … (missing from the catalog)), the agent
resource carries model_provider_code / model_provider_kind /
model_provider_state, and the GUI model-providers page counts the
agents referencing each entry. The disclosure an ai-type agent attests
(disclosures.model_provider, the MODEL row) is the model vendor,
not this code — see resources/agent.
The data-handling-approved marker
Each catalog entry carries a data-handling-approved marker
(data_handling_approved; --data-handling-approved on the create and
update verbs, the DATA-HANDLING column in the listing): the record of
the estate's one-time review of that provider's data-processing
terms — training use, retention, residency, DPA. An administrator sets
it after the review; a new entry defaults to unreviewed.
Lanes that send customer-submitted content to a model — such as a support-desk agent's AI lane — refuse to be configured against an unapproved provider. Because the approval is a property of the catalog entry itself, never a hardcoded provider list, a new or custom provider added later inherits the same check: review its terms, mark the entry, and only then can such a lane use it. The marker does not gate ordinary brokered calls over the estate's own content. The marker's place in the wider AI-agent custody story is ai-agents.
How a brokered model call flows
The gateway's route is /api/model/{provider}/… — the rest of the path
and query are forwarded to the provider's endpoint, so an Anthropic-SDK
caller sets its base URL to <server>/api/model/<code> and its usual
/v1/messages lands on the provider. Per call:
- The agent authenticates with its existing session token — the
same credential every other agent-plane surface takes, carried as the
Authorization bearer or as the
x-api-keyheader value. No new auth mechanism exists for the gateway. - The agent's manifest must reference the addressed provider — an agent calls only the provider it declared at registration; anything else refuses by name.
- The provider must be registered, enabled and — except for
openai-compatible, whose credential is optional — hold a credential; each miss is its own named refusal. - The grant's token-cap windows, where any is set, are checked before the call forwards — the sliding minute first, then the local day, week, month, fiscal quarter and fiscal year on the estate's reporting calendar; the first window over its cap refuses pre-flight naming the window, the grant and the local boundary it resets at (the caps live on trust grants, see ai-agents; the windows and the calendar are provider-usage). Then the provider's own budget windows (below), where the record sets any, are checked the same way; both apply and the tighter wins, the grant windows first so an agent over its own cap is told so rather than that the provider is full.
- The gateway injects the stored credential per the kind, strips every inbound credential carriage and hop-by-hop header (the agent's bearer never reaches the provider; no inbound API key is ever forwarded), and streams the provider's answer through untouched — payloads are never parsed or transformed, and the upstream connection carries no overall timeout, as a streamed response holds it for its whole generation.
- Token usage and the served model are read off the response as it
streams — Anthropic streaming usage blocks and chat-completions usage
alike, prompt-cache figures included — stamped onto the call's audit
event, added to every applying grant's cap windows, and landed in the
call's UTC minute bucket keyed on the model the response reported (the
telemetry provider-usage rolls up and
opschain agents usagereads; every other provider family records on the same substrate, and the provider's record opens its operational dashboard).
Every refusal is loud JSON in the provider error shape, so the calling model SDK surfaces it verbatim. Registration is guarded at the other end too: an agent manifest naming a provider the catalog does not hold refuses at enrolment, so a typo fails loudly before the first model call.
Settings and configuration
No config.yaml key tunes this component: the catalog is
database-backed and administered entirely through the service. On the
agent host, the manifest's provider reference is set from
OPSCHAIN_MODEL_PROVIDER; the adapter environment and custody modes are
ai-agents territory. The calendar the gateway's token-cap
windows reset on and the telemetry's retention are the usage.*
stored settings (provider-usage);
model_usage.cap_warning_fraction is the budgets' warning threshold.
Governance and audit
Every catalog verb sits behind the /admin updatable determination
(permission denied on deny), over the v2 user-token lane — the caller's
own opschain login session, with the admin-token fallbacks the CLI
manual records.
Entitlement: create and enable gate on packs:ai-agents — the
same pack an ai-type agent enrols under, with the typed refusal naming
it. The lapse posture is polite: stored providers keep resolving and
serving existing agents, only new registrations (the create/enable
writes) refuse, and delete stays open — a lapse never bricks a running
fleet. See entitlements.
Events:
api:model_providers:create/update/destroy— catalog lifecycle; record facts only (code, kind, endpoint, the reference, the marker, the TLS trust facts —ssl_verifyand whether a trust anchor is set — enabled and the six budget windows), never a credential, a rotation carryingcredential_rotated.warn:trust:cap_refused/warn:trust:cap_approachingwithscope: provider— a provider budget window reached or approaching its ceiling (the section above; thescope: grantvariants are the trust-grant story).info:agent:model_call— every served gateway call: the provider, the sniffed model name, method, path, upstream status and the observedinput_tokens/output_tokens.warn:agent:model_call_refused— a gateway refusal, with the provider and the named reason.warn:agent:model_provider_refused— a registration named a provider the catalog does not hold.
The per-agent token-budget refusal and the trust-review spend
aggregation (tokens_by_provider, cost attribution) are the trust-grant
story — ai-agents carries them in full.
Related pages
opschain docs cli/admin-model-providers— the command family (list,show,create,update,delete), verb by verb.opschain docs getting-started/12-6-model-providers-and-budgets— the hands-on guide: custody, the catalog, token budgets.opschain docs ai-agents— the custody modes, the adapters, the transcripts and the caps.opschain docs entitlements—packs:ai-agentsand the lapse posture.- The API service is the v2
ModelProviderService(opschain docs api-v2); the gateway route is/api/model/{provider}/…, agent- authenticated and deliberately not part of the admin service.