Skip to main content
Version: v5.7.0

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​

KindServesDefault endpointCredential injection
anthropicAnthropic-hosted Claude modelshttps://api.anthropic.comx-api-key
openaiOpenAI-hosted modelshttps://api.openai.com/v1Authorization bearer
azure-foundry-anthropicClaude deployed in Azure AI Foundry (the Anthropic wire — agents keep the Claude adapter unchanged)none — the record requires your resource endpointx-api-key
azure-openaiGPT-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-compatibleA 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 URLAuthorization 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 /v1 segment 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 (default true) — set false as 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 as warn:trust:cap_refused with scope: provider, provider_code, window, limit, used, timezone and resets_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_fraction of a window (default 80%) warn:trust:cap_approaching with scope: provider fires 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​

AttributeMeaning
codeThe estate code the provider is addressed by: agent manifests reference it, and the gateway path carries it. Immutable after create.
display_nameThe human name admin surfaces render.
kindThe provider kind (table above). Immutable after create.
endpointEndpoint override; empty means the kind's default where one exists — the Azure kinds and openai-compatible require one.
credential_setWhether 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_refThe 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_approvedThe estate's one-time data-handling review marker (below).
ssl_verifyWhether the endpoint's TLS certificate is verified (default true; the TLS trust section above).
ssl_ca_certA 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.
enabledWhether the gateway serves calls for this provider.
tokens_per_minute, tokens_per_day, tokens_per_week, tokens_per_month, tokens_per_quarter, tokens_per_yearThe 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 a credential_ref carrying it (openai-compatible alone may carry neither).
  • Update partially updates — a supplied credential rotates the stored key for every agent at once; a supplied credential_ref re-pins the reference. Enabling a disabled provider requires packs: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:

  1. 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-key header value. No new auth mechanism exists for the gateway.
  2. The agent's manifest must reference the addressed provider — an agent calls only the provider it declared at registration; anything else refuses by name.
  3. 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.
  4. 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.
  5. 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.
  6. 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 usage reads; 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_verify and whether a trust anchor is set — enabled and the six budget windows), never a credential, a rotation carrying credential_rotated.
  • warn:trust:cap_refused / warn:trust:cap_approaching with scope: provider — a provider budget window reached or approaching its ceiling (the section above; the scope: grant variants 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 observed input_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.

  • 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-agents and 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.