Usagestat integration
AgenticDriver uses Usagestat as an optional local or remote service dependency for usage storage, retention, forwarding, quotas and provider metadata.
It does not fork its provider probes, credential discovery, or logo collection.
Pin Usagestat v2.0.0
(fbf84796d80fd5567516cec2e11b0784e7c187db) for native readback and optional
durable run ingestion. Ingestion is disabled unless explicitly configured;
even a stable version string does not establish that the running service enables
it. Check the ingestion protocol before capture. The stable native Linux builds
require glibc 2.39 or later; its optional npm installer requires Node 24 or later.
This does not change the SDK's own Node requirement.
Upgrade from SDK 0.2.0-rc.1: desktop readback already uses native routes,
but that SDK's limits() and accountLimits() call nonexistent /v1/limits
routes. 0.2.0-rc.2 includes the #76 correction
described here. Applications validating the derived schema must use
agenticdriver.usagestat-limits.v1. Updating Usagestat alone cannot repair an
older installed SDK client.
The inspected Usagestat endpoints are:
| API | SDK method | Purpose |
|---|---|---|
GET /v1/providers | UsageStatClient.providers() | Provider names, metadata, icon references |
GET /v1/usage | UsageStatClient.usage() | Account snapshots and display metrics |
GET /v1/usage | UsageStatClient.limits() | Quota resources derived from snapshots |
GET /v1/usage/:instanceId | UsageStatClient.accountLimits() | Quota resources for an explicitly bound account |
import { UsageStatClient } from "@agenticdriver/sdk/usagestat";
const usagestat = new UsageStatClient({ url: "http://127.0.0.1:6736" });
const providers = await usagestat.providers();
const snapshots = await usagestat.usage();
const limits = await usagestat.limits();
// For per-run capture, configure the authenticated sink described below.Every built-in adapter has a usageStatId, such as codex, claude, gemini,
openai-api, or xai. Multiple execution instances may map to the same provider;
apps should maintain explicit account/instance mapping when joining quotas.
Do not assume a provider-level quota snapshot belongs to every user or account.
accountLimits provides a lookup through an explicit mapping. Configure it on
the trusted application server with the upstream instance from Usagestat's
instanceId configuration (the emitted providerId/limits map key):
const accountQuotas = new UsageStatClient({
url: "http://127.0.0.1:6736",
accounts: [
{
hostId: "driver-installation-01",
provider: "openai-personal",
accountId: "account-personal-01",
instanceId: "openai-api-personal",
subjects: ["authenticated-user-id"],
},
],
});
const quota = await accountQuotas.accountLimits({
hostId: "driver-installation-01",
provider: "openai-personal",
accountId: "account-personal-01",
subject: "authenticated-user-id",
});Derive that identity from the trusted host and authenticated session. No match
fails with QUOTA_UNBOUND before any request. The client requests only the bound
GET /v1/usage/:instanceId endpoint and returns that account's quota resources. Missing
or errored snapshots fail with QUOTA_UNAVAILABLE; provider-level fallback is
disabled. Duplicate bindings and assigning one upstream instance to different
host/account identities are rejected. The upstream connection is selected once
for the client, with redirects disabled. Raw limits()/usage() are administrative
reads of all accounts and should not be exposed directly to application users.
The scoped native endpoint itself is still an administrative read, not an
independently authenticated end-user API. The application server owns the trusted
mapping and authorization; it must not accept identity fields from an untrusted
request or infer a binding from the provider family.
Usagestat has no separate limits endpoint. The client derives quota resources from
/v1/usage snapshots with limitsFromSnapshots() (schema
agenticdriver.usagestat-limits.v1, replacing the never-served upstream
crossusage.limits.v1 literal): each progress line becomes a resource keyed by
its slugged label (Session → session, repeats get -2, -3), with used,
limit, remaining, utilization, unit (percent, dollars, count or
count:<suffix>), resetsAt and the original label. Failed providers (an error
source, any non-ready state or an Error badge) are listed in errors. Progress lines
with negative/nonfinite values, an unknown format or malformed reset time are omitted, so admission treats
them as unknown.
fetchedAt, source, and errors must be considered before treating a quota
snapshot as current. Quotas and per-run token metering are different observations.
Receiving a cached response does not refresh fetchedAt. Resource labels are
provider-defined and may change; inspect the returned keys rather than assuming
every account reports session and weekly. Freshness is the caller's explicit
maxAgeMs policy; the separate T3 compatibility adapter's TTL is not a native
API guarantee.
Provider icons
Use the shared provider catalog and asset guide for browser-safe identity displays, account quota freshness and an allowlisted asset cache. It includes the same integration recipe for Brandstorm, LitAgent and AI Workspace.
The client retains Usagestat's icon.path, icon.colorPath, icon.url, variant
metadata, and brand color where the daemon supplies them. Filesystem paths refer
to the Usagestat host, so a remote browser cannot use them directly. Applications
should resolve known catalog entries into their own trusted asset pipeline and
preserve the source icon license/notices. Do not create an arbitrary filesystem
read endpoint or copy browser credentials to retrieve icons.
No copied logos or new artwork are needed for the SDK itself, which is headless. The existing brand assets remain owned by Usagestat and their respective licensors.
Per-run usage
Usagestat 2.0.0 includes the optional native run-ingestion contract (AD-030).
Enable it with
usagestatd --run-usage-config /absolute/path/run-usage.json; add --no-poll for
an ingestion-only service. The backend configuration, account bindings and
forwarding controls are documented in Usagestat's
docs/run-ingestion.md.
An installation without the enabled route fails explicitly. Always call
ingestionProtocol() to verify
usagestat.run-ingestion.v1 accepting agenticdriver.usage.v2; these are separate
contract versions, not a requirement for a /v2 HTTP API. The native ingestion
daemon requires a loopback listener and its separate configured bearer credential.
Prefer a separate ingestion-only service alongside the execution host. Keep the
existing quota/readback daemon and its provider account setup intact. Configure
bindings from that host's actual usage.hostId, provider instance id, explicit
accountId and authenticated connection subject. Do not infer any of these from
a display name or a provider family. Provider credentials remain on the execution
host; the ingestion credential is a different service secret. A connection's
execution permissions and expiry are still enforced by AgenticDriver.
Usagestat's stable release reconciled unchanged real SDK execution and embedding records, including duplicate delivery, scoped reads and restart. See its release handoff. That is backend qualification, not evidence that an arbitrary application host already has ingestion configured. Verify the actual host's new records after connecting it.
Records now carry agenticdriver.usage.v2, stable host and optional account
identity, authenticated subject, timestamps, source, coverage, known subtotals,
complete measurements, and trusted host labels. Request metadata is excluded.
See usage identity, accounting and retention for schema migration,
deduplication, unknown measurements and API-equivalent estimates.
const metering = new UsageStatClient({
url: "http://127.0.0.1:6736",
token: async () => readBackendCredential(), // application-supplied secret lookup
});
await metering.ingestionProtocol(); // checks agenticdriver.usage.v2 support
const driver = new AgenticDriver({
providers: [selectedProvider],
usage: {
hostId: "driver-installation-01",
accounts: { "openai-personal": "account-personal-01" },
},
onUsage: metering.usageSink(),
onTelemetryError: reportCaptureFailure,
});The native backend token must authorize this exact host, provider instance,
account and authenticated application subject. Missing SDK account bindings fail
before capture. capture(record) returns a versioned receipt after backend
commit; resending that same record deduplicates. run(identity, eventId) retrieves
its scoped record and delivery state for reconciliation. retryForwarding(identity, eventId) explicitly queues a retained permanent forwarding failure after its
cause has been repaired. It never reruns the agent.
The host CLI uses existing secret references:
{
"usage": { "hostId": "driver-installation-01" },
"usagestat": {
"url": "http://127.0.0.1:6736",
"tokenRef": { "file": "usagestat-ingestion.key" },
"ingestionTimeoutMs": 1000
}
}Add these fields to the host configuration and give every provider instance
its explicit accountId. SDK tokens and Usagestat ingestion tokens are separate
credentials. configuredDriver reports an unacknowledged capture through its
onTelemetryError option or a redacted stderr message. URLs require HTTPS except
for loopback HTTP; redirects are disabled. The default 1-second capture timeout
(bounds 100–1500 ms) covers telemetry I/O and secret lookup, not agent execution.
The SDK still has no default run deadline or inactivity timeout.
For durable offline forwarding, use SDK → local Usagestat → remote Usagestat. The local backend stores records and handles capacity, retries, retention and remote reconciliation. The SDK contains a thin sender, with no second usage backend or durable outbox. Direct remote capture also works, but an unavailable first hop is a capture failure; durability begins after its acknowledgement. Capture failures do not replace a successful run result, and idempotent run replay does not regenerate or re-emit telemetry. Applications needing recovery before capture acknowledgement must preserve the original report and resend it, not execute the request again. Backend receipt expiry is authoritative; use a record-level expiry when a cutoff must remain fixed across later retention-policy changes.
The optional jsonlUsageSink remains a diagnostic append-only sink with no
rotation, deletion, synchronization or offline-delivery guarantee. Prefer the
native backend for durable usage collection. It does not add SDK records to
Usagestat's daily-import/provider-probe totals, which could double-count usage.
Validate the existing backend using its own native checks. The desktop read integration can be exercised with the actual binary:
npm run test:usagestat --prefix apps/desktop -- /absolute/path/to/usagestatdThat check copies the actual daemon into a temporary profile with no provider
plugins and verifies its empty read contract. No provider can be polled. It works
with stable 2.0.0 and older alpha.4, which has no --no-poll flag. It does not
invent accounts or run measurements.
To verify usage ingestion, run meaningful prompts on an explicitly configured real
provider and reconcile the host's actual usage records with the existing backend.
The quota correction has an opt-in read-only check against existing real accounts:
USAGESTAT_URL=http://127.0.0.1:6736 \
USAGESTAT_BINDINGS_FILE=/private/path/quota-bindings.json \
node --import tsx --test tests/usagestat-quota.test.tsThe private file is an array of explicit UsageStatAccountBinding objects like
the mapping above. The check keeps them in memory, issues only native usage
reads, verifies real ready/failed snapshots and admission, and rejects unbound
subjects without a request. It does not provision connections, install production
account mappings or invoke a model. An optional USAGESTAT_CHECK_REPORT path
records sanitized counts without account labels or credentials. CI without an
explicit real endpoint skips this live check; a skip is not account qualification.