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
The Usage chart shows 4.2M tokens for the day the 250M ceiling was tripped — the daily series drops the cache columns, which are 98.2% of what the ceiling counts #547
Six agents were stopped overnight by the 250M-token daily circuit breaker, twice, the second time at iteration 0 with 268M tokens counted (transcript quoted in #485). He then opened the Usage page to find the tokens. The page's daily chart, for the single most expensive day in the ledger, tops out at 4.2M tokens.
Measured
usage_summary on production, 2026-08-13.
The daily series for 2026-08-11 — the largest day in the whole ledger:
and usage.ts:386 builds each entry from only those four fields, although the UsageBucket behind it (dayMap) has already accumulated cacheReadTokens and cacheWriteTokens via bump() at usage.ts:330-331. The numbers are computed and then discarded on the way out.
The console mirrors the shape it is given. store/console/src/pages/Usage.tsx:10 declares interface Day with no cache fields, and :95:
Note this is not an oversight of principle elsewhere on the page: the totals strip (Usage.tsx:284) and the per-row TokenCell (:136-148) both include cache and both say so. It is only the time series — the one view that could answer "which day did I blow the ceiling, and on what?" — that drops it.
The ceiling was written to count every token regardless of payer (usage.ts:614-619) because a subscription runaway consumes tokens, not dollars. Also correct.
Composed: the ceiling counts a quantity that is 98% cache, and the only chart that could show it over time was written before the cache columns existed and never grew them. A user who trips the token breaker is sent to a page that contradicts it.
What to do, cheapest first
Widen the daily row. Add cacheReadTokens / cacheWriteTokens to UsageSummary["daily"] and to the object built at usage.ts:386. Both values are already in b. Two fields, no query change, no migration.
Make the chart's "Tokens" metric the ceiling's metric.Usage.tsx:95 → d.inputTokens + d.outputTokens + (d.cacheReadTokens ?? 0) + (d.cacheWriteTokens ?? 0). The tooltip at :111 should break it out (4.2M I/O + 850M cache) so the composition is legible, not just the magnitude.
Then, and only then, draw the ceiling on it. A horizontal 250M/day rule on the token chart is what turns "you were stopped" into "here is the day, and here is why". Worth its own ticket; it is worthless before step 1 because the series it would sit above is off by two orders of magnitude.
Alternatives considered and rejected
Leave the chart at I/O and add a separate cache chart. Rejected: the failure is a reader comparing ONE number to the ceiling. Two charts means the reader must add them, which is the arithmetic that was silently wrong in the first place.
Change the ceiling to count only input+output so it matches the chart. Rejected, and firmly — it would make the breaker blind to exactly the workload that trips it. usage.ts:614-619 is right that tokens are the unit a subscription runaway is denominated in, and cache reads are real tokens sent to a real API. Fix the display.
GET /v1/usage returns cacheReadTokens and cacheWriteTokens on every daily entry.
For this account, 2026-08-11's entry reports non-zero cache tokens and the chart's tokens metric for that day is within rounding of the sum of the four columns.
sum(daily[].{input,output,cacheRead,cacheWrite}) equals totals for the same range — assert it in usage.test.ts; today that identity silently fails for two of the four columns.
The chart tooltip states the I/O and cache split rather than one number.
Regression risk
The chart's y-axis will jump ~137x for any account with engine rows, which is the point, but it will also flatten the pre-engine days to invisibility. Check the denseDays zero-days still render as gaps rather than being lost in the new scale, and keep the "Cost" metric untouched — that one is already correct.
Measured via MCP usage_summary and GET /v1/budget/limits on production; the code paths are read from source.
Related: #212 (split cache out), #270 (what the estimate excludes), #485 (which ceiling to enforce), #543, #544.
The owner's account of it
Six agents were stopped overnight by the 250M-token daily circuit breaker, twice, the second time at iteration 0 with 268M tokens counted (transcript quoted in #485). He then opened the Usage page to find the tokens. The page's daily chart, for the single most expensive day in the ledger, tops out at 4.2M tokens.
Measured
usage_summaryon production, 2026-08-13.The daily series for 2026-08-11 — the largest day in the whole ledger:
{ "date": "2026-08-11", "inputTokens": 2192612, "outputTokens": 2032986, "costMicros": 6457081537, "calls": 822 }inputTokens + outputTokens = 4,225,598. That is what the chart plots. That day's notional value is $6,457.08.The same window's totals, which DO carry the cache columns:
So over the last seven days:
accountUsageSincecounts against the 250M ceiling sums to 1,302,882,910.Live confirmation of the ceiling's own arithmetic,
GET /v1/budget/limits, 2026-08-13:30.7M tokens in the trailing 24h, against a day whose chart-visible tokens are under 1M.
Mechanism — two places, one omission
The ceiling counts cache.
workers/api/src/lib/usage.ts:643-645:The daily series does not carry cache.
usage.ts:314:and
usage.ts:386builds each entry from only those four fields, although theUsageBucketbehind it (dayMap) has already accumulatedcacheReadTokensandcacheWriteTokensviabump()atusage.ts:330-331. The numbers are computed and then discarded on the way out.The console mirrors the shape it is given.
store/console/src/pages/Usage.tsx:10declaresinterface Daywith no cache fields, and:95:Note this is not an oversight of principle elsewhere on the page: the totals strip (
Usage.tsx:284) and the per-rowTokenCell(:136-148) both include cache and both say so. It is only the time series — the one view that could answer "which day did I blow the ceiling, and on what?" — that drops it.Why it composed into a bug
input_tokensso the hit rate became visible and cost stopped being overstated. Correct.usage.ts:614-619) because a subscription runaway consumes tokens, not dollars. Also correct.Composed: the ceiling counts a quantity that is 98% cache, and the only chart that could show it over time was written before the cache columns existed and never grew them. A user who trips the token breaker is sent to a page that contradicts it.
What to do, cheapest first
cacheReadTokens/cacheWriteTokenstoUsageSummary["daily"]and to the object built atusage.ts:386. Both values are already inb. Two fields, no query change, no migration.Usage.tsx:95→d.inputTokens + d.outputTokens + (d.cacheReadTokens ?? 0) + (d.cacheWriteTokens ?? 0). The tooltip at:111should break it out (4.2M I/O + 850M cache) so the composition is legible, not just the magnitude.Alternatives considered and rejected
usage.ts:614-619is right that tokens are the unit a subscription runaway is denominated in, and cache reads are real tokens sent to a real API. Fix the display.Acceptance criteria
GET /v1/usagereturnscacheReadTokensandcacheWriteTokenson everydailyentry.sum(daily[].{input,output,cacheRead,cacheWrite})equalstotalsfor the same range — assert it inusage.test.ts; today that identity silently fails for two of the four columns.Regression risk
The chart's y-axis will jump ~137x for any account with engine rows, which is the point, but it will also flatten the pre-engine days to invisibility. Check the
denseDayszero-days still render as gaps rather than being lost in the new scale, and keep the "Cost" metric untouched — that one is already correct.Measured via MCP
usage_summaryandGET /v1/budget/limitson production; the code paths are read from source.Related: #212 (split cache out), #270 (what the estimate excludes), #485 (which ceiling to enforce), #543, #544.