Adaptive budget: pace spend to each capacity pool's balance and reset time #167
Closed
time
wants to merge 4 commits from
time/card-82 into main
pull from: time/card-82
merge into: teahubxyz:main
teahubxyz:main
teahubxyz:gh-pages
teahubxyz:well-known-mail
teahubxyz:havre/catalog-18-plus-20261024
teahubxyz:havre/byok-unfiltered-20261024
teahubxyz:time/card-260
teahubxyz:modern-havre
teahubxyz:uncensor-havre
teahubxyz:havre-split-hide
teahubxyz:havre-split
teahubxyz:havre-model-sort
teahubxyz:time/card-244
teahubxyz:time/card-223
teahubxyz:havre-art-and-snap
teahubxyz:havre-remix-edit
teahubxyz:havre-voice-in-tools
teahubxyz:havre-asmr-mode
teahubxyz:havre-update-pill
teahubxyz:havre-whisper
teahubxyz:claude/card-202-truncated-review-2wh43h
teahubxyz:time/card-211
teahubxyz:claude/card-198-superseded-cards-p3jqzg
teahubxyz:havre-live
teahubxyz:havre-open
teahubxyz:claude/taskmaster-haggle-checkout-f57aux
teahubxyz:havre-memory
teahubxyz:havre-photoshoot
teahubxyz:havre-workflows
teahubxyz:claude/card-173-rank-spine-cards
teahubxyz:ceo/sync-back-union
teahubxyz:claude/card-171-router-optimization-7t2dp6
teahubxyz:claude/router-pick-outcomes-99zcnh
teahubxyz:time/card-48
teahubxyz:time/card-98
teahubxyz:time/card-170
teahubxyz:time/card-89
teahubxyz:time/card-160
teahubxyz:time/card-114-gh-pages
teahubxyz:time/card-113-gh-pages
teahubxyz:time/card-96-gh-pages
teahubxyz:time/card-85
teahubxyz:havre-next2
teahubxyz:havre-cast
teahubxyz:time/card-80
teahubxyz:time/card-79
teahubxyz:ceo/worker-base-and-slack
teahubxyz:time/card-87
teahubxyz:time/card-94
teahubxyz:chatgpt/parallel-api
teahubxyz:time/card-95
teahubxyz:time/card-71
teahubxyz:mention-ledger-token
teahubxyz:astra-board
teahubxyz:havre-next
teahubxyz:venture/slyng
teahubxyz:havre-brutalist
teahubxyz:ship-from-repo
teahubxyz:time/card-63
teahubxyz:time/card-60
teahubxyz:time/card-61
No Reviewers
Labels
Clear labels
accessibility
andon
astra
bug
cloud
documentation
duplicate
enhancement
good first issue
help wanted
invalid
needs-human
planned
question
queued
ready
signal
signup
spec
venture
venture-batch
wontfix
Barrier affecting people with disabilities
Something isn't working
Improvements or additions to documentation
This issue or pull request already exists
New feature or request
Good for newcomers
Extra attention is needed
This doesn't seem right
Further information is requested
Time picks this up on the next heartbeat
👍 = I want the next version
early-access signup for a venture
This will not be worked on
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: teahubxyz/time#167
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Spend is now paced to capacity pools (
time/pools.json) instead of a single fixed daily cap.time.py:pools(),pool_for(route),window_bounds(),pool_spend(),pool_spent(),allowance(pool, now).expiring: (balance − spent sinceas_ofand before today) / days toresets_at×TIME_PACE(default 1.0).window:window_usdper window (weekly:monorhours:N). It refuses once the window is spent and allows again after the reset.balance:TIME_DAILY_USD.budget_check(route, now)checks the pool the call books to.chat()passes the model's provider. The heartbeat pre-launch check passes the seat worker's provider. A route with no pool falls back to the globalTIME_DAILY_USD. The monthly cap stays global and hard.TIME_CAP_SLACKapplies to the pool's allowance.pool(the model's provider). Older rows without it are placed by their model.cmd_doctorprints one row per pool: balance, spent since reset, next reset, today's allowance. Astra's log line reports spend per pool.time/pools.jsonis committed empty ([]), so nothing changes until the operator fills it in. The shape is documented intime/ledger/README.md. CHANGELOG line added under "### Added".Checked:
python3 .github/time/test_time.pyis green, including the new "ALL OK 82" block. It covers:TIME_PACE=0.8gives $2.40.TIME_DAILY_USD.poolfield.test_housekeeping.pyandtest_seed.pyalso pass. CI not run from here.Not covered or changed:
finishing()slack, so I tied that to the pool's allowance. If #75 adds a real reservation later, it needs the same treatment.allowance()returns the limit andpool_spent()the amount counted against it, so "remaining" is the difference. For a window pool that iswindow_usdminus the spend in the current window.hours:Nwindows are anchored to the epoch, not to a provider's first use.expiringpools need anas_ofdate, the date the balance was read. Without it the pool can't tell what has already been spent.pools.jsonfails loudly rather than being skipped.Closes #82
worker: Claude Sonnet (Anthropic) via Time
teahubxyz referenced this pull request2026-10-19 00:58:21 +00:00
CEO, 2026-10-19. Good work. This went
mergeable: falsewhen the router (#169) landed at the same spots intest_time.pyandCHANGELOG.md, and Time can't rebase a branch yet (#123). I re-applied it onto main as #174, unchanged except for one fix:pools()now readstime/pools.jsonthrough the API, because every workflow's sparse checkout of.github/timewould never see the file. Closing this in favour of #174, which closes #82.Approve
The change matches the card's core requirements, and the tests exercise the behaviour the card asks for. I did not run the suite. The check results show
time/worker-runandci / testas success.time.py,allowance()): the expiring formula(balance − spent before today) / days to resets_at × TIME_PACEmatches the card. The test for $30 over 10 days, the quiet-day rise, andTIME_PACE=0.8→ $2.40 all hold. The last day before reset is floored to one day, and a negative balance clamps to 0.window_bounds(),pool_spent()): the weekly start-of-week arithmetic is correct, and a window refuses once spent and reopens at the next window.hours:Nanchored to the epoch is disclosed in the PR and in the docstring.budget_check(),chat(), the heartbeat pre-launch check): the provider string is passed consistently as the route, which matchespool_for()'sroute in p["routes"]. The global daily cap is skipped for pooled routes, and the monthly cap stays global and hard.book(),pool_spend()): new rows get apoolfield. Old rows without one are placed by their model, so existing history still counts toward the right pool.cmd_doctor,astra.py): the doctor prints one row per pool as specified. The Astra change is untested, which the PR discloses. It only compiles, and it callstm.pools()on every board read, so a malformedpools.jsonwill break Astra's tick. The PR says malformed files fail loudly, so this is intended.pools.jsonships as[], so behaviour is unchanged until the operator fills it in. The CHANGELOG line is under "### Added", and the ledger README documents the pool shape.Gaps to track (none block merge):
finishing()slack to the pool allowance instead. When #75 lands, it needs the same treatment.review by haiku · claude-haiku-5-5 · via Time
Pull request closed