DocsOperate & extend
Operate & extend
Markdown ↗

Usagestat integration

On this pageProvider iconsPer-run usage

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:

APISDK methodPurpose
GET /v1/providersUsageStatClient.providers()Provider names, metadata, icon references
GET /v1/usageUsageStatClient.usage()Account snapshots and display metrics
GET /v1/usageUsageStatClient.limits()Quota resources derived from snapshots
GET /v1/usage/:instanceIdUsageStatClient.accountLimits()Quota resources for an explicitly bound account
ts
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):

ts
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.

ts
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:

json
{
  "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:

sh
npm run test:usagestat --prefix apps/desktop -- /absolute/path/to/usagestatd

That 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:

sh
USAGESTAT_URL=http://127.0.0.1:6736 \
USAGESTAT_BINDINGS_FILE=/private/path/quota-bindings.json \
node --import tsx --test tests/usagestat-quota.test.ts

The 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.

Maintained with the AgenticDriver SDKSource snapshot ↗