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,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.
|
||||
Reference in New Issue
Block a user