Usage and cost truth table

July 29, 2026 · View on GitHub

Agent Chronicle keeps integration evidence separate from billing claims. This page explains what each path can prove today.

Use the CLI version:

agent-chronicle usage truth-table
agent-chronicle usage truth-table --json

Summary

Integration pathWhat Chronicle can observeUpdate timing / freshnessUsage confidenceCost confidenceHard budget basis
Codex local usage importLocal Codex sessions, exact recorded parent lineage, model when present, non-cached input, cached input, output, reasoning tokens, turn count, timestampsRepeated rollout token_count events during a thread; --limit-sessions selects complete recent root groups, and later token growth reaches Chronicle only via dashboard refresh or an importer run with --refreshclient_reportedunknown; optional estimated_from_tokens with --estimate-costsAdvisory only from import; use token/runtime limits or proxy for hard dollar stops
Claude Code local usage importLocal Claude Code project JSONL usage, model when present, input, cache creation/read, output tokens, turn countAssistant message rows carry message.usage as transcript files are written; local journal/result rows are not the usage source in observed samplesclient_reportedunknown; optional estimated_from_tokens with --estimate-costsAdvisory only from import; use token/runtime limits or proxy for hard dollar stops
OpenCode JSON event stream importOpenCode JSON/JSONL step-finish token fields and client-reported cost when presentUsage/cost appear on captured step-finish eventsclient_reportedclient_reportedUseful for dashboards/advisory budgets, not provider invoice truth
Hermes local state importHermes state.db session rows, provider/model, input/cache/output/reasoning tokens, message count, client-reported cost fieldsSession-level fields are read from Hermes' local SQLite state databaseclient_reportedclient_reported when Hermes stores cost fieldsUseful for dashboards/advisory budgets, not provider invoice truth
OpenClaw JSONL local usage importOpenClaw JSONL assistant usage rows, provider/model, input/cache/output tokens, optional client-reported cost fieldsUsage appears on assistant message rows when OpenClaw writes JSONL session logsclient_reportedclient_reported when usage.cost.total is present; otherwise unknownUseful for dashboards/advisory budgets, not provider invoice truth
Cursor primary state observationComposer identity, client timestamps, explicit modelConfig.modelName, and exact child links from primary User/globalStorage/state.vscdb onlyOn explicit import, Dashboard refresh, or watcher scan; active WAL and source/schema races fail closedunknown; no token fields are selectedunknown; no cost fields are selectedNo token/dollar enforcement; proves bounded session presence only
Agent MCP workflow eventsSection started/checkpoint/completed/blocked events, machine checks, and blocker context reported by an MCP-capable agentOnly when the configured agent calls Chronicle MCP toolsunknown unless the agent supplies usage metadataunknown unless the agent supplies cost metadataNot a billing source by itself
Agent MCP usage debug snapshotsAgent-visible token/cost numbers or an explicit unavailable marker, plus client/session/turn join keysOnly when the configured agent calls agentacct_record_agent_usage_debug; stored as metadata for comparisonunknown in Chronicle totals; debug values live under metadata.agent_reported_*unknown in Chronicle totals; debug cost lives under metadata.agent_reported_cost_usd when providedComparison evidence only; never a hard budget basis
Native coding-agent hook captureHost-emitted lifecycle/session/turn/tool ids, relative file metadata, subagent links, and recognized machine-check exit codesOn each explicitly activated Claude Code, Codex, or Cursor hookunknown; hooks do not capture tokensunknown; hooks do not capture costNo token/dollar enforcement; objective exit codes may be machine evidence
OpenLIT / OTLP trace ingestionTrace/span/session ids, runtime lifecycle, tool-span metadata, allowlisted model labels, and telemetry-reported usage/cost fields when presentAs spans reach local POST /v1/traces, or on explicit JSON importtelemetry_reported, corroborating unless measurement provenance proves moretelemetry_reported, never provider-billed merely because it used OTLPAdvisory unless separately backed by provider-billed evidence
Paperclip snapshot connectorWork-item/execution/agent/work-product and cost claims from an exported snapshotOn explicit snapshot importunknownorchestrator_claim; missing and zero remain differentAdvisory; Chronicle never dispatches a Paperclip action
Entire Git checkpoint connectorPublic checkpoint/commit/session ids and allowlisted change/usage metadataOn explicit read-only repository scancheckpoint_reported, corroborating onlyunknownNo hard budget basis; Git proves artifacts, not billing
Provider/API proxyProvider/model, request estimate, provider usage fields, provider-billed cost when returnedRequest estimates before forwarding; provider usage/cost after each proxied responseprovider_reported when returnedprovider_billed when returned; otherwise estimated_from_tokensStrongest hard dollar enforcement path for traffic routed through Chronicle
Chronicle-owned process wrapperRuntime, exit code, stdout/stderr logs, pause/resume/kill ownership metadataProcess state while Chronicle owns the command; token/cost freshness needs import, proxy, or MCP eventsunknownunknownUseful for process/runtime control, not token or billing truth by itself

Important boundaries

  • MCP setup proves that an agent can report work to Chronicle. It does not automatically parse private session logs or prove exact billing.
  • MCP is a first-class semantic source, not a fallback to hooks. Hooks/OTLP can prove mechanical activity when MCP did not fire; they cannot invent task meaning.
  • Telemetry means machine-emitted runtime evidence. OTLP is its transport format, not a provider invoice and not the MCP protocol.
  • Metadata-only hooks do not capture token usage or cost. They deliberately omit prompts, responses, thoughts, tool arguments/results, stdout, and stderr.
  • Paperclip rows are orchestrator claims. Entire rows prove Git/checkpoint artifact metadata. Neither is silently promoted to provider billing or objective completion.
  • Agent usage debug snapshots prove only what the agent reported seeing about itself, including explicit "unavailable" reports. They are not included in Chronicle's token/cost totals.
  • Local usage import proves Chronicle parsed an implemented local client session/export path. Imported usage is client-reported and sanitized; prompts/transcripts are not stored in Chronicle events.
  • Current local usage truth rows include both metadata.usage_source=local_client_session_store and metadata.usage_provenance=agent_sentinel_local_usage_import. Ordinary event writes and MCP agentacct_record_event cannot create this trusted provenance.
  • Pricing-table estimates are equivalent-cost estimates, not provider invoices.
  • Provider/API proxy data applies only to traffic intentionally routed through Chronicle.
  • Subscription tools such as Claude Code and Codex may expose useful per-session token data without exposing exact marginal subscription billing.
  • Current local Codex and Claude Code subscription samples expose token/cache fields but do not expose provider-billed cost fields to Chronicle.
  • Ingestion receipts prove what Chronicle attempted and parsed from each configured local source. They do not upgrade client-reported usage to provider billing or prove that a stopped watcher will capture future sessions.

Pre-provenance ledger migration note

Older Agent Chronicle local usage import rows may have metadata.usage_source=local_client_session_store but no metadata.usage_provenance. Those rows are preserved as historical or diagnostic events, but they are no longer counted as usage truth totals.

To regenerate trusted usage rows, re-run:

agent-chronicle usage import-local --client all

or use the dashboard refresh action. The CLI importer recognizes legacy local-import-shaped rows for dedupe so an upgrade does not duplicate the same client_session_id; dashboard refresh can replace legacy rows for the same client/session with trusted rows. If you want a clean dogfood ledger, archive the old events.jsonl, clear the active event ledger, and then re-import local usage. Do not delete the source client logs under Codex, Claude Code, Hermes, OpenCode, or OpenClaw.

Old MCP semantic events from pre-MCP-v1 dogfooding can also be noisy. They can be archived with the old event ledger and left out of the active ledger when starting a clean local MCP v1 dogfood run.

Observed local timing

Codex local sessions write repeated rollout token_count events with last_token_usage and total_token_usage. On the observed local subscription sample, these appeared throughout an active thread, not only in a final archived-session record.

Claude Code local project files write usage on assistant message rows. On the observed local subscription sample, journal.jsonl started/result rows did not carry token or cost usage; they describe task orchestration results rather than billing truth.

For dashboard UX, treat every local import as a snapshot. The CLI importer and usage watch record each session once at first observation and never update it by default — a background watcher does NOT keep an already-imported session's totals live. Only the dashboard's "Refresh & save usage" button, or an importer run with --refresh, replaces re-observed rows with fresh totals. Show both last imported at and source last updated at before implying that a number is live.

Local imports also preserve parent/child session metadata when the client exposes it. Claude Code subagent transcript files are linked back to their parent sessionId, and Codex child threads use thread_spawn_edges or the rollout's own session_meta.parent_thread_id. If Codex marks a thread as a subagent without exposing a parent edge, Chronicle records it as a child with an unknown parent instead of guessing a root from timestamps or working directory.

Codex applies --limit-sessions after resolving that exact lineage. The number is a count of recent root conversation groups; activity on a descendant makes its group recent, and every discovered row in a selected group is returned. A missing-parent orphan remains its own group. Per-source diagnostics make the scope explicit with limit_unit=root_groups, selected_root_groups, returned_rows, and excluded_by_limit.

Codex internal workflow labels such as codex-auto-review are not model names. An internal row with no directly reported model may inherit one only from its exact recorded parent session. The saved usage metadata records client_model_source=inherited_exact_parent_session and the parent id when that happens. Without that exact parent evidence, the model remains unknown and the row remains unpriced; Chronicle never applies a scan-wide model fallback.

Ingestion receipts and sync health

A persisted import or dashboard refresh records owner-only, atomic per-source receipts at <store-dir>/ingestion-health/state.json. Receipts include last_attempt_at, last_success_at, parsed/skipped/error counts, a bounded error code, source watermark, scan limit, and source-specific limit diagnostics. --dry-run --json returns the same discovery diagnostics without writing usage rows or health state.

Inspect operational state through either interface:

agent-chronicle usage health
agent-chronicle usage health --json
curl http://127.0.0.1:8765/ingestion/health
curl http://127.0.0.1:8765/health

GET /health answers process/API liveness with ok; it also embeds ingestion_status and the ingestion snapshot. A green ok does not erase a degraded sync. GET /ingestion/health and usage health --json expose the direct per-source view. A clean manual scan leaves the overall state unknown, because it proves the past scan succeeded but not that future sessions are being watched. Overall healthy requires a fresh watcher heartbeat plus a successful receipt, from the current watcher epoch/build, for every source that watcher is configured to scan. Older receipts outside the live watcher's scope remain visible as historical rows but do not make the live watcher falsely green or red.

usage watch uses a single-writer lease per store and refreshes its heartbeat around scans. A live duplicate is rejected before scanning; a stale lease can be replaced, and a lost lease makes the former watcher exit instead of continuing as a competing writer. A watcher process keeps the code version loaded when it started, so upgrades require an explicit watcher restart. Stop it through the terminal or process supervisor that launched it, start the watch command again, then confirm the new watcher.importer_version and heartbeat with usage health --json.

Join health has a different scope from scan health. agent-chronicle event summary --json, agentacct_get_event_summary, and GET /events/summary calculate canonical context-match and attribution coverage over every matching store event, even when limit bounds the recent aggregate window. result_scope.partial explains that recent window; usage_context_bridge.detail_scope.partial explains any capped links, attributions, or unlinked_contexts arrays. The full-store ratios, health_status, coverage_complete, and degraded_reasons remain canonical and must not be recomputed from the returned detail sample. The older joinable field retains its compatibility meaning of instrumentation readiness; it is not a completeness score.

Pricing catalog

Optionally estimate equivalent cost from known model pricing rows:

agent-chronicle usage import-local --client all --estimate-costs

The built-in pricing catalog is intentionally small. To extend coverage without changing code, Chronicle keeps a local LiteLLM model_prices_and_context_window.json snapshot in Chronicle state. Dashboard and usage import-local --estimate-costs automatically use that state-local snapshot when it exists:

agent-chronicle cost pricing-catalog --store-dir .agent-sentinel/state --refresh
agent-chronicle cost pricing-catalog --store-dir .agent-sentinel/state --provider openai --model gpt-4o-mini --json
agent-chronicle usage import-local --client all --estimate-costs

Auto-refresh (TTL). Every pricing path — usage import-local --estimate-costs, each usage watch --estimate-costs tick, and the dashboard's "Refresh & save usage" button — refreshes the store-local snapshot automatically when its fetched_at sidecar timestamp is older than 7 days, the snapshot is missing, or the snapshot no longer parses (a torn/corrupted file counts as stale and is repaired; the sidecar records last_refresh_error: snapshot unreadable until it is). A fetched_at more than a few minutes in the future (clock skew at fetch time) also counts as stale. The refresh is best-effort by contract: a failed download records last_refresh_error / last_refresh_attempt_at in the sidecar, keeps the stale snapshot, and never blocks or fails an import. Failed attempts back off: while last_refresh_attempt_at is less than an hour old the refresh is skipped entirely (reason throttled), so in the steady state an unreachable or slow network costs at most one download attempt per hour per store — never one per tick — and every import proceeds immediately on the existing snapshot. Snapshot writes are atomic (per-write unique temp file + rename), so a concurrently-serving dashboard never reads a torn file. Disable the auto-refresh with AGENTACCT_PRICING_AUTO_REFRESH=0; cost pricing-catalog --refresh stays available as the force-now path. No telemetry: the download is a plain GET of LiteLLM's public open-source pricing table — nothing about your machine, store, or usage is sent.

You can still point at an explicit local Chronicle or LiteLLM JSON catalog with --catalog-path or AGENTACCT_PRICING_CATALOG_PATH; a pinned catalog is respected as-is and is never auto-refreshed.

Provider aliases: claude-code rows fall back to anthropic-keyed prices and codex rows to openai-keyed prices when no exact (provider, model) entry exists — exact entries (including the deliberate builtin codex/gpt-5.5 fast-pricing rows) always win before the alias. The builtin fast-pricing rows (the ones carrying a cost multiplier) are also protected on the merge path: an external snapshot or pinned-file row keyed exactly codex/gpt-5.5 never replaces them, so the 2.5x convention cannot be silently dropped by upstream catalog drift; external rows still extend coverage for every key the builtin table lacks.

The pricing refresh downloads only the public LiteLLM model cost map. Estimated costs are labeled estimated_from_tokens, not provider billing. Unknown models remain unpriced until a trusted catalog entry exists.

Stored-row stability vs. the unknown→priced transition. Once a stored row is priced, later catalog price drift never rewrites it. The ONE exception is the unknown→priced transition: on a --refresh --estimate-costs scan (or the dashboard refresh, which always prices), a re-observed row whose stored cost_confidence is unknown and whose (provider, model) NOW resolves in the catalog is replaced with a priced row — pricing_source provenance stamped, event id reissued once — after which it is priced and stable like any other row. client_reported costs are never overwritten.

Practical interpretation

For developer onboarding:

agent-chronicle init --agent codex
agent-chronicle usage import-local --client all --dry-run --json
agent-chronicle usage discover-sources
agent-chronicle usage truth-table
agent-chronicle serve --store-dir .agent-sentinel/state

For API-key experiments with hard dollar caps:

agent-chronicle cost proxy \
  --enable-forwarding \
  --forward-provider openai \
  --max-total-usd 0.01

For normal subscription-based coding-agent workflows, treat local import and MCP events as a local activity ledger. Use the confidence labels on reports and dashboards before making budget or ROI claims.