# Token & Cost Telemetry (https://jackin.tailrocks.com/reference/research/agent-telemetry/token-cost-telemetry/)



**Status**: Partially implemented (Phase 3, [Agent Orchestration Program](/reference/research/agent-orchestration/program-research/))

**Implementation note**: Both halves of the design have a landed Capsule slice. The **account-quota** side shipped with the Capsule usage overlay (a Capsule-local Turso `account_usage_snapshots` cache feeding the focused status signal + provider overlay). The **token-spend** side shipped in #532: the in-container token monitor reads each agent's provider logs (Claude/Codex/Kimi JSONL, Amp thread JSON, OpenCode via the async `turso` reader), recomputes per-session totals idempotently each poll, fills cost from the provider stream or the static pricing table, and the daemon polls it on a self-throttled 30s/60s cadence; totals are exposed as `TokenUsageSummary` over the control channel and printed by `jackin-capsule token-usage <id>`. That code (<RepoFile path="crates/jackin-usage/src/token_monitor.rs">crates/jackin-usage/src/token\_monitor.rs</RepoFile>, re-exported by <RepoFile path="crates/jackin-capsule/src/lib.rs">crates/jackin-capsule/src/lib.rs</RepoFile>) is the source of truth; this page tracks only the remaining work below.

**Adapter hardening still open**: each provider reader currently globs *all* of that provider's session logs in the container and attributes them to the polling session, so two concurrent same-provider agents see the union of both — per-session file correlation (by cwd / session metadata) is the remaining accuracy work, and the field/path assumptions want validation against real provider output (Implementation sequence steps 2–3). **Host-global telemetry still open**: per-instance `usage_samples` storage substrate, workspace/session spend summaries, the shared `account/*` and `usage/*` daemon API + `jackin usage --json` CLI, and the console/Desktop spend badges. The quota-window cards and richer model catalog were deliberately dropped from the Capsule slice (they duplicate the shipped account-quota plane).

## Problem [#problem]

jackin❯ has no idea how much an agent has cost the operator. Token usage,
cache reads/writes, dollar cost — all of it is invisible. For a developer
running one Claude session this is fine; for a team running several
parallel agents on different projects, it's expensive ignorance:

* The operator can't tell which workspace burned 10× more tokens than the
  others.
* The autonomous queue (Phase 4) can't enforce a per-task or per-day
  budget.
* The cost model can't be optimized — operators don't see when prompt
  caching is saving them money vs. when it isn't.

multicode tracks this per-workspace and renders it in their TUI. CodexBar and OpenUsage show the adjacent desktop-account version of the same problem: operators do not only care about per-session tokens, they care about subscription windows, reset times, weekly caps, provider health, and whether starting one more agent will burn through an account limit.

## Why It Matters [#why-it-matters]

* It's the simplest cost-awareness primitive that doesn't require a
  vendor-specific billing integration.
* It makes [declarative resource
  limits](/roadmap/declarative-resource-limits/) feel
  finished — operators see RAM/CPU usage, but cost is the dimension they
  most care about for AI agents.
* It enables future budget gates ("don't queue another task if this
  workspace exceeded $X today") without those gates having to reinvent
  the underlying tracking.

## Inspiration in multicode [#inspiration-in-multicode]

**Sources**:

* No dedicated README section (implementation-only feature)
* Source — [`lib/src/services/usage_aggregation_service.rs`](https://github.com/graemerocher/multicode/blob/main/lib/src/services/usage_aggregation_service.rs) (token + cost aggregation)
* Source — [`lib/src/services/resource_usage_service.rs`](https://github.com/graemerocher/multicode/blob/main/lib/src/services/resource_usage_service.rs) (CPU + RAM + OOM sampling)

multicode's `usage_aggregation_service` (see [`lib/src/services/usage_aggregation_service.rs`](https://github.com/graemerocher/multicode/blob/main/lib/src/services/usage_aggregation_service.rs))
parses the OpenCode message history, sums input/output
tokens per session, multiplies by a synthetic per-model cost table, and
exposes:

* `usage_total_tokens: u64`
* `usage_total_cost: f64`
* `usage_cpu_percent: u16` (CPU)
* `usage_ram_bytes: u64` (RAM)
* `oom_kill_count: u64`

These live on the workspace snapshot (transient — lost across TUI
restarts). The TUI renders cost and tokens as columns in the workspace
table.

The cost table is hardcoded per model. Re-using their per-1000-token
prices and approach is fine; the more interesting work is making
jackin❯ pricing pluggable across runtimes.

## Inspiration in CodexBar and OpenUsage [#inspiration-in-codexbar-and-openusage]

**Sources**:

* CodexBar — [https://github.com/steipete/CodexBar](https://github.com/steipete/CodexBar)
* CodexBar provider / refresh documentation — [https://github.com/steipete/CodexBar/tree/main/docs](https://github.com/steipete/CodexBar/tree/main/docs)
* OpenUsage — [https://github.com/robinebers/openusage](https://github.com/robinebers/openusage)
* OpenUsage plugin API — [https://github.com/robinebers/openusage/tree/main/docs](https://github.com/robinebers/openusage/tree/main/docs)

CodexBar and OpenUsage are better references for account-limit telemetry than for jackin❯ session telemetry. Both are menu-bar-first tools that collect provider usage from existing local state or provider APIs, then render quota bars, reset countdowns, and stale/error states. CodexBar is broader and deeper on local/provider source handling: it supports Claude, Codex, Amp, Kimi, OpenCode, and many providers outside jackin❯; exposes configurable refresh cadences; renders reset windows; ships a CLI for local cost scans; and treats browser cookies / API keys / OAuth / local CLI state as provider-specific source strategies. OpenUsage is the cleaner product reference: a lightweight menu-bar panel, automatic refresh, global shortcut, local HTTP API on `127.0.0.1`, proxy support, and an explicit plugin direction for adding providers.

Useful ideas:

* Track **account quota snapshots** separately from per-session token samples. A Claude weekly limit, Codex session window, Amp free-tier usage bucket, or OpenCode workspace subscription window is not the same entity as a single assistant-message token sample.
* Store reset-window metadata: provider, account label, plan/source, usage amount, limit amount when known, reset time, stale/error state, and provenance.
* Keep refresh state resilient: background refresh, stale-but-usable cached values, throttled errors when prior data exists, and a clear repair/login state when no data can be trusted.
* The daemon's `account/*` and `usage/*` endpoints (specified below) are the local read API; resist the temptation to ship a second always-on HTTP server.
* Treat provider adapters as a small, typed set for the built-in jackin❯ runtimes (`claude`, `codex`, `amp`, `kimi`, `opencode`) instead of inheriting CodexBar/OpenUsage's generic provider marketplace shape.
* Prefer runtime-owned credentials or credentials forwarded by jackin❯ over host-wide browser-cookie scraping. Cookie import can exist as an explicit diagnostic or fallback, but it must not become the primary path for account state jackin❯ already launched.
* Preserve the host-mutation rule: reading host-side auth/config is allowed when surfaced; writing provider config, Keychain entries, browser profiles, or host dotfiles is not part of telemetry refresh.

What not to copy:

* Do not build a generic subscription tracker. jackin❯ should monitor the accounts and runtimes it launches, plus explicit operator opt-ins, not every AI provider installed on the host.
* Do not make hidden browser-cookie scraping the happy path. It increases permissions, fragility, and trust cost.
* Do not expose broad host filesystem scans. Saved workspaces, running role containers, known agent auth locations, and explicit provider settings are enough.

## Better accuracy references [#better-accuracy-references]

CodexBar and OpenUsage remain useful UI references, but they are not the reliability baseline. The better roadmap inputs are:

* **Tokemon** — [https://github.com/richyparr/tokemon](https://github.com/richyparr/tokemon) and [https://www.tokemon.ai/](https://www.tokemon.ai/). Best reference for Claude-specific accuracy: OAuth / provider usage endpoint as primary truth for 5-hour and rolling-week quota, JSONL logs as fallback for local token/cost estimation, multi-profile support, burn-rate projection, warning thresholds, and a terminal statusline export. The main caution is scope: it is Claude-first and small-community, so jackin❯ should borrow the source-priority model rather than treat the implementation as canonical.
* **Token Tracker** — [https://github.com/mm7894215/TokenTracker](https://github.com/mm7894215/TokenTracker) and [https://www.tokentracker.cc/](https://www.tokentracker.cc/). Best reference for local-first project attribution across many agent CLIs: hooks where available, passive log readers where hooks are not available, normalized buckets keyed by source/model/time, local dashboard, menu bar app, widgets, `status --json`, and explicit "token counts only, never prompts" positioning. The main caution is that auto-installing hooks into host tool configs would violate the jackin❯ host-mutation rule unless the operator explicitly opts in; jackin❯ already owns role launch and can install container-side hooks without touching host configs.
* **Brim** — [https://getbrim.tech/](https://getbrim.tech/). Best reference for trust-minimized UX: narrow provider scope, native menu bar rings, manual refresh for server quota, local log reads for token/cost breakdown, no background network traffic unless requested. The main caution is that manual refresh alone is too passive for the jackin❯ daemon-backed status bar; jackin❯ should use a bounded cache TTL with a visible freshness label.
* **MeterBar** — [https://github.com/shipshitdev/meterbar.app](https://github.com/shipshitdev/meterbar.app) and [https://meterbar.app/](https://meterbar.app/). Useful reference for a simple native account dashboard, Keychain-local credentials, CLI JSON output, and provider API source mapping. The caution is that direct host credential-file access must be surfaced and scoped; jackin❯ should prefer daemon-owned account connectors and 1Password / Keychain / existing forwarded auth sources over app-level ad hoc reads.
* **Control Tower** — [https://github.com/krishcdbry/ControlTower](https://github.com/krishcdbry/ControlTower) and [https://control-tower.dev/](https://control-tower.dev/). Useful as a product comparison for multi-account, alerts, usage history, quiet hours, and provider detail pages, but the public project appears much less mature than Tokemon, Token Tracker, or CodexBar. Treat it as UI comparison only.

jackin❯ needs **two truth channels**, not one:

1. **Quota availability** comes from the provider's server-side usage/quota source whenever available. This is the number that answers "will this account hit a subscription or session limit?" Local JSONL token totals are not authoritative for this question because providers can apply hidden weighting, cache accounting, model-specific quotas, or server-side resets.
2. **Workspace/project cost** comes from jackin❯-owned session attribution: runtime hooks, structured transcripts, local logs, and Capsule/session metadata. This is the number that answers "which workspace burned the tokens?" Provider quota pages usually cannot answer that at jackin❯ workspace granularity.

Any UI that mixes these numbers must label them separately: **Account availability** and **Workspace spend**. A single percentage without provenance is a bug.

## Recommended Shape [#recommended-shape]

A small **usage adapter** per runtime, parallel to (and consuming the
same input stream as) the
[agent runtime status](/roadmap/agent-runtime-status/) and
[tag protocol](/roadmap/agent-tag-protocol/) parsers. Output
samples land in the
[persistent storage layer](/reference/research/agent-orchestration/memory/persistent-storage-layer/)'s
`usage_samples` table.

### Sample event [#sample-event]

```rust
pub struct UsageSample {
    pub at: SystemTime,                 // wall-clock at sample emission; SQL stores epoch seconds (occurred_at)
    pub session_id: Option<i64>,        // Capsule session emitting the sample, when known
    pub workspace: Option<String>,      // jackin❯ workspace name for attribution
    pub provider: Agent,                // canonical runtime enum from src/agent/mod.rs
    pub model: String,                  // must match a key in the pricing table; example: "claude-opus-4-7"
    pub token_input: u64,               // delta per assistant message (not cumulative)
    pub token_output: u64,
    pub token_cache_read: u64,
    pub token_cache_write: u64,
    pub cost_usd_micros: u64,           // computed from (provider, model, tokens) at sample time; never recomputed
    pub cost_source: CostSource,        // ExplicitUsd | PriceTable | Unpriced
}

pub enum CostSource { ExplicitUsd, PriceTable, Unpriced }
```

Per-instance DB rows mirror the value fields; the SQL row adds a surrogate `id INTEGER PRIMARY KEY`, stores `at` as `occurred_at INTEGER` (epoch seconds), carries `cost_source TEXT`, and may carry a `source_hash` dedupe key for log scanners or runtime streams. The hash is derived from source identity and event bytes so refreshes can be idempotent without storing prompts, completions, or raw log text. `instance` is implicit in the DB file path and is not stored per-row.

`cost_usd_micros` is integer to avoid floating-point summation drift across thousands of samples; renderers divide by 1e6 for display. `cost_source` is load-bearing provenance: provider-reported USD fields are `ExplicitUsd`, verified model price tables are `PriceTable`, and unknown/Amp pass-through rows without explicit USD stay `Unpriced` instead of receiving a synthetic zero. The `delta per assistant message` rule matters: Claude stream-json emits cumulative chunks, Codex app-server emits per-event deltas — adapters convert to deltas before emitting `UsageSample`, so SQL aggregates are simple sums.

`model: String` is loose by intent in V1: the pricing table is the closed set, and an unknown model is logged with no `cost_usd_micros` plus `cost_source = Unpriced` rather than rejected. Once the pricing table stabilizes, the field should tighten to a `Model` newtype whose constructor normalizes against the pricing table and rejects unknown variants — that is the right encapsulation but is a later tightening, not a V1 blocker.

### Account quota snapshots [#account-quota-snapshots]

The snapshot splits into three concerns — identity, quota numbers, cache metadata — so the type prevents a renderer from showing quota without acknowledging freshness:

```rust
pub struct AccountUsageSnapshot {
    pub provider: Agent,                  // canonical runtime enum
    pub account_label: String,            // operator-facing, never a raw token
    pub quota: AccountQuota,
    pub cache: SnapshotCacheMeta,
}

pub struct AccountQuota {
    pub window: UsageWindow,              // Session, Daily, Weekly, Monthly, Credits
    pub usage: Option<QuotaUsage>,        // None when status is NeedsLogin / Unsupported / Error
    pub resets_at: Option<SystemTime>,    // next provider reset boundary; orthogonal to usage
}

pub struct QuotaUsage {
    pub used: UsageAmount,                // current consumption; always present when QuotaUsage is Some
    pub limit: Option<UsageAmount>,       // cap; None for providers that report used-only (e.g. credits remaining)
}

pub struct SnapshotCacheMeta {
    pub fetched_at: SystemTime,           // when the daemon read this snapshot
    pub expires_at: Option<SystemTime>,   // when the daemon must refresh; None if provider doesn't say
    pub source: UsageSource,
    pub confidence: UsageConfidence,
    pub status: UsageSnapshotStatus,
    pub last_error: Option<UsageRefreshError>,
}
```

Per-message samples answer "what did this agent session cost?" Account snapshots answer "can I safely start more work on this account before the next reset?" The two models stay separate so renderers can mix them without confusing readers about provenance.

`confidence` answers "how much do we trust this number?" — provenance. `status` answers "is this snapshot useful right now?" — lifecycle. The two axes are intentionally separate; a snapshot can be `provider_authoritative` + `stale` (we have a trusted number that's past TTL) or `local_estimate` + `fresh` (best-effort number we just computed). The split into `quota` (the numbers) and `cache` (the metadata) means a renderer cannot present `quota` without `cache` in scope, so the source / confidence / status / freshness labels are always reachable from the same struct.

Supporting types:

```rust
pub enum UsageSource { RuntimeApi, LocalLog, CliAuth, BrowserCookie, Manual }
pub enum UsageConfidence { ProviderAuthoritative, LocalEstimate, Fallback }
pub enum UsageWindow { Session, Daily, Weekly, Monthly, Credits }
pub enum UsageSnapshotStatus { Fresh, Stale, NeedsLogin, Unsupported, Error }

pub struct UsageAmount {
    pub amount: u64,
    pub unit: &'static str,             // "tokens" | "messages" | "requests" | "usd_micros"
}

pub struct UsageRefreshError {
    pub kind: RefreshErrorKind,         // Network | Auth | RateLimited | Schema | Other
    pub at: SystemTime,                 // attempt timestamp
    pub message: String,                // operator-facing diagnostic, never contains secrets
}
```

Constructors so adapters cannot assemble inconsistent `(source, confidence, status)` triples:

```rust
impl AccountUsageSnapshot {
    pub fn from_provider_api(provider: Agent, label: String, quota: AccountQuota, fetched_at: SystemTime, expires_at: Option<SystemTime>) -> Self;  // (RuntimeApi, ProviderAuthoritative, Fresh)
    pub fn from_local_log(provider: Agent, label: String, quota: AccountQuota, fetched_at: SystemTime) -> Self;                                     // (LocalLog,   LocalEstimate,        Fresh)
    pub fn from_browser_cookie(provider: Agent, label: String, quota: AccountQuota, fetched_at: SystemTime) -> Self;                                // (BrowserCookie, Fallback,           Fresh)
    pub fn unsupported(provider: Agent, label: String) -> Self;                                                                                     // (Manual, Fallback, Unsupported); quota.usage = None
    pub fn needs_login(provider: Agent, label: String, source: UsageSource) -> Self;                                                                // (source, mapped-confidence, NeedsLogin); quota.usage = None
    pub fn errored(prior: &Self, error: UsageRefreshError) -> Self;                                                                                 // preserves prior quota; status = Stale, last_error = Some(error)
}
```

Constructor callers cannot stamp `(BrowserCookie, ProviderAuthoritative, Fresh)` or other illegal triples; field-by-field assembly stays available only inside the `usage::snapshot` module that owns the writer.

Cross-field invariants the daemon writer must uphold (none are expressible in the type system; failing them is a bug):

* `quota.usage.unwrap().used.unit == quota.usage.unwrap().limit.unwrap().unit` whenever `limit` is `Some` (renderers compare amounts on the same scale).
* `cache.last_error.is_some() ⇔ cache.status ∈ { Error, Stale }` (and only when the stale state came from a failed refresh).
* `(source, confidence)` defaults to `(RuntimeApi → ProviderAuthoritative)`, `(LocalLog | CliAuth → LocalEstimate)`, `(BrowserCookie | Manual → Fallback)`; adapters override only when a runtime\_api response is demonstrably stale.

SQL flattens this nested shape: `UsageAmount` becomes `<field>_amount INTEGER` + `<field>_unit TEXT` column pairs; `window: UsageWindow` serializes as `window_kind TEXT`; `UsageRefreshError` serializes to `last_error TEXT` as JSON; the `quota` / `cache` split is conceptual only (one row holds both halves; see the [daemon-global account cache](/reference/research/agent-orchestration/memory/persistent-storage-layer/#daemon-global-account-cache) schema). `account_key_hash` is a write-time hash of the provider-stable account identity (OAuth `sub`, provider user id, or salted hash of the canonical login email — **not** the editable `account_label` display string); it lives in the SQL row but not in the value struct, so consumers cannot stamp inconsistent hashes and a label rename does not produce a duplicate snapshot row.

### Source priority by runtime [#source-priority-by-runtime]

V1 should treat source selection as a deterministic matrix, not as a best-effort scrape. Each adapter returns both a normalized snapshot and a `UsageSource` / `confidence` pair so renderers can explain why a number should or should not be trusted.

| Runtime    | Account availability priority                                                                                                                                                                                                                                                                                                                                                                  | Workspace spend priority                                                                                                              | V1 confidence call                                                                                                                                       |
| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `codex`    | `codex app-server` JSON-RPC (`account/read`, `account/rateLimits/read`) or OAuth-backed usage endpoint when daemon-owned credentials are available; optional web dashboard only for explicit extras                                                                                                                                                                                            | `CODEX_HOME` / `~/.codex` session JSONL, including archived sessions, plus runtime events when jackin❯ launches Codex                 | Strong V1 candidate because a non-interactive CLI RPC source exists and local logs are structured enough for attribution                                 |
| `claude`   | The console usage endpoint Tokemon reverse-engineered (no Anthropic-published public quota API exists at the time of writing — endpoint is undocumented and may shift; jackin❯ treats it like a scrape, throttles, and degrades to local-estimate when it 4xx/5xxs); Claude CLI `/usage` probe only as a repair/diagnostic fallback; cookie-backed web API only after explicit operator opt-in | Claude JSONL project logs and stream-json usage payloads, deduped by message/request identity where streaming chunks are cumulative   | Strong V1 candidate for **workspace spend**; account-availability confidence drops to `local_estimate` whenever the undocumented endpoint is unreachable |
| `amp`      | One-shot `amp --no-color usage` under daemon timeout, with explicit browser-cookie settings scrape fallback only if the operator opts in outside Capsule                                                                                                                                                                                                                                       | Amp thread/API usage events when available from the launched runtime; local transcript fallback only if structured token counts exist | Capsule bridge can show Amp Free and individual credits from the CLI; the persistent account cache still needs scheduled refresh/backoff                 |
| `kimi`     | Kimi For Coding billing endpoint from `KIMI_AUTH_TOKEN` / local Kimi Code OAuth token; cookie/JWT web state can be a fallback only if explicitly enabled                                                                                                                                                                                                                                       | Kimi local logs or runtime hooks if they expose per-turn token usage                                                                  | Capsule bridge can show weekly and rate-limit buckets; the persistent account cache still needs scheduled refresh/backoff                                |
| `opencode` | Provider-specific web dashboard or configured provider account state, not a generic OpenCode account guess                                                                                                                                                                                                                                                                                     | OpenCode message history, following multicode's local aggregation approach where compatible                                           | Workspace spend can arrive before quota; account availability may be `unsupported` per backing provider                                                  |

### Host-owned telemetry cache [#host-owned-telemetry-cache]

The jackin❯ daemon owns a host-side telemetry cache so every container and UI surface reads the same account state instead of each role container polling providers independently. The cache is a small persistent store under the daemon's data directory and is keyed by account identity, provider, source, window, and workspace/session attribution where applicable.

Rules:

* The daemon is the only component that refreshes provider quota snapshots by default. Containers can request a refresh through the daemon, but they do not hold independent polling loops.
* Each snapshot carries `fetched_at`, `expires_at`, `source`, `confidence`, and `last_error`. UI surfaces must show stale data as stale rather than silently hiding it.
* Containers receive read-only access through the Capsule/daemon control channel, not by bind-mounting writable host cache directories into every container. A narrow read-only materialized file at `/jackin/run/usage/accounts.json` is acceptable for agents that can only read files; the daemon writes the file with tmp+rename atomicity (write to `accounts.json.tmp` in the same directory, `fsync`, then `rename(2)` over `accounts.json`) so concurrent readers always see a complete document. The payload carries an embedded `fetched_at` epoch-seconds field per snapshot so readers do not depend on file `mtime`; a missing file means "no snapshots yet" and is distinct from a stale-but-present file.
* Cache TTL is provider-specific. Server-side quota endpoints get conservative refresh intervals and backoff on errors; local session samples can update on session events.
* The cache stores tokens, costs, percentages, reset times, source metadata, and opaque account labels. It never stores prompts, completions, raw conversation text, provider cookies, or bearer tokens.
* If an account requires authentication, the daemon requests it through existing jackin❯ auth mechanisms: forwarded CLI auth, 1Password references, macOS Keychain integration, or an explicit host-bridge approval. Failed auth is a first-class `needs_login` / `needs_secret` state.

### Host-side effects [#host-side-effects]

The host-side write this roadmap authorizes is the daemon telemetry cache itself, stored under jackin❯-owned host state and surfaced through the launch/daemon summary before the feature ships. Telemetry refresh may read existing host-side auth/config sources when the operator has enabled that source, but it must not write provider config, Keychain entries, browser profiles, Git config, shell dotfiles, or any user repository as part of normal refresh.

Any repair action that would change host state, such as connecting a provider account, adding a Keychain item, or enabling a browser-cookie fallback, must be an explicit operator action rather than a background refresh side effect. Containers only receive read-only views through the daemon/Capsule channel or daemon-materialized files under `/jackin/run/usage/`; they do not own or mutate the host cache.

### Storage shape [#storage-shape]

Per-message samples land in the per-instance `usage_samples` table; account snapshots land in the daemon-global `account_usage_snapshots` table. Both schemas live in the [persistent storage layer](/reference/research/agent-orchestration/memory/persistent-storage-layer/) — see [usage samples](/reference/research/agent-orchestration/memory/persistent-storage-layer/#tables-v1) for the per-instance table and [daemon-global account cache](/reference/research/agent-orchestration/memory/persistent-storage-layer/#daemon-global-account-cache) for the snapshot table. `account_key_hash` is a stable hash of the provider/account identity so raw email/token values never become primary keys; operator-facing labels stay display-only.

### Daemon API contract [#daemon-api-contract]

Daemon-owned account usage endpoints land before any UI polish. The naming follows the existing daemon convention (`daemon/hello`, `session/list`, `event/subscribe`) — slash-namespaced, two segments:

* `account/list`: returns every known account snapshot with freshness, confidence, and active workspace/session references.
* `account/refresh { provider?, account? }`: schedules a refresh and returns the last cached snapshot immediately; does not block the UI on provider network calls.
* `usage/workspace { workspace, window }`: returns local token/cost attribution from per-instance samples.
* `usage/session { instance, session_id, window }`: returns the per-session token/cost summary when the session is still known.
* `usage/focused`: active account quota plus session/workspace spend for the currently focused Capsule pane.

These can live in the daemon protocol first. Capsule should consume a narrow read path later: focused provider/account summary in, no provider polling out. If a file bridge is needed for agents that can only read files, the daemon writes a read-only materialized JSON file under `/jackin/run/usage/` inside the container; containers never write back to the host telemetry cache.

Capsule-local implementation note: the `jackin-capsule` control channel now has
`UsageFocused` / `UsageRefreshFocused` and `UsageAccountList` message variants.
These read the Capsule daemon cache and `/jackin/state/usage/telemetry.db`.
The Capsule daemon persists account quota buckets only. It no longer persists
local-log samples, token-bearing runtime-stream JSON samples, workspace
summaries, session summaries, or invented price-table estimates. These APIs are
not yet the host-global daemon API or final `account/list` / `account/refresh`
/ `usage/*` CLI surface.

### Implementation sequence [#implementation-sequence]

1. **Storage substrate.** Land the per-instance `usage_samples` table and daemon-global `account_usage_snapshots` cache with schema tests and no UI.
2. **Codex account adapter.** Use `codex app-server` / OAuth-backed reads for account availability, bounded with timeouts and launch-failure backoff. Add local Codex JSONL scanner for workspace spend.
3. **Claude account adapter.** Use OAuth/provider usage first and local JSONL/stream-json for spend. CLI PTY probing is user-initiated repair or diagnostics, not a background loop.
4. **Shared daemon endpoints.** Expose account list, refresh, workspace summary, and session summary; include source, confidence, freshness, and last-error fields in every response.
5. **CLI.** Ship `jackin usage --json` and a compact human view before the Desktop/Capsule work so the data contract can be tested without a native UI.
6. **Console and Desktop surfaces.** Add compact badges, then account/detail panels. Badges must carry both axes — `UsageConfidence` for provenance and `UsageSnapshotStatus` for lifecycle — before shipping percentage-only displays.
7. **Capsule focused signal.** The Capsule/control/TUI bridge has landed: Capsule can request a normalized focused usage view, render a compact focused-provider status label, and open the read-only prefix `u` overlay from a daemon-owned account snapshot cache. Provider-authoritative quota adapters and scheduled refresh/backoff for the in-container overlay have shipped. The remaining work is to replace the Capsule-local account cache with the final host-global account snapshot service described above. Rendering contract lives in [Capsule usage overlay](/reference/capsule/).
8. **Amp/Kimi/OpenCode research adapters.** Add provider rows only after each runtime has a non-interactive source contract or an explicit fallback UX. Until then, render honest unsupported/local-estimate states.

### Surface contract [#surface-contract]

Every interactive jackin❯ surface that can launch or focus an agent exposes the active account's quota state inline, with details one action away. The contract:

* **Compact by default:** provider plus remaining percent (`Claude 80%`, `Codex 18%`) or state (`Amp login`, `stale`); use a glyph/ring when text crowds the surface. Prefer "remaining" over "used" language — `Claude 18% left` is safer than `Claude 82%` when the operator is moving quickly.
* **Two rows, labeled:** when both numbers exist, render **Account availability** (provider-authoritative) and **Workspace spend** (local attribution) separately. Never combine into one gauge.
* **Confidence and status visible:** every rendered number carries both labels — `UsageConfidence` for provenance, `UsageSnapshotStatus` for lifecycle (variants declared once in the [snapshot type definition](#account-quota-snapshots) above). `Kimi unsupported` beats a missing badge. A `provider_authoritative` + `stale` snapshot is not healthier than a `local_estimate` + `fresh` one; render both axes.
* **Color plus text:** pair warning colors with glyphs or labels because the Capsule and console may run in low-color terminals.
* **Focused-agent aware:** when the operator is attached to one agent, the visible signal follows that agent's account, not a global average.
* **Repair stays explicit:** separate "read-only auth source missing" from "jackin❯ wants to write host state". The latter requires explicit operator approval under the [host-side effects](#host-side-effects) rule.

Surfaces consuming the cache:

* **Desktop app.** Accounts view is the full inspection surface: every account, provider, source, current availability, reset time, confidence, last refresh, active workspaces, repair actions. Running Agents view shows focused-account quota and workspace/session spend inline.
* **macOS status bar.** Most-constrained active account by default — see the [most-constrained ordering](#most-constrained-ordering) rule below. Provider glyph plus remaining percent or `stale` / `login` state. Menu expands to all accounts with reset timers, source labels, and repair actions.
* **`jackin console` Accounts / Usage.** Lists every known account with source, current availability, reset time, last refresh, and which workspaces currently use it. Workspace and running-agent rows carry compact badges for the attached account.
* **Capsule multiplexer.** Focused-tab provider only — see [Capsule usage overlay](/reference/capsule/) for the rendering contract (status-line layout, prefix `u`, modal contents, daemon-disconnected and needs-login fallbacks).
* **CLI / automation.** `jackin usage` exposes account, workspace, and session views with `--json`, including freshness and provenance so scripts do not mistake local estimates for provider-enforced limits.

The contract must answer, for the focused agent: which built-in-runtime accounts jackin❯ knows about, which one this agent is using, how much quota remains, when it resets, how much this workspace/session burned today/this week/lifetime, and which confidence + status pair the displayed limit carries.

#### Most-constrained ordering [#most-constrained-ordering]

Compare accounts across providers in this fixed priority chain (first hit wins):

1. `status == NeedsLogin` or `Error` — show this first regardless of any percentage; one broken account is the highest-risk surface state.
2. `status == Stale` AND age > 2× provider TTL — the snapshot is too old to trust.
3. `status == Fresh` AND `used.is_some()` AND `limit.is_some()` AND `used.unit == limit.unit` — rank by **remaining percentage** = `1 - used.amount / limit.amount`. Lowest remaining wins.
4. `status == Fresh` AND `resets_at.is_some()` AND no usable percentage — rank by &#x2A;*earliest `resets_at`**. Soonest reset wins.
5. Tie-break: provider alphabetical (`Agent::slug()`).

The status bar never compares percentages across different `UsageAmount.unit` values (a tokens-remaining percentage is not the same scale as a messages-remaining percentage); same-unit percentage comparison is the only legitimate use of step 3.

### Adapter shape [#adapter-shape]

Each adapter consumes the runtime's structured output (same input stream as the [agent runtime status](/roadmap/agent-runtime-status/) and [tag protocol](/roadmap/agent-tag-protocol/) parsers), resolves source priorities from the matrix above, and emits `UsageSample` for per-message attribution plus `AccountUsageSnapshot` for account quota. The adapter knows its runtime; the consumer doesn't.

Hidden constraint not captured in the matrix:

* **Codex:** keep bare TUI `/status` probing out of background refresh; it can trigger interactive auth and break the session.

### Pricing table [#pricing-table]

```toml
# ~/.config/jackin/pricing.toml (or section of operator config)

[pricing.models."claude-opus-4-7"]
input_per_million = 15.00
output_per_million = 75.00
cache_read_per_million = 1.50
cache_write_per_million = 18.75

[pricing.models."gpt-5-codex"]
input_per_million = 5.00
output_per_million = 15.00
```

Operator-overridable; jackin❯ ships a default table for current models.
Stale prices are a documentation issue, not a runtime issue — the
table is just a multiplier.

When a sample arrives for a model not in the table, jackin❯ records
the tokens, leaves cost as null, and prints a one-line warning at next
console open ("no pricing for model X — tokens tracked but cost unknown").

### Console rendering [#console-rendering]

The per-agent table (when [console resource
panel](/roadmap/console-resource-panel/) is open) gains a
"Cost (today)" column showing the running cost since 00:00 local time.
A second `Tokens (1h)` column may surface the rolling-1-hour rate;
defer if cell width gets tight.

A `jackin usage <selector>` CLI command dumps a structured summary
for scripting:

```sh
$ jackin usage the-architect --json
{
  "instance": "jackin-the-architect",
  "today_cost_usd": 4.12,
  "today_tokens": { "input": 124000, "output": 38500, ... },
  "lifetime_cost_usd": 67.40
}
```

## Scope (V1) [#scope-v1]

* Per-runtime usage adapter consuming the runtime's structured output stream.
* Per-provider account snapshot adapter for the built-in jackin❯ runtimes, starting with Claude and Codex where local sources are most mature.
* `UsageSample` events appended to the storage layer's `usage_samples` table.
* `AccountUsageSnapshot` events appended to storage and exposed through the daemon for the Desktop Agent Hub, `jackin console`, Capsule multiplexer, and CLI.
* Host-owned telemetry cache with TTL, stale/error states, and provider-specific refresh backoff.
* Pricing table with operator override; ship sensible defaults.
* Console "Cost (today)" column when the resource panel is open.
* Compact quota badges on Desktop running-agent rows, `jackin console` workspace/agent rows, and macOS status item.
* Console / Desktop account panel showing all accounts, quota windows, reset time, workspace attribution, and stale/login-needed state.
* Capsule multiplexer quota signal — Capsule's rendering contract lives in [Capsule usage overlay](/reference/capsule/).
* `jackin usage <selector>` CLI with `--json` and human-readable
  formatting.
* Cost computed at sample time and stored, not recomputed on render —
  preserves history when prices change.

## Defer [#defer]

* Budget gating ("kill the agent or refuse queue dispatch when daily
  cost exceeds $X"). Useful but waits — it requires the queue to exist
  first.
* Multi-currency. USD only in V1; the internal representation is
  micros-of-USD, conversion is a render-time concern.
* Org-level aggregation across operators. Out of scope.
* Generic provider marketplace / third-party plugin API. Useful later, but first-class support should stay scoped to the built-in jackin❯ runtimes.
* Browser-cookie import as the primary source for supported runtime accounts. Keep it explicit and fallback-only.
* Token-savings reporting from prompt caching. Tracked as a column;
  not separately summarized in V1.
* Per-tool-call cost attribution (which tool call cost the most).
  Defer; needs richer event extraction than V1's per-message sampling.

## Open Questions [#open-questions]

* **What's the canonical input for the Claude usage adapter?** Claude
  Code's stream-json transcript exposes tokens per message; the
  question is whether jackin❯ attaches as a sidecar consumer or
  intercepts the runtime's stdout. The latter risks interfering with
  the operator's own view of the agent. Recommended default: *stream-
  json sidecar*, gated behind the same opt-in as the status adapter.
* **Default pricing table source.** Hand-maintained in this repo, or
  fetched from an Anthropic/OpenAI-published source at build time?
  Recommended: *hand-maintained for V1*; revisit if it stales out fast.
* **Daily cost reset boundary.** Local midnight, UTC midnight, or
  rolling 24-hour? Recommended: *local midnight* for the "today"
  column; CLI exposes any window.

## Related Files [#related-files]

* New module (e.g. `src/runtime/usage.rs`) — adapter
  trait + sampler
* The persistent-storage module — owns `usage_samples` schema
* <RepoFile path="crates/jackin/src/cli/role.rs">crates/jackin/src/cli/role.rs</RepoFile> — `jackin usage` subcommand
* <RepoFile path="crates/jackin/src/console/tui.rs">crates/jackin/src/console/tui.rs</RepoFile> — column rendering
* New config block — `[pricing.models]`

## See Also [#see-also]

* [Agent Orchestration Program](/reference/research/agent-orchestration/program-research/)
* [Persistent storage layer](/reference/research/agent-orchestration/memory/persistent-storage-layer/) —
  required (sample storage)
* [Agent runtime status](/roadmap/agent-runtime-status/) —
  parallel consumer of the same input stream
* [Console resource panel](/roadmap/console-resource-panel/) — rendering home
* [Capsule usage overlay](/reference/capsule/) — Capsule TUI rendering contract
* [jackin❯ Desktop Agent Hub](/reference/research/desktop/agent-hub/jackin-desktop-agent-hub/) — desktop account/quota rendering home
* [Autonomous task queue](/roadmap/autonomous-task-queue/) — future budget-gating consumer
