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:
Metal Agent
2026-08-31 21:47:11 -07:00
co-authored by Claude Opus 5
parent 7fe84f71e2
commit ef52aa7ce2
34 changed files with 7319 additions and 137 deletions
@@ -0,0 +1,97 @@
# 0015: Claude Code's subscription windows come from the status line, not the credential
Status: accepted; bridge installed and verified end to end.
Decision 0014 concluded that Claude Code cannot report a remaining balance.
That was wrong, and the error was one of not looking rather than of reasoning:
the transcript genuinely carries no quota, and that fact was generalised into a
claim about the harness. Claude Code reports its quota through three other
channels.
## The four surfaces
| Surface | Carries | Cost | Needs a credential |
|---|---|---|---|
| **Status line `rate_limits`** | `five_hour`, `seven_day``used_percentage` + `resets_at` | none; relayed from headers the CLI already received | no |
| Account usage endpoint | the same windows, plus per-model scoped limits | tight budget; 429s under polling | **yes** |
| Stream `rate_limit_info` | `status`, `rateLimitType`, `unifiedWindows` | none | no, but requires driving the harness |
| `/usage` in a PTY | rendered text | a turn | no, but screen-scraped |
The status-line shape is documented inside the CLI itself:
```text
"five_hour": { // present only while the API reports it and its resets_at has not passed
"used_percentage": number, // Percentage of limit used (0-100)
"resets_at": number // Unix epoch seconds when this window resets
}
```
`rate_limits_available` is the honest-gap signal, also documented: "False when
plan rate limits do not apply (API key, Bedrock, Vertex, or missing profile
scope)". An account with no subscription window says so, rather than leaving
Lumbridge to infer it from an absence.
## Why the status line and not the endpoint
The account usage endpoint is more authoritative — it answers without an active
session and exposes per-model scoped limits. It also requires reading Claude
Code's OAuth credential, and AGENTS.md says Lumbridge "must not scrape their
private credentials." Other tools in this space do read it; that is their call
to make, and it is a real capability difference.
Lumbridge keeps the rule. The status line is pushed *to* a command the user
installs, so no credential is ever touched, and the data is free because the
CLI is relaying rate-limit headers it already received on its own API calls.
The cost is that a window only appears once a session has run, and that
installation edits `settings.json`.
The endpoint is recorded here as a rejected-for-now alternative rather than an
unconsidered one. Reopening it means amending AGENTS.md first.
## Provenance
`ProviderReported`. Under decision 0013's test the CLI forwards the provider's
number rather than computing one — the same standing as Codex's forwarded
`x-codex-*` headers. The footer shows `provider · exact`.
## The bridge
`scripts/claude-statusline-bridge.sh` is installed as Claude Code's
`statusLine` command. It appends the rate-limit fields — and only those — to
`$XDG_DATA_HOME/lumbridge/claude-rate-limits.jsonl`, and prints a status line.
Three properties it must hold:
- **It cannot fail.** A status-line command that errors or hangs degrades the
user's session, so every step is guarded and it always exits 0 having
printed something.
- **It copies nothing else.** The payload also carries the session's cost,
transcript path, working directory, and model. The bridge builds a fresh
record from three numeric fields per window rather than filtering the
original, so there is no path by which the rest travels.
- **It stays bounded.** The feed rotates past 256 KB. Lines are appended rather
than overwritten so concurrent sessions do not clobber each other; the probe
reads the newest line, because each line is a complete snapshot and an older
one is superseded, never summed.
Installation is a separate, explicit, reversible script — not something a probe
does on the user's behalf. It backs up `settings.json`, writes through a
temporary file, refuses to replace a `statusLine` someone else configured, and
supports `--dry-run` and `--uninstall`.
## Window handling
Each window becomes its own account-free profile — `claude-code-five-hour` and
`claude-code-seven-day` — because two windows are two facts about two quota
periods, and merging them means silently picking one. The pane maps to the
five-hour window: it is the limit that actually stops work.
A reading's window start is derived from `resets_at` minus the documented
window length, which is what lets the ledger scope a burn rate to one quota
period. Two rules follow decision 0012 unchanged: a window whose `resets_at`
has passed is stale, so its reset is dropped and only the percentage survives;
and a start the local clock places in the future costs the start, not the
reset, which is the half that bounds a forecast.
A percentage above 100 — possible on a gateway spend limit once breached — is
clamped to the whole rather than stored as more than a full window.