DocsOperate & extend
Operate & extend
Markdown ↗

Usage accounting

On this pageIdentity and explicit account bindingMeasurements and coverageVersioned records, privacy and retentionEmbedding usage

AgenticDriver records one aggregate metering event for each executed run. Run metering, an account quota snapshot, an API-equivalent estimate, and an invoice are distinct observations. The SDK does not derive subscription spend, available quota, or an invoice amount from token counts.

Usagestat is the usage backend dependency. Its existing Rust core and daemon own durable account history, quota observations, pricing/probes and provider metadata. The SDK's responsibility is producing normalized execution measurements and delivering them through a thin integration. Native run ingestion accepts those events separately from the existing daily-report importer and connects through an optional SDK sink. It does not create a parallel usage backend inside AgenticDriver. Applications that do not enable metering can use the SDK without a running Usagestat service.

Identity and explicit account binding

Configure a persistent host ID and opaque account IDs for any host that sends records to shared storage or joins account quotas:

ts
const driver = new AgenticDriver({
  providers: [personalOpenAI, companyOpenAI],
  usage: {
    hostId: "driver-installation-01",
    accounts: {
      "openai-personal": "account-personal-01",
      "openai-company": "account-company-01",
    },
    labels: { app: "brandstorm", environment: "development" },
    retentionDays: 30,
  },
  onUsage: (record) => usageStore.accept(record),
});

personalOpenAI and companyOpenAI are configured adapters with the corresponding instance IDs. Account IDs are host assertions about their selected credentials; the SDK does not discover or hash credentials to create an account ID. Preserve the ID when rotating a credential for the same account. Assign a new binding and provider instance when selecting a different account. IDs should be opaque identifiers, without email addresses or credential values.

IdentityMeaning
hostIdStable namespace for an execution host installation.
providerThe explicitly selected provider instance within that host.
accountIdA trusted host binding to a billing/subscription account; absent when unbound.
subjectThe authenticated application user, supplied by the host's bearer-token mapping.
runId / eventIdUUID of the execution and its single aggregate event.

The same vendor can have many accounts. Never use vendor, model name, display name, authMode, or usageStatId as an account join key. Include hostId, provider, accountId and the authorized subject when selecting account data. Deduplicate delivered aggregate events by (hostId, eventId), independent of labels or quota fetch time. An idempotent run replay returns the original result without another usage event or provider invocation.

If hostId is omitted in an embedded driver, it gets a fresh process-local UUID for that driver object. No account is inferred. Account bindings require an explicit host ID. init writes a persistent random usage.hostId into new host configurations; --account-id optionally binds the selected provider. Existing configurations can add usage.hostId and each provider's accountId. Keep host IDs unique when cloning installations. driver.usageIdentity(provider, subject) returns the trusted host-side identity to use in a quota lookup.

Optional resource policies use that same trusted identity for subject/account admission. UsageStatClient.quotaAdmission() reads existing scoped quota snapshots with explicit unit, freshness and unknown-value policies. Per-run observed token/cost guards do not replace Usagestat storage or reserve account-wide spend.

Measurements and coverage

Every measurement is optional. Absent means unknown. Explicit zero means the adapter reported zero. Invalid, negative, nonfinite, fractional token counts, unsafe integer sums, and impossible cache/reasoning subsets are omitted.

FieldMeaning
inputTokensAll reported input tokens, including cached input.
cachedInputTokensCache-read subset of input tokens; never add it to input again.
outputTokensReported generated tokens including reasoning when known.
reasoningTokensReported reasoning subset of output tokens; never add it again.
costUsdExplicit provider-reported run cost. It is not inferred or invoice-verified.
apiEquivalentCostUsdReported dollar estimate at the reporting CLI/adapter's API rates.

Anthropic input totals sum uncached input, cache reads, and cache writes only when all three counters are present. Missing cache components make that aggregate unknown. Gemini output totals sum candidate and thought counters when both are known; a reported total minus reported prompt count can also supply the output total. A missing reasoning counter stays absent. Gemini CLI model aggregates require the counter from every reported model entry. These mappings follow the provider definitions for Anthropic usage and Gemini usage metadata.

Claude Code's local dollar figure is an estimate and may use configured rates. Its value is carried as apiEquivalentCostUsd; it does not establish a charge against a subscription. The SDK does not calculate another estimate from a price table. See Claude Code cost reporting.

The source on a metering record identifies provider-response, cli-report, adapter-report (the default for custom adapters). This is provenance, not independent billing verification. Historical records with synthetic provenance remain readable; live adapters cannot declare that source.

Each record contains:

  • usage: totals only for fields reported by every started model step.
  • observedUsage: sums of available reported values, with unknown steps excluded.
  • coverage.startedSteps and coverage.completedSteps: model invocations begun and complete model turns received. These count logical steps, not HTTP attempts.
  • coverage.reportedSteps: number of completed steps reporting each valid field.

For example, if step one reports 10 input and 2 output tokens, then step two reports only 20 input tokens, the complete usage is {inputTokens: 30} and the observed subtotal is {inputTokens: 30, outputTokens: 2}. If step two is interrupted without a complete turn, complete totals are absent and the first step's known measurements remain in observedUsage. Do not bill or infer remaining quota from those subtotals. A failed/cancelled run may still have consumed provider resources. Unreported measurements are not recoverable from the truncated stream.

Versioned records, privacy and retention

The aggregate envelope is agenticdriver.usage.v2. It includes the identity, model, vendor, auth mode, status, source, timestamps, duration, measurements, coverage and trusted host labels. Its generated schema is protocol/usage-record.schema.json. validateUsageRecord also checks identity, timestamp and coverage consistency.

Version 2 replaces the prototype v1 metering envelope. Existing v1 records lack host/account identity and measurement coverage; keep them in their original namespace or migrate them with a verified external mapping. Do not automatically join them to an account or reinterpret a historical CLI costUsd as invoiced spend. The HTTP run protocol remains version 1.0; apiEquivalentCostUsd is an additive optional usage field supported by all four bundled clients.

Request metadata is excluded from metering even when it contains keys such as subject, accountId, hostId, or app. usage.labels is an explicit trusted host configuration for short nonsecret labels. Keep credentials, prompts, email text, document passages and private reasoning out of those labels. No prompt, generated text, tool argument/result or credential field exists in the metering schema. The JSONL sink validates and copies a record before writing it, rejecting unknown versions and extra private payload fields.

retentionDays optionally sets each record's expiresAt relative to its finish time. It is a storage policy hint and never a run timeout. A durable sink must enforce that deadline for primary data, derived indexes and backups; use usageRecordExpired(record, now) when accepting or retaining a record. Without this option, retention belongs to the sink's configured policy. The local JSONL sink appends private files but does not rotate or purge old lines; provide rotation or use a sink with deletion support. Deleting metering records does not authorize deleting idempotency/recovery barriers, which have a separate lifecycle.

onUsage remains optional and bounded to protect run delivery from a stuck sink. It is not a guaranteed durable delivery queue. Record IDs and the v2 schema are the contract implemented by the optional Usagestat service dependency. Reject unknown versions, preserve event IDs during retries, authenticate the source host, validate its allowed accounts/subjects at ingestion, and isolate read access by subject. A client-supplied JSON record is never sufficient proof of its claimed identity.

Embedding usage

Retrieval uses the same v2 usage envelope and optional Usagestat sink. Configure metering explicitly on the retrieval service, independently of generation:

ts
const retrieval = new RetrievalService(corpora, {
  embeddingBatch: { maxTexts: 32, maxBytes: 65_536 },
  usage: {
    hostId: "driver-installation-01", // same persistent installation ID as generation
    labels: { app: "literature-review" },
    onUsage: metering.usageSink(),
    onTelemetryError: reportCaptureFailure,
  },
});

Each embedding batch and query produces its own record UUID with metadata.operation: "embedding", metadata.purpose: "index" | "query", and a metadata.parentRunId when called within a UUID-identified operation. Grouping metadata does not change the event's deduplication identity. The account comes from the embedding adapter's explicit providerId/accountId binding. Configure that binding and its allowed subjects in Usagestat separately from the generation account. Reusing a provider instance ID for conflicting embedding account identities is rejected. Optional metering labels are limited to 29 to leave room for the three operation labels.

An embedding call is one metered step. OpenAI-compatible reported prompt tokens are input tokens with source provider-response; unreported measurements remain unknown. Failed/cancelled calls can still consume resources and have unknown final usage. Successful batches remain recorded even if a later batch fails and the index replacement rolls back. Reindexing identical content emits no duplicate embedding usage; ingestion preparation itself is not a model invocation. Generation usage contains only its own model steps.

Records contain no passages, queries, PDF bytes or mail content. A sink failure is reported through the telemetry error hook and never repeats an embedding or changes the index result. The callback is bounded to two seconds as telemetry I/O, not an execution deadline. The SDK has no embedding usage database or outbox; native Usagestat owns storage, retention, scoped reconciliation and offline forwarding. Durability begins when that backend acknowledges capture.

Maintained with the AgenticDriver SDKSource snapshot ↗