Skip to content

feat(cli): weight cache reads in the limits usage bar - #56

Merged
coldhighsun merged 3 commits into
mainfrom
feat/cache-read-weighted-usage-bar
Oct 21, 2026
Merged

coldhighsun merged 3 commits into
mainfrom
feat/cache-read-weighted-usage-bar

Conversation

@coldhighsun

Copy link
Copy Markdown
Owner

Problem

The usage-progress bar of aimon watch --view limits ran ahead of the percentage Claude shows (seen with Sonnet 5.5 at high effort).

The bar was list-price cost / limit, where the limit was derived once from a /usage percentage. Cache reads dominate a long agentic session: in the author's transcripts they were about 98% of all tokens and about 65% of the list-price cost. The subscription quota is not metered at API list prices, so counting cache reads in full made the bar run fast. Deduplication, thinking tokens and the window placement were checked and are not the cause.

Changes

  • Core: UsageWindowSummary carries a CostBreakdown (input / output / cache read / cache write); EstimatedCost is now its total, so the cost has a single source. CostCalculator.EstimateCostBreakdown prices each kind separately. Total is not serialized.
  • Weighted bar: the bar is measured in a quota-weighted cost where the cache-read part is scaled by --cache-read-weight (default 0.1). --reset-cache-read-weight forgets a saved weight; the two options cannot be combined (rejected at parse time).
  • Saved readings instead of saved limits: the percentage read off Claude plus the cost counted at that moment is saved (UsageCalibration), and the limit is derived from it under the weight in effect. Tuning the weight therefore does not need /usage to be read again.
  • Safer calibration: NaN, infinite and out-of-range percentages are rejected; a percentage given while nothing is counted for the window is ignored instead of replacing a working calibration (with a notice that names the real cause); both arguments are validated before anything is prompted for.
  • Visibility: a line below the table shows what the bar is made of (cache read / output / cache write / input) and how much of the list-price cost it counts. Startup and r notices are drawn in the view through SpectreRenderer.WithNotices, because anything printed is cleared with the screen.
  • Testability: the startup decisions moved into LimitsStartup, which takes inputRedirected and never reads the terminal state itself.
  • Docs: README (English and Chinese) and CLAUDE.md.

Behaviour change to be aware of

Settings written by the previous version store a derived limit, which cannot be re-derived under another cache read weight. It is ignored on load, reported once when it leaves a window without a calibration and input is redirected, and cleared on the next save. In an interactive terminal the percentages are prompted for once.

Caveat

The default weight of 0.1 is a heuristic, not a measured or published figure; I could not compare against Claude's real percentage. The help text, README and CLAUDE.md say so. Tune it with --cache-read-weight against /usage.

Not done

  • No automated test for the interactive prompts (PromptAndSaveBoth, PromptForCalibration): they use the console and the real settings file.
  • The pricing table is unchanged; claude-sonnet-5-5 still resolves to the sonnet-5 entry.

Test plan

  • dotnet build AIUsageMonitor.slnx (0 warnings, 0 errors)
  • dotnet test AIUsageMonitor.slnx (475 passed)
  • The renderer test fails when the bar is switched back to the list-price cost (checked by hand)
  • Run aimon watch --view limits, press r, enter the /usage percentage, and compare the bar with /usage after a while; adjust --cache-read-weight if needed

UsageWindowSummary now carries a CostBreakdown (input, output, cache read,
cache write) and EstimatedCost is its total, so the cost has one source of
truth. CostCalculator.EstimateCostBreakdown prices each kind separately and
EstimateCost is defined as its total; the breakdown is not serialized with a
derived Total.

This lets callers weigh the kinds of token differently, which the limits view
needs because the subscription quota is not metered at API list prices.
The usage-progress bar of `watch --view limits` ran ahead of the percentage
Claude shows, because it was measured in API list-price cost and cache reads
make up most of that cost in a long session while the subscription quota does
not weigh them that way.

- Measure the bar in a quota-weighted cost: the cache-read part is scaled by
  --cache-read-weight (default 0.1, a heuristic). --reset-cache-read-weight
  forgets a saved weight.
- Save the reading each limit is derived from (the percentage read off Claude
  plus the cost counted at that moment) instead of the derived limit, so the
  weight can be tuned without reading /usage again. Limits saved by an older
  version cannot be carried over and are reported once.
- Ignore a percentage given while nothing is counted for the window instead of
  replacing a working calibration, and reject NaN, infinite and out-of-range
  percentages.
- Show what the bar is made of below the table, and show startup and
  recalibration notices in the view, because anything printed is cleared with
  the screen.
- Move the startup decisions into LimitsStartup so they can be tested without
  a terminal.
Document the cache read weight and its reset flag in the README (English and
Chinese), and record in CLAUDE.md where the limits startup decisions live and
that messages for the user must go through the notices list instead of being
printed, since the screen is cleared right after startup.
@codecov

codecov Bot commented Oct 21, 2026

Copy link
Copy Markdown

@coldhighsun
coldhighsun merged commit b59dba0 into main Oct 21, 2026
8 of 9 checks passed
@coldhighsun
coldhighsun deleted the feat/cache-read-weighted-usage-bar branch October 21, 2026 07:13
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.

1 participant