Files
lumbridge-code/docs/decisions/0014-claude-code-transcript-usage.md
Metal AgentandClaude Opus 5 ef52aa7ce2 Replace the footer's placeholder usage with a real observation ledger
The footer showed invented percentages. It now shows what two harnesses
actually report, or says it does not know.

lumbridge-core gains an append-only per-profile UsageLedger and a projection
that labels every derived value estimated, withholds a burn rate from a single
sample, withholds a window fraction with no reported ceiling, withholds an
exhaustion estimate that lands after the reset, and reports an expired window
as rolled over rather than freezing its last percentage. A missing fact renders
as missing, never as zero. (0012)

lumbridge-harness is the impure side: processes, clocks, and untrusted wire
text in, observations out. Three adapters:

- Codex's account/rateLimits/read over the app-server's JSON-RPC stdio. The
  client cannot express a request outside a two-variant enum and answers every
  server-to-client request with -32601, so a harness asking Lumbridge for a
  credential is refused by construction. (0013)
- Claude Code's session transcripts, as a byte-offset tail follower that
  reports nothing until the backlog is read to EOF — a partially-read backlog
  is indistinguishable from a burst of spend, and the first run against 20 MB
  reported forty-six billion tokens an hour. The parser models four counters,
  so the conversations in those files are not representable. (0014)
- Claude Code's five-hour and seven-day subscription windows, via a bridge
  installed as its statusLine command. 0014 had claimed no such surface
  existed; it does, and the record is corrected in place rather than quietly
  edited. Lumbridge does not read the OAuth credential to call the account
  usage endpoint, which is what comparable tools do — AGENTS.md forbids it,
  and 0015 says so rather than leaving the gap unexplained.

Also in here: a capability-check ordering fix in the workspace reducer, where
the applied-request replay table was consulted before the capability check and
so answered questions the caller had no right to ask; the GPUI spike wired to
the live probes with per-harness gauges and provenance chips; and a launcher
that matches its own window by PID, because GPUI sets WM_NAME but not
_NET_WM_NAME and a title match never succeeded.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 21:47:11 -07:00

104 lines
5.2 KiB
Markdown

# 0014: Claude Code usage comes from its transcripts, and reports spend without a ceiling
Status: accepted for token spend. **Corrected by decision 0015**: this record
originally claimed Claude Code exposes no quota window. That was wrong — it
does, through the status line — and the claim is struck below rather than
quietly edited out, because the reasoning that followed from it shaped the
adapter.
Decision 0013 established the provenance test for a usage adapter: does the
harness forward a provider's number, or record its own? Claude Code is the case
that makes the second half of that test matter.
## Correction
The original text asserted that "Claude Code publishes no quota API and no
remaining-balance command" and that a user asking how much they have left
"gets the honest answer — Lumbridge can show what was spent, and cannot show
what remains, because Claude Code does not say."
**Both statements are false.** Claude Code 2.1.80 and later pipe a
`rate_limits` object with `five_hour` and `seven_day` windows to the configured
`statusLine` command on every turn, and the CLI documents that shape itself.
There is also an account usage endpoint. The correct claim is narrower and is
the only one this record still makes: *the transcript* carries spend and not
quota. Decision 0015 covers the window surface.
## The surface
What Claude Code writes to disk per session is the API's `usage` object for
every assistant turn,
appended to a per-session JSONL transcript under
`$CLAUDE_CONFIG_DIR/projects` (default `~/.claude/projects`). Each assistant
record carries `input_tokens`, `output_tokens`,
`cache_creation_input_tokens`, and `cache_read_input_tokens`, alongside the
model that produced it.
Summing those is how this adapter reports spend. **A subscription's window,
ceiling, and reset are not in the transcript**, so this profile reports
consumption against no ceiling rather than implying a quota — but that is a
fact about this file, not about Claude Code, and the quota is read separately
under decision 0015.
The `statusLine` hook does require mutating the user's `settings.json`, which
is why the transcript came first: reading a file the harness already writes
needs no change to the user's configuration. That was a good reason to build
this adapter, and a bad reason to conclude the other surface did not exist.
## Why harness-reported
Claude Code forwards the provider's per-response counts faithfully, so a single
record's numbers originate with the provider. But a running total is the
*harness's* record — an append-only log Lumbridge is reading, not a provider's
statement of account. Under 0013's rule the value is therefore
`HarnessReported`, and the footer says `harness · approximate`.
## Content blindness is structural, not procedural
**These files contain the user's conversations.** The parser models exactly
four integers, a record type, and a model name. Every other field — message
text, tool inputs, file contents, `service_tier`, `inference_geo`,
`server_tool_use` — is discarded by serde during parsing, because there is no
field in the wire types that could hold it. A test asserts that no debug
rendering of a parsed record can contain message content.
That is the same discipline as the Codex probe's refusal path: the safe
behaviour is made unrepresentable rather than merely avoided.
## What "tokens" counts
The reported number is `input + output + cache_creation + cache_read`.
Cache reads are included because they are billed and do count against a
subscription window, even though they are re-reads of context already counted
once. Excluding them would understate consumption. Weighting them by price
would require a price list this adapter does not have, and would turn a counted
fact into an estimate. In practice cache reads dominate the total — on the
transcripts this was verified against they were roughly 98% of it — so the
derived burn rate is largely a measure of context re-read per turn rather than
of new work. The split is preserved in `TokenTally` for a detail view that can
say so.
## Priming before reporting
The probe is a tail follower: it remembers a byte offset per transcript and
reads only what was appended since, so its running total is monotonic by
construction. The ledger rejects an observation that moves a profile backwards,
so this is a correctness requirement, not an optimisation.
It also **reports nothing until it has read every existing transcript to its
end**. A partially-read backlog is indistinguishable from an enormous burst of
spend, and the ledger will derive a burn rate from it: the first run against a
20 MB backlog with an 8 MB per-poll budget reported **forty-six billion tokens
an hour**, which was catch-up, not usage. The first reading must be a true
baseline before any rate derived from it can mean anything. A shrinking file
resets its own offset rather than trusting a position into different content.
## Bounds
A scan walks at most six directory levels and 4096 files, never follows
symlinks out of the tree, reads at most 8 MB per poll, and refuses a single
record over 1 MB while still advancing past it. A transcript's last line is
routinely a partial record still being written; it is left unconsumed for the
next read rather than parsed or skipped.