You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Spec #201. Follow-up from the #204 recon spike, whose verdict was build — see the evidence comment on #201. Deliberately notready-for-agent: the three decisions in "Open calls" below are human scoping,
and #201 states the passive header side-channel stays the source of truth for this release, so this lands
after 2.0.43.
What to build
An active quota lane: read the account usage endpoint on a cadence, cache the reading, and let the
statusline show real numbers before the session's first turn — the hole the passive header
side-channel cannot fill, since headers only arrive on a response.
Both endpoints are verified to answer Wisp's stored OAuth credentials with no extra scope or consent:
Anthropic — GET https://api.anthropic.com/api/oauth/usage, Authorization: Bearer <stored token>, anthropic-beta: oauth-2025-04-20
(/backend-api/api/codex/usage is a 404 — do not carry it forward.)
Both agree with the header-derived meters exactly, so this is a second door onto the same ledger, not a
competing truth.
Hard constraints (from the recon — do not rediscover these)
The Anthropic endpoint carries a multi-minute 429 penalty. Never call it on the statusline render
path; the statusline runs constantly. Cached, low-frequency poll only, renderer reads the cache.
Parse limits[] on shape, never the named top-level keys. The payload carries seven_day_opus, seven_day_sonnet, seven_day_cowork, seven_day_oauth_apps plus unstable codenames (tangelo, iguana_necktie, nimbus_quill, omelette_promotional, cinder_cove, amber_ladder), nearly all null. status.ts already matches on shape for this exact reason.
Normalize units at the boundary. This endpoint reports an integer percent and ISO-8601 resets_at;
the header path reports a 0..1 fraction, and status.json stores epoch seconds — which Statusline: expired quota window renders refilled, not alarmed #203's expired() predicate depends on.
Redact on values. Codex returns email, user_id, account_id; Anthropic returns an org uuid and a
workspace id in response headers. None of it may reach status.json, bridge.log, or the repo.
Codex secondary_window is null on a Plus plan — consistent with the settled one-window rule; do not
render an absent window as 0%.
What it unlocks beyond the current two numbers
Model-scoped weekly windows (scope.model.display_name), which window is actually binding (is_active), severity, extra-usage/credit state, spend caps, plan_type, and Codex's authoritative limit_reached / rate_limit_reached_type.
Open calls (human, before this is agent-ready)
Cadence — how often, and is it wall-clock or event-driven (session start, post-turn, on stale cache)?
Precedence — when a poll and a turn header disagree, which wins? They agreed exactly in the recon, so
a "freshest wins" rule is probably enough, but it is a decision, not a default.
Opt-in or always-on — this is an extra outbound call against the user's account on their credential.
Acceptance criteria
Cadence, precedence, and opt-in decided and written into the ticket before implementation starts.
Poll is cached and never fires from the statusline render path.
limits[] parsed on shape; no meter invented from a null named bucket.
Units normalized to the existing QuotaMeter contract (integer percent, epoch-second resetAt).
No account-identifying value reaches status.json, bridge.log, or the repo.
A statusline opened before the first turn of a session shows real meters.
Blocked by
None mechanically — blocked on the three open calls above and on 2.0.43 shipping.
Parent
Spec #201. Follow-up from the #204 recon spike, whose verdict was build — see the evidence comment on
#201. Deliberately not
ready-for-agent: the three decisions in "Open calls" below are human scoping,and #201 states the passive header side-channel stays the source of truth for this release, so this lands
after 2.0.43.
What to build
An active quota lane: read the account usage endpoint on a cadence, cache the reading, and let the
statusline show real numbers before the session's first turn — the hole the passive header
side-channel cannot fill, since headers only arrive on a response.
Both endpoints are verified to answer Wisp's stored OAuth credentials with no extra scope or consent:
GET https://api.anthropic.com/api/oauth/usage,Authorization: Bearer <stored token>,anthropic-beta: oauth-2025-04-20GET https://chatgpt.com/backend-api/wham/usage,Authorization: Bearer <stored token>,chatgpt-account-id: <stored account id>(
/backend-api/api/codex/usageis a 404 — do not carry it forward.)Both agree with the header-derived meters exactly, so this is a second door onto the same ledger, not a
competing truth.
Hard constraints (from the recon — do not rediscover these)
path; the statusline runs constantly. Cached, low-frequency poll only, renderer reads the cache.
limits[]on shape, never the named top-level keys. The payload carriesseven_day_opus,seven_day_sonnet,seven_day_cowork,seven_day_oauth_appsplus unstable codenames (tangelo,iguana_necktie,nimbus_quill,omelette_promotional,cinder_cove,amber_ladder), nearly allnull.status.tsalready matches on shape for this exact reason.resets_at;the header path reports a 0..1 fraction, and
status.jsonstores epoch seconds — which Statusline: expired quota window renders refilled, not alarmed #203'sexpired()predicate depends on.email,user_id,account_id; Anthropic returns an org uuid and aworkspace id in response headers. None of it may reach
status.json,bridge.log, or the repo.secondary_windowisnullon a Plus plan — consistent with the settled one-window rule; do notrender an absent window as 0%.
What it unlocks beyond the current two numbers
Model-scoped weekly windows (
scope.model.display_name), which window is actually binding (is_active),severity, extra-usage/credit state, spend caps,plan_type, and Codex's authoritativelimit_reached/rate_limit_reached_type.Open calls (human, before this is agent-ready)
a "freshest wins" rule is probably enough, but it is a decision, not a default.
Acceptance criteria
limits[]parsed on shape; no meter invented from anullnamed bucket.QuotaMetercontract (integer percent, epoch-secondresetAt).status.json,bridge.log, or the repo.Blocked by
None mechanically — blocked on the three open calls above and on 2.0.43 shipping.