Part of #72, follows #168. The router's policy is still one function
(`router.score`); a restless bandit needs rewards, and nothing recorded
what a pick turned into. This card is the data, not the policy.
- `router.py`: the shape of an outcome and the arithmetic over the log.
`picks()` reads every live pick out of Astra's beats (a card picked
twice keeps its first pick; a dry run picked nothing), `outcome()`
settles one pick into a line and returns None while its card is open,
`read_outcomes()`/`summary()`/`outcome_lines()` are the read side.
- `housekeeping.py` step 4: one line per settled pick appended to
`time/ledger/router-outcomes.jsonl` in one commit, dry-run writes
nothing. A pick already in the file is never written twice.
- `astra.py`: one summary line per kind (picks, merged share, median
days, median $) over the ranked table, so the owner sees what a kind
returns before changing its weight.
Two notes on what the card asked for:
- Spend proxy: the card offered the card's worker beats' `usd` or the
runs' time window. Beats carry no `usd` today, so this uses the runs'
time window — each `dispatched` beat until the next beat on the card
— which attributes a run's spend to the card that ran it instead of
charging it the whole factory's spend over the days it was open.
Review and planning calls on the card are not counted; two cards
whose runs overlap are each charged their own window. Documented in
`time/ledger/README.md`.
- The line carries `pr` as well as the shape the card listed: the card
asks for the PR number and the line is the only place to keep it.
- The file lives under `time/ledger/`, as the card says, since that is
the one path `ci.yml` ignores and the sweep appends to it daily. No
workflow change is needed: `housekeeping.yml` already passes
`GITHUB_TOKEN` (the push) and `TIME_VENTURE_MEASURE_DAYS` (day 7).
A venture pick waits until its venture's day 7 has passed and then
carries the signups counted at that day; with no registry entry for the
card the line goes in with no count rather than waiting forever.
Tests: `test_housekeeping.py` covers a merged pick, an unmerged pick, a
pick still in flight (no line), idempotence, the venture's day-7 count
and dry-run; `test_time.py` section 172 covers the pure functions, the
summary's arithmetic and the log's header. All three suites green.
Closes#172
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0198aoK6ddbLN4d6q8r3J7vg
The cloud lane: cloud sessions build cards on the cloud-session credit (#197)
Astra labels the best M cards `cloud` (up to 3 in the lane); the launcher on hb-5 starts a claude --cloud session per card on the GitHub copy; sync brings the branch home as a PR that closes the card and moves it cloud -> ready; the heartbeat adopts and the council lands it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017K4vyY3yUD3GsvoZaAtKkD
time/worker-run sed -n 845,885p .github/time/test_time.py; tail -5 .github/time/test_time.py
Budget: the monthly cap counts out-of-pocket money; credit is limited by its pool (#193)
credit_covered(): what an expiring pool's prepaid credit paid this month, within its balance and before it lapses, is left out of the month TIME_MONTHLY_USD caps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017K4vyY3yUD3GsvoZaAtKkD
Router: a card that waits is never picked; Astra gets TIME_WORKER (#185)
An open Depends on #N, or the seat's blocked note, keeps a card out of the picks and after the movable cards in the promotion order. astra.yml passes TIME_WORKER and TIME_PACE.
Closes#184.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017K4vyY3yUD3GsvoZaAtKkD
Two dry rankings (2026-10-19 01:02 and 13:14 UTC) put the right cards on top (#135, #175), dropped blocked #84 once it was known stale, and kept L cards out. The two gaps they showed are fixed: money left is the worker's pool (#181, closes#175) and repeat cards aren't written (#181); the stall that kept promotion from happening at all is fixed (#183, closes#135).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017K4vyY3yUD3GsvoZaAtKkD
Router: money left is the worker pool's allowance; no repeat cards (#181)
money_left(): the allowance left in the pool that takes TIME_WORKER's route, else the daily cap's remainder, capped by the month's. The seat sees cards closed in the last 14 days; a new card repeating an open or lately closed one is not written.
Closes#175.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017K4vyY3yUD3GsvoZaAtKkD
Sync: a repo's copies on other forges follow its home (#177)
sync.py + time/sync.json + the sync workflow. One home per repo, one writer per branch: main goes out to every copy
fast-forward only (a diverged copy is a needs-human issue, never overwritten, unless forced once); a copy's claude/*
branches come home as PRs for the council. First copy: github.com/teahubxyz/time, skipped until GITHUB_MIRROR_TOKEN.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017K4vyY3yUD3GsvoZaAtKkD
time/worker-run cd .github/time; grep -n "stuck_h =\|auto_max =\|TIME_AUTO_RETRY" time.py | head | cut -c1-220; sed
The owner's credit: $200 granted 2026-10-13, $162.22 left on the 18th by the console, expiring 2026-11-08. The ledger
books $38.51 of Claude spend since the grant, so $161.49 as of the 19th. Paced over the 20 days left that is about
$8/day for the anthropic route; the $100 monthly cap stays hard and binds first in October.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017K4vyY3yUD3GsvoZaAtKkD
Adaptive budget: pace spend to each capacity pool (#174, re-cut of #167)
The worker's #82 change (sonnet via Time): budget_check(route) paces each call to its capacity pool in
time/pools.json (expiring credit spread over the days left, refilling windows, or TIME_DAILY_USD without a pool);
the monthly cap stays hard. pools.json is read through the API once per run, since every workflow checks out only
.github/time. Ships empty: nothing changes until it is filled in. Closes#82.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017K4vyY3yUD3GsvoZaAtKkD
Router v1: the factory ranks its own next work, dry to start (#169)
Astra's seat judges every candidate (kind, value, size); router.py ranks them with the owner's weights per kind
(time/router.json), a restless boost and card age. dry posts the table; live queues from the top within the day's
money and the heartbeat promotes queued cards in ranked order. Astra reads the last 14 days of its log. Closes#168.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017K4vyY3yUD3GsvoZaAtKkD
venture: the lead (opus) researches and picks the day's venture alone; fable is out (#164)
One seat, TIME_VENTURE_LEAD (opus), runs the day's venture: a research round over the top trending repos (with the
start of their READMEs) and the studio's track record, TIME_VENTURE_FIELD candidates with a verdict each, one pick;
no pitches, no vote. It writes the launch plan alone and judges the build, and hands the launch drafts to its helper
(TIME_VENTURE_HELPER, sonnet). The chair and judge default to opus. Batches keep the board; TIME_VENTURE_LEAD=board
brings it back for the daily venture. Closes#165.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017K4vyY3yUD3GsvoZaAtKkD
venture: one venture a day, built for quality (#162)
The day's card starts only once every other venture card is launched or held; the board picks one from a field of
TIME_VENTURE_FIELD (6) pitches; opus builds; the judge scores the product 1-10 and a page under TIME_VENTURE_BAR (8)
gets polish rounds (never the last of TIME_VENTURE_FIXES=3), then launches with its score. An open batch card with
no Time beats no longer starts a batch while batches are off. Closes#163.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017K4vyY3yUD3GsvoZaAtKkD
venture.yml: install Chromium for the browser pass (#157)
The first Claude ventures launched without the browser pass (no Chrome on the runner): no headless checks, no
contact sheet for the portfolio. One step installs Chromium in the job image; venture.py's chrome() finds it.
Merged on the owner's word, CI green (run 1014).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017K4vyY3yUD3GsvoZaAtKkD
venture.yml: every 6 hours, Claude seats (#141)
Owner's call: the venture loop runs every 6 hours instead of every 20 minutes, on the Claude seats.
The builder is vars.TIME_VENTURE_WORKER || claude:sonnet, decoupled from the heartbeat's worker.
Merged on the owner's word after the council approved the engine half (#140).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017K4vyY3yUD3GsvoZaAtKkD