Skip to main content
Version: v5.7.0

Provider usage — the usage telemetry of every registered provider

Included from Basic tier

Every call OpsChain makes to a registered provider is a fact the platform can keep: how hard it is hitting your vault, how many image pushes failed this week, whether the identity provider is refusing exchanges, how far behind the SIEM feed is, how many tokens each agent spent on which model. Provider usage is that fact kept as telemetry, the same way for every provider family: a minute-grained usage table every family writes at its existing seam, calendar rollups computed on the estate's own reporting calendar, one operational dashboard per registered provider, one overview of them all, one CLI verb, and the same numbers exported as OpenTelemetry metrics. Model providers keep their token budgets on top of it. Facts only — counts, values, latencies — never money and never a user.

The families and what each records are below; the model lane (token telemetry and the six token-cap windows) keeps its own section, because it is the one family with budgets. The hands-on walkthrough for that lane is opschain docs getting-started/12-6-model-providers-and-budgets.

The substrate — metrics as rows, UTC at the base, calendar on top​

Base grain: one minute, UTC, no time zone. Every family records through one call at its seam, and the server folds the samples into one row per (minute, family, provider code, metric, project path, detail, agent, grant) in provider_usage_minute. The three detail dimensions are empty where a family has none: detail is the model a model call reported or the image a registry push carried; agent_code and grant_id belong to the model lane. A row carries count (samples — a counter's count), errors (the samples that failed), value_sum and value_max (a counter's count, a value metric's quantity — tokens, bytes, seconds — summed and its largest single sample), and latency_sum_ms / latency_max_ms where the seam times the call. Metrics are rows, never columns: adding one is a constant in the family's code, never a schema change. Minute rows are kept for usage.minute_retention (default 14 days).

project_path is the scoping dimension where a call has one — a step's run, an agent's project. Platform-level calls (an identity exchange, a SIEM ship, an inbound webhook) carry the empty path and roll on the global calendar. Nothing per user: the rows carry provider codes, project paths, agent codes and grant ids, never a username.

Rollups: hour, day, week, month, quarter, year, derived set-based from the minute rows on a reporting calendar — a time zone and a fiscal year start — never in UTC: Adelaide and India sit on half-hour offsets, so even an hourly UTC bucket straddles two local hours, and a "day" that ends at UTC midnight ends mid-afternoon for half the world. The cascade is tiered so no grain ever needs minute rows older than the minute retention: hours from minutes, days from hours, and weeks, months, fiscal quarters and fiscal years from days (a fiscal quarter that starts on the 6th still sums whole days). Each rolled row carries the peaks that survive aggregation — peak_minute_value on an hour row and coarser, peak_hour_value on a day row and coarser, peak_day_value on a week row and coarser (a counter's busiest minute is its count, a value metric's its quantity) — so a burst is still visible in a yearly total. Hour rows are kept for usage.hour_retention (default 90 days); day and coarser rows are kept.

The calendar is recorded on every rolled row (timezone, fiscal_year_start). Changing either setting re-rolls the project from the minute rows still within retention under the new calendar and marks the older rows as rolled under the previous calendar (calendar_superseded), rather than mixing two calendars in one table: a summary reads only the current calendar's rows; the model lane's CSV export carries both, flagged, so nothing is silently lost.

The rollup and retention pass runs on the cron leader every five minutes (Config.ModelUsageRollupInterval is the test seam), project by project, each on its own calendar; a project whose rollup fails is noticed and retried next tick without stalling the rest. Samples from every family but the model gateway are batched in memory and written every two seconds in one set-based UPSERT; the model gateway writes its call synchronously because the token budgets read it on the next call.

Every call is still on the ledger where the family already emits events (info:agent:model_call, info:image_build:provider_used, warn:siem:delivery_failing, the transport session events, the login events …): the buckets are the fast path, never the only record.

The families and their metrics​

Family (kind)Metrics (rows in metric)Where the sample is taken
Secrets providers (secrets)reads (a provider read, with its latency; a failed read counts as an error), cache_hits (served from the client's per-path cache instead), snapshot_hits (answered from the run snapshot — one read per run per reference), token_mints (a run-scoped vault child token minted at dispatch; a refused mint is an error), token_revokes (a lease revoked by accessor at run end or on a runner's restart), queue_waits (a call that waited for the provider's rate budget), rate_limited (a 429 or throttling answer, honoured and retried; an error), breaker_trips (the circuit breaker opened; an error) and the breaker_open gauge (1 while open — the posture page's breaker state). A runner provider's counts arrive on the step's result and are written by the server.the provider clients, the dispatch lease, the run snapshot and the runner's result
Transport providers (transport)checkouts (a step handed a transport session), sessions_minted (a short-lived certificate signed), session_failures (the credential did not resolve or the bundle did not mint)dispatch
Model providers (model)calls, refused_calls (a token cap refused pre-flight; an error), tokens (input + output — the metered total; its value_max is the largest call and its peaks are the token peaks), input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens; detail = the model as reportedthe model gateway
Image build providers (image_build)builds (a build dispatched through the provider), build_failures (a named refusal at the crossing), build_seconds (registered; the server never sees the build's duration today)the dispatch crossing
Image registries (image_registry)pushes (an image artifact uploaded with the registry named; detail = the image reference), push_failures (a named refusal at the crossing), bytes_pushed (registered; the descriptor carries no size today)the dispatch crossing and the artifact upload
Identity providers (identity)logins (a federated sign-in landed; a provider that could not answer is an error on it), token_exchanges (an authorization code exchanged at the provider's token endpoint), refusals (the provider or the directory refused the credential, or the sign-in was refused by policy), jwks_refreshes (a signing-key set fetched; a failed fetch is an error)the federated handlers and the directory bind
SIEM connections (siem)events_shipped (per batch, the events delivered), ship_failures (a batch the receiver refused — retried, so it reappears), backlog_depth (gauge: filtered events still past the cursor after a delivery, counted up to 100,000)the forwarder
OTLP connections (otlp)spans_exported, export_failures, backlog_depth (the same, for the trace lane)the forwarder
GitHub connections (github)api_calls / api_errors (the installation-token mint against the GitHub REST API), tokens_minted, rate_limit_remaining (gauge: GitHub's X-RateLimit-Remaining as last answered), webhooks_received (a delivery accepted on the connection's inbound webhook), signature_failuresthe dispatch mint and the inbound lane
JSM connections (jsm)api_calls / api_errors (every request the integration makes, with its latency), webhooks_received, signature_failures, rate_limit_remaining and tokens_minted (registered; JSM answers no rate-limit header and mints no token)the JSM client and the inbound lane

A failure metric (refused_calls, session_failures, build_failures, push_failures, refusals, ship_failures, export_failures, api_errors, signature_failures, rate_limited, breaker_trips) counts in errors as well as count, so "errors over the range" for a provider is the sum of errors across every metric and "calls" is the family's primary metric (the first in each list). A gauge (backlog_depth, rate_limit_remaining) reads as the highest reading in the latest minute that observed it.

Surfaces​

  • One operational dashboard per registered provider (base platform, every family): reached from the provider's record — the grain picker (minute, hour, day, week, month, quarter, year, in the reporting calendar), the primary count and errors over the range, latency, the family's gauges, the health probe's last result and the top projects by volume. A model provider shows its token rows here and keeps its budget panel.
  • A providers overview listing every registered provider of every family with its health, today's count and errors on the reporting calendar's local day, and its gauges — the page an operator opens first.
  • v2 ModelUsageService (opschain docs resources/model_usage): ListProviderUsage(kind, code?, grain, from, to, metric?, group_by?, project_code?, timezone?) answers one ProviderUsageRow per bucket, provider and metric (and one more dimension on request — project, detail, agent or grant) with one total per metric the family registers; ListProvidersOverview() answers every registered provider as a ProviderOverviewRow. Both sit behind the provider-record administrator's verdict — the verdict every family's records are read under — 404-on-deny.
  • CLI: opschain admin providers usage --kind <kind> [--code <code>] [--grain day] [--from 30d] [--to …] [--metric …] [--by …] [-o json] (opschain docs cli/admin-providers-usage) and opschain admin providers overview [-o json] (opschain docs cli/admin-providers-overview) — the same verbs for every family.
  • OpenTelemetry metrics: every metric is exported per provider on the otlp_http SIEM connection as opschain.provider.<metric> with the provider.kind and provider.code attributes (and provider.detail where the row has one), so an external dashboard shows the same numbers as the page (opschain docs integrations-siem, "The usage metrics"). A model provider with a provider-level budget also ships opschain.provider.token_budget_remaining — a gauge per window the record sets a ceiling on, with the window attribute beside provider.kind=model and provider.code (opschain docs model-providers).

Model usage — token telemetry and token budgets​

Every model call an AI agent makes in brokered custody flows through the platform's model gateway, so the platform knows — as a fact, not an estimate — how many tokens each agent spent, on which model, under which grant, in which project. The gateway lands each served call as the model family's metric rows above, keyed on the model as the response reported it (detail), the agent, the project path and the most specific token-capped grant that applied — the model comes from the response, not the request and not the provider record, because a record can be repointed and an alias can resolve differently tomorrow; when a response names no model the request's own model field stands in. cache_read_tokens and cache_creation_tokens are recorded where the provider reports them (Anthropic's cache_read_input_tokens / cache_creation_input_tokens, the openai family's cached_tokens). Tokens and calls only: price is an opinion that changes monthly and belongs to finance, so no price, cost or currency appears anywhere — the CSV export is what a finance model, Power BI or Grafana prices itself.

Budgets — token caps on six windows, all enforced per call. Controls never read the telemetry buckets. A trust grant carries any subset of six windows, each a token count, each a shared counter row in the store (replica-safe, restart-safe; the in-memory window is the fallback only while the store errors) that the gateway checks before it forwards a call:

WindowFlag on opschain admin trust grantTypeResets
minute--tokens-per-minuteslidingcontinuously — the sum of the last sixty seconds (the burst cap)
day--tokens-per-day (alias --tokens)calendarlocal midnight in the reporting zone
week--tokens-per-weekcalendarlocal Monday 00:00
month--tokens-per-monthcalendarthe 1st, local
quarter--tokens-per-quartercalendarthe fiscal quarter start
year--tokens-per-yearcalendarthe fiscal year start

A call must pass every window set on every active token-capped grant that applies to the agent; the windows are checked minute first, then the calendar windows coarsest last, and the first window that fails is the one named — in the refusal the agent's model SDK surfaces verbatim and in the audited warn:trust:cap_refused event (window, grant_id, limit, used, provider, timezone, window_resets_at as the local boundary with its offset). A refused call lands as refused_calls and spends nothing. Unused budget never rolls over; an exhausted month refuses for the rest of the month. The remedy is a governed grant change — an emergency top-up rides the same approval funnel as every grant change, with its own evidence — never a silent degrade or an automatic roll-forward.

warn:trust:cap_approaching fires once per window per period when a window's spend crosses model_usage.cap_warning_fraction (default 0.8) of its cap — the once-per-period guard is itself a shared row, so a fleet of replicas warns once. The event carries the same fields as the refusal plus fraction.

A model provider record can carry the same six windows as a provider-level budget — a ceiling across every agent and every call through that provider, on one shared counter row per provider and window in the same substrate, checked after the grant windows (both apply, the tighter wins) and refused by name the same way; the two cap events carry scope: grant or scope: provider to tell them apart. The provider's dashboard, opschain admin providers usage --kind model --code <code> and the opschain.provider.token_budget_remaining gauge on the OTel export read the ceilings (model-providers).

Only input and output tokens count against a cap; cache tokens are recorded in the buckets but never metered, because a cache hit is the provider charging less for the same context, not the agent spending more.

Measure, then set the budget. A new agent runs under conservative caps. The trust review (opschain admin agent trust-review, the GUI's trust sheets) shows observed-against-cap for every window of every token-capped grant that applies to the agent — token_caps: per grant the windows (cap, used, fraction, the period's start and resets_at in the reporting zone) and the minute distribution behind them (p95_minute_tokens, peak_minute_tokens, minutes_observed). opschain agents usage --recommend-caps --grant <id> proposes a value for every window the grant carries (or all six with --all-windows) from the observed per-bucket distribution with stated headroom: the p95 of the window's bucket totals × --headroom (1.5 by default), rounded up, and never below the observed peak while fewer than twenty buckets were seen (a thin history should not propose a cap the agent already broke). --propose raises a replacement grant — the measured grant's subject, scope, actions, tier and act-as with the recommended caps — through POST /api/trust/grants, exactly the funnel opschain admin trust grant rides; because every applying grant's caps are enforced, the command then tells you to revoke the measured grant once the replacement is active. Budgets become evidence-based the way autonomy does.

The model surfaces. SummarizeModelUsage(project_code?, agent_code?, since, until, grain, group_by, timezone?) pivots the model rows back into token columns (ModelUsageRow) at one grain grouped on agent, provider, model, project or grant (or one total row per bucket), with totals and the project's calendar; ExportModelUsage answers the same window as CSV at the full tuple, superseded-calendar rows included and flagged; RecommendCaps(grant_id, headroom?, since?, all_windows?) answers the proposals beside the grant. These reads need read on the project's path (404-on-deny); an estate-wide read, no project named, needs read on the root. opschain agents usage (opschain docs cli/agents-usage) is the CLI; the dashboard panel Model tokens, the project agent page's Usage tab and the per-window gauges on the trust review sheets are the GUI.

Settings and configuration​

Four stored settings serve the whole substrate and one the token budgets, read live (opschain docs settings-reference, "Usage"):

KeyDefaultOverrideMeaning
usage.timezoneUTCglobal → projectThe IANA reporting zone the rollups and the calendar cap windows use.
usage.fiscal_year_start01-01global → projectMM-DD: when the fiscal year (and its quarters) start — 07-01 for Australia, 04-06 for the UK, 10-01 for the US federal year.
usage.minute_retention336h (14 days)globalHow long minute rows are kept.
usage.hour_retention2160h (90 days)globalHow long hour rows are kept (raised to the minute retention when set shorter).
model_usage.cap_warning_fraction0.8globalThe fraction of a token cap at which warn:trust:cap_approaching fires.

A project override lets a subsidiary on another calendar roll and budget on its own zone and fiscal year; the grant's calendar windows follow the project the agent is enrolled in; platform-level rows roll on the global calendar. No config.yaml key tunes this component beyond the rollup cadence test seam.

Governance and audit​

Reads of the telemetry are not audited. The provider reads (ListProviderUsage, ListProvidersOverview, the dashboard pages, the CLI) sit behind the provider-record administrator's verdict; the model reads behind read on the project. The events behind the model lane:

  • info:agent:model_call — every brokered call, with the response's model and token figures (cache figures where present).
  • warn:trust:cap_refused — a call refused pre-flight by the first failing window (scope: grant or scope: provider, cap: tokens, window, limit, used, provider, timezone, window_resets_at).
  • warn:trust:cap_approaching — a window crossed the warning fraction, once per period (fraction beside the refusal's fields).

What this component does not do​

  • It holds no price list and no currency: a cost view, where one is wanted, is a read-time overlay on these facts, never a stored one.
  • It never routes or selects a model by cost: steering models is an API gateway's job; the manifest names the provider, the record names the endpoint, the response names the model.
  • It puts no budget or cap on a non-model family: the secrets provider's rate limit, where set, is a client-side budget on OpsChain's own traffic, not a quota on the provider.
  • It does not replace the health probe: the dashboard shows the probe's last result beside the usage; a probe never becomes a metric.
  • It keeps no telemetry per user, and none for anything that is not a registered provider (runners, agents and the server have their own observability lanes).
  • opschain docs resources/model_usage — the data dictionary (model rows, provider rows, the overview row).
  • opschain docs cli/admin-providers — the provider usage verbs.
  • opschain docs ai-agents — "Budget, rate and token caps".
  • opschain docs model-providers — the gateway the model lane is read at.
  • opschain docs secrets-providers, opschain docs transport-providers, opschain docs image-builds, opschain docs identity, opschain docs integrations-siem, opschain docs integrations-github, opschain docs integrations-jsm — each family's page names what it records.
  • opschain docs resources/trust — the grant's cap fields.
  • opschain docs events — the event rows.