Why.TIME_DAILY_USD / TIME_MONTHLY_USD are fixed numbers. The factory actually draws on several pools with different shapes: API credits that expire on a date, subscription allowances that refill on a window, Venice credits that are a balance. A fixed daily cap leaves expiring credit unspent on quiet days and starves busy ones. The operator's rule: look at actual usage and reset times, and budget to them. Still deterministic: no model decides whether to spend.
Change (.github/time/time.py, governor section).
time/pools.json (committed, operator-maintained at first): one entry per pool — name, kind (expiring | window | balance), balance_usd (or window_usd), resets_at (ISO date for expiring, a cadence such as weekly:mon or hours:5 for window), and which routes book to it (anthropic, venice, codex). The ledger rows gain a pool field from the model's provider.
allowance(pool, now): for expiring, (balance − spent_since_reset) / days_to_reset × TIME_PACE (default 1.0; 0.8 leaves headroom); for window, the window's remaining amount; for balance, the fixed daily cap as today. budget_check() checks the pool the call books to, and the daily/monthly caps become the fallback when a pool has no entry.
cmd_doctor prints each pool: balance, spent since reset, next reset, today's allowance. The Astra log line reports spend per pool, not one number.
Later card: pull balances from provider APIs where one exists; until then the operator updates pools.json and the factory paces to it.
Tests. An expiring pool with $30 left and 10 days to reset allows $3/day and refuses the fourth dollar; the allowance rises after a day passes with no spend; a window pool refuses once the window is spent and allows again after its reset; a route with no pool falls back to TIME_DAILY_USD; the reservation in #75 counts against the pool's allowance, not the global cap.
CHANGELOG line under "### Added".
DONE: PR open with the code above, python3 .github/time/test_time.py green including the new checks, CI green
**Why.** `TIME_DAILY_USD` / `TIME_MONTHLY_USD` are fixed numbers. The factory actually draws on several pools with different shapes: API credits that expire on a date, subscription allowances that refill on a window, Venice credits that are a balance. A fixed daily cap leaves expiring credit unspent on quiet days and starves busy ones. The operator's rule: look at actual usage and reset times, and budget to them. Still deterministic: no model decides whether to spend.
**Change (`.github/time/time.py`, governor section).**
- `time/pools.json` (committed, operator-maintained at first): one entry per pool — `name`, `kind` (`expiring` | `window` | `balance`), `balance_usd` (or `window_usd`), `resets_at` (ISO date for `expiring`, a cadence such as `weekly:mon` or `hours:5` for `window`), and which routes book to it (`anthropic`, `venice`, `codex`). The ledger rows gain a `pool` field from the model's provider.
- `allowance(pool, now)`: for `expiring`, `(balance − spent_since_reset) / days_to_reset × TIME_PACE` (default 1.0; 0.8 leaves headroom); for `window`, the window's remaining amount; for `balance`, the fixed daily cap as today. `budget_check()` checks the pool the call books to, and the daily/monthly caps become the fallback when a pool has no entry.
- `cmd_doctor` prints each pool: balance, spent since reset, next reset, today's allowance. The Astra log line reports spend per pool, not one number.
- Later card: pull balances from provider APIs where one exists; until then the operator updates `pools.json` and the factory paces to it.
**Tests.** An expiring pool with $30 left and 10 days to reset allows $3/day and refuses the fourth dollar; the allowance rises after a day passes with no spend; a window pool refuses once the window is spent and allows again after its reset; a route with no pool falls back to `TIME_DAILY_USD`; the reservation in #75 counts against the pool's allowance, not the global cap.
CHANGELOG line under "### Added".
DONE: PR open with the code above, `python3 .github/time/test_time.py` green including the new checks, CI green
⏱️ Time: today's spend is $15.55 of the $15.00 cap; carrying on tomorrow. Nothing is lost; this card resumes once there is budget.
⏱️ Time: today's spend is $15.55 of the $15.00 cap; carrying on tomorrow. Nothing is lost; this card resumes once there is budget.
<!-- time {"beat":"capped"} -->
⏱️ Time: Claude Sonnet (Anthropic) is working on this now, on branch time/card-82.
⏱️ Time: Claude Sonnet (Anthropic) is working on this now, on branch `time/card-82`.
<!-- time {"beat":"dispatched","launched":true,"worker":"anthropic"} -->
⏱️ Time: dispatched 6h ago and no PR closing #82 yet. Check the agent. I'll dispatch again on my own next beat; comment /time retry to force one.
⏱️ Time: dispatched 6h ago and no PR closing #82 yet. Check the agent. I'll dispatch again on my own next beat; comment `/time retry` to force one.
<!-- time {"beat":"stuck"} -->
⏱️ Time: Claude Sonnet (Anthropic) is working on this now, on branch time/card-82. Retrying on my own, resuming the branch.
⏱️ Time: Claude Sonnet (Anthropic) is working on this now, on branch `time/card-82`. Retrying on my own, resuming the branch.
<!-- time {"beat":"dispatched","launched":true,"worker":"anthropic","auto_retry":true} -->
⏱️ Time: sonnet opened PR #167 (21 steps, claude-sonnet-5-5). Review and landing follow on the next beat.
⏱️ Time: `sonnet` opened PR #167 (21 steps, claude-sonnet-5-5). Review and landing follow on the next beat.
<!-- time {"beat":"dispatched","worker":"anthropic","auto_retry":true,"pr":167,"steps":21,"seat":"sonnet","model":"claude-sonnet-5-5"} -->
⏱️ Time: PR #167 is up for this card. haiku reviewed it on the PR.
⏱️ Time: PR #167 is up for this card. `haiku` reviewed it on the PR.
<!-- time {"beat":"pr","pr":167,"launched":false,"seat":"haiku","as":"time","review":"venice","seats":["haiku"]} -->
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.
Why.
TIME_DAILY_USD/TIME_MONTHLY_USDare fixed numbers. The factory actually draws on several pools with different shapes: API credits that expire on a date, subscription allowances that refill on a window, Venice credits that are a balance. A fixed daily cap leaves expiring credit unspent on quiet days and starves busy ones. The operator's rule: look at actual usage and reset times, and budget to them. Still deterministic: no model decides whether to spend.Change (
.github/time/time.py, governor section).time/pools.json(committed, operator-maintained at first): one entry per pool —name,kind(expiring|window|balance),balance_usd(orwindow_usd),resets_at(ISO date forexpiring, a cadence such asweekly:monorhours:5forwindow), and which routes book to it (anthropic,venice,codex). The ledger rows gain apoolfield from the model's provider.allowance(pool, now): forexpiring,(balance − spent_since_reset) / days_to_reset × TIME_PACE(default 1.0; 0.8 leaves headroom); forwindow, the window's remaining amount; forbalance, the fixed daily cap as today.budget_check()checks the pool the call books to, and the daily/monthly caps become the fallback when a pool has no entry.cmd_doctorprints each pool: balance, spent since reset, next reset, today's allowance. The Astra log line reports spend per pool, not one number.pools.jsonand the factory paces to it.Tests. An expiring pool with $30 left and 10 days to reset allows $3/day and refuses the fourth dollar; the allowance rises after a day passes with no spend; a window pool refuses once the window is spent and allows again after its reset; a route with no pool falls back to
TIME_DAILY_USD; the reservation in #75 counts against the pool's allowance, not the global cap.CHANGELOG line under "### Added".
DONE: PR open with the code above,
python3 .github/time/test_time.pygreen including the new checks, CI green⏱️ Time: today's spend is $15.55 of the $15.00 cap; carrying on tomorrow. Nothing is lost; this card resumes once there is budget.
⏱️ Time: Claude Sonnet (Anthropic) is working on this now, on branch
time/card-82.⏱️ Time: can't go further on this card: 'command'. I'll carry on once it's added.
⏱️ Time: dispatched 6h ago and no PR closing #82 yet. Check the agent. I'll dispatch again on my own next beat; comment
/time retryto force one.⏱️ Time: Claude Sonnet (Anthropic) is working on this now, on branch
time/card-82. Retrying on my own, resuming the branch.⏱️ Time:
sonnetopened PR #167 (21 steps, claude-sonnet-5-5). Review and landing follow on the next beat.⏱️ Time: PR #167 is up for this card.
haikureviewed it on the PR.