Skip to content

Surface the credit budget wham/usage already reports - #160

Merged
ualtinok merged 5 commits into
cortexkit:mainfrom
iceteaSA:feat/surface-spend-control
Sep 18, 2026
Merged

ualtinok merged 5 commits into
cortexkit:mainfrom
iceteaSA:feat/surface-spend-control

Conversation

@iceteaSA

@iceteaSA iceteaSA commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Closes #158.

wham/usage returns a credit budget with its own limit, usage and reset clock. normalizeWham read only rate_limit_reset_credits.available_count and dropped the rest, so nothing — sidebar, /openai-quota, quota manager — could see it.

Why it is a third axis, not a missing field

Live probe against a team-plan account:

"spend_control": {
  "reached": false,
  "individual_limit": {
    "source": "workspace_spend_controls", "unit": "credit",
    "limit": "2500", "used": "501.7787666320801", "remaining": "1998.22123336792",
    "used_percent": 20, "remaining_percent": 80,
    "reset_after_seconds": 1053442, "reset_at": 1790812800
  }
}

That account was at 0% on its 5-hour window and 51% on its 7-day window while 20% through a credit budget resetting a month out — a clock unrelated to either rate-limit window. An account can therefore be healthy on both bars we render and still be refused, which today surfaces as an unexplained failure on an account showing headroom.

What this adds

  • normalizeWham parses spend_control.individual_limit and the credits booleans into an optional reading on OAuthQuotaSnapshot, reusing the existing toResetIso coercion
  • the reading travels through QuotaManager and sidebar state on the same plumbing as primary/secondary
  • the TUI renders a third bar only when the reading is present
  • /openai-quota shows a credit-budget line when present, omits it entirely when absent

Parsing

Three things from the probe that a reasonable implementation would get wrong:

  • the numeric fields arrive as strings ("2500", "501.7787666320801"), not numbers
  • spend_control and individual_limit are independently nullable
  • credits.balance is null while has_credits is true — no balance does not mean no credits

Every field is parsed defensively and the whole reading stays optional.

The regression that mattered

Most accounts have no spend control, so the feature shows for a few while a rendering regression would hit everyone. That case is pinned by an exact-match assertion on the projected rows — primary and secondary only, plus an explicit assertion that no spend-control row exists — rather than by checking the value is undefined.

I verified it by mutation rather than taking it on trust: making the bar unconditional reddens three tests, including two pre-existing window-projection tests that have nothing to do with this feature.

(fail) dynamic quota TUI rows > one 7-day primary window produces one 7d row paced over seven days
(fail) dynamic quota TUI rows > two present windows produce two rows and zero windows produce none
(fail) dynamic quota TUI rows > renders the existing two-bar sidebar output without spend control
(fail) commands > quota command shows spend control only for accounts that report it

Deliberately not in this PR

Routing. Sticky-balanced placement weighs window pressure only, so it will currently send cold sessions to an account with 1% of its credit budget left, and spend_control.reached is the boolean that flips when the budget is gone. Wiring that in changes placement behaviour rather than display and wants its own review — happy to follow up.

model_usage.credits_would_enable (per-model availability gated behind buying credits) is also left alone, though it is directly relevant to gpt-6-astra being in the allow-list.

Gates from the repo root: build, format:check (157 files), lint (157 files), types (3 projects), test — 147 core + 1367 OpenCode + 14 Pi.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Surfaces the credit budget wham/usage already reports, so accounts no longer look healthy on both rate-limit windows while spend controls are close to refusing traffic. Previously normalizeWham kept only reset-credit counts and dropped the budget; now it reaches the sidebar as a third bar and /openai-quota as a credit line. Closes #158.

New Features

  • The spend-control reading is optional: accounts without it keep the exact same snapshot and two-bar sidebar.
  • Parsing is defensive because the API sends numeric fields as strings and spend_control, individual_limit, and credits.balance can be null.
  • The budget survives per-turn quota pushes, which don't carry spend-control fields.
  • The feature is display-only: routing still uses window pressure, and model_usage.credits_would_enable is left untouched.

Written for commit 87cdca9. Summary will update on new commits.

Review in cubic

wham/usage carries a third exhaustion axis on its own clock: a credit
budget with its own limit, usage and reset, independent of the two rate
limit windows. normalizeWham read only rate_limit_reset_credits, so an
account could sit healthy on both windows and still be refused with
nothing on any surface to explain it.

The numeric fields arrive as strings and both spend_control and its
individual_limit are nullable, so every field is parsed defensively and
the whole reading stays optional — an account without spend controls
produces exactly the snapshot it did before.
@ualtinok
ualtinok merged commit 87b8641 into cortexkit:main Sep 18, 2026
5 checks passed
@ualtinok

Copy link
Copy Markdown
Contributor

Merged as 87b8641, and #158 closed with it.

The framing that sold it is the risk running opposite to the feature: most accounts have no spend control, so this shows for a few while a rendering regression would hit everyone. Pinning that with exact-match assertions on the projected rows, rather than checking a value is undefined, is the form that actually catches it — and I confirmed by mutation rather than taking it on trust. Making the bar unconditional reddens four, including three window-projection tests with nothing to do with this feature, which is exactly the blast radius the assertion shape is there to expose.

The three parsing traps are worth keeping in the record, because each is silently wrong rather than loudly wrong: numbers arriving as strings, spend_control and individual_limit independently nullable, and balance: null alongside has_credits: true. The last one is the nastiest — reading a null balance as "no credits" inverts the meaning.

On what you left out: agreed on both, and the routing one is the interesting half. Sticky placement weighs window pressure only, so it will currently favour an account with 1% of its credit budget left over one at 60% of its 5-hour window — and spend_control.reached is the boolean that makes that a hard refusal rather than a gradient. Worth its own review precisely because it changes where traffic goes rather than what a bar says, and because the weighting question (is a credit budget a third pressure axis, or a hard gate that removes a candidate?) is a design call rather than a wiring one. Send it when you want to have that argument.

model_usage.credits_would_enable I would also like eventually, since it is the field that would tell us whether gpt-6-astra is actually reachable on an account rather than merely allow-listed.

Gate on the merge: core 147, opencode 1372, pi 14, typecheck and biome clean.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

wham/usage returns a credit budget and a spend-control limit that nothing surfaces

2 participants