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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
7fe84f71e2
commit
ef52aa7ce2
@@ -0,0 +1,103 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user