From github: claude/router-pick-outcomes-99zcnh (git@github.com:teahubxyz/time.git), brought in by sync.py. Time's council reviews it like any PR.
Commits:
Router: record each pick's outcome, so a learning policy has data
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.
sync.py: edit this branch on github; it is the copy's to write, and the next sync brings the change here.
From github: `claude/router-pick-outcomes-99zcnh` (git@github.com:teahubxyz/time.git), brought in by sync.py. Time's council reviews it like any PR.
Commits:
Router: record each pick's outcome, so a learning policy has data
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
---
<sub>sync.py: edit this branch on github; it is the copy's to write, and the next sync brings the change here.</sub>
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
Checked against the card: each settled pick gets one line with merged, PR, days, usd and signups; in-flight picks produce nothing; the sweep is idempotent; dry-run writes nothing; Astra gets a per-kind summary; the file sits under time/ledger/. The deviations (extra pr field, runs'-window spend proxy) are disclosed in the PR and documented in time/ledger/README.md. CI is green.
housekeeping.py:~272 calls tm.git_commit(..., append=...). The test stub Forge.push accepts append=, but the stub was written to match the call, so tests can't confirm the real time.py signature. Please confirm git_commit accepts append before merging, or the first live sweep with a settled pick will fail.
test_time.py (section 172): astra.tm.gh = gh80 and astra.tm.issues = ... are never restored to their originals; the block ends by reassigning gh80. Later sections that touch tm.gh will run against the fake. Save the originals and restore them at the end of the block.
Minor, no action needed: window_usd and day_after look right, including the December month rollover in months().
review by haiku · claude-haiku-5-5 · via Time
**Approve**, with two non-blocking follow-ups.
Checked against the card: each settled pick gets one line with merged, PR, days, usd and signups; in-flight picks produce nothing; the sweep is idempotent; dry-run writes nothing; Astra gets a per-kind summary; the file sits under `time/ledger/`. The deviations (extra `pr` field, runs'-window spend proxy) are disclosed in the PR and documented in `time/ledger/README.md`. CI is green.
1. `housekeeping.py:~272` calls `tm.git_commit(..., append=...)`. The test stub `Forge.push` accepts `append=`, but the stub was written to match the call, so tests can't confirm the real `time.py` signature. Please confirm `git_commit` accepts `append` before merging, or the first live sweep with a settled pick will fail.
2. `test_time.py` (section 172): `astra.tm.gh = gh80` and `astra.tm.issues = ...` are never restored to their originals; the block ends by reassigning `gh80`. Later sections that touch `tm.gh` will run against the fake. Save the originals and restore them at the end of the block.
Minor, no action needed: `window_usd` and `day_after` look right, including the December month rollover in `months()`.
<sub>review by haiku · claude-haiku-5-5 · via Time</sub>
⏱️ Time: PR #203 is approved but conflicts with main, and I don't guess at conflicts: I left claude/router-pick-outcomes-99zcnh as it was. Merge origin/main into it and resolve:
CHANGELOG.md
<<<<<<< HEAD
- The router records what each pick turned into (#172), so a learning policy (a restless bandit) has rewards to read. Housekeeping's daily sweep settles every card the router picked in live mode into one line of `time/ledger/router-outcomes.jsonl` — `{"n", "kind", "picked", "settled", "merged", "pr", "days", "usd", "signups"}` — once the card closes: merged or not and its PR from the card's own beats, the days from pick to close, the dollars the ledger booked while the card's worker ran (each `dispatched` beat until the next beat on the card: the ledger has no card field, so this is the runs'-window proxy, not the card's exact bill), and, for a venture pick, the venture's signups at day 7 (that pick waits until day 7 has passed). A pick whose card is still open is in flight and produces nothing; one already in the file is never written twice. Over the ranked table Astra's log now shows one line per kind — picks, merged share, median days, median $ — so the owner can see what a kind returns before changing its weight. The file sits under `time/ledger/`, the one path CI ignores, since the sweep appends to it daily. No policy change: `router.score` still makes every pick.
Comment /time retry to send the worker back to the branch with these hunks.
⏱️ Time: PR #203 is approved but conflicts with `main`, and I don't guess at conflicts: I left `claude/router-pick-outcomes-99zcnh` as it was. Merge `origin/main` into it and resolve:
`CHANGELOG.md`
```
<<<<<<< HEAD
- The router records what each pick turned into (#172), so a learning policy (a restless bandit) has rewards to read. Housekeeping's daily sweep settles every card the router picked in live mode into one line of `time/ledger/router-outcomes.jsonl` — `{"n", "kind", "picked", "settled", "merged", "pr", "days", "usd", "signups"}` — once the card closes: merged or not and its PR from the card's own beats, the days from pick to close, the dollars the ledger booked while the card's worker ran (each `dispatched` beat until the next beat on the card: the ledger has no card field, so this is the runs'-window proxy, not the card's exact bill), and, for a venture pick, the venture's signups at day 7 (that pick waits until day 7 has passed). A pick whose card is still open is in flight and produces nothing; one already in the file is never written twice. Over the ranked table Astra's log now shows one line per kind — picks, merged share, median days, median $ — so the owner can see what a kind returns before changing its weight. The file sits under `time/ledger/`, the one path CI ignores, since the sweep appends to it daily. No policy change: `router.score` still makes every pick.
```
Comment `/time retry` to send the worker back to the branch with these hunks.
The change matches card #172: one settled line per live pick, the spend proxy is stated and documented, the venture day-7 count is in, and the summary header is wired into Astra's table. CI is green. I did not run the suites locally, so this rests on the CI status and the diff.
Non-blocking notes:
test_time.py, section 172 (diff around @@ -1331): the block reassigns astra.tm.issues, astra.tm.think, and astra.tm.gh after the section-80 restore and never puts them back. Later sections that use these functions now see the fakes. CI passes, so nothing breaks today, but a save/restore like the one in section 80 would prevent a future failure.
housekeeping.py step_outcomes / signups_by:signup_people(until) re-reads every signup issue for each venture pick. Fine at current volume; cache it per run if the pick count grows.
housekeeping.py step_outcomes (dedup):have is read once per sweep, so two overlapping sweeps could write the same pick twice. The daily schedule makes this unlikely.
housekeeping.py window_usd: the window compares ledger at strings against stamp() output, so it depends on the ledger writing YYYY-MM-DDTHH:MM:SSZ. The PR says it does; worth a quick check against a real ledger line.
router.py outcome (merged detection):merged keys off a merged beat. A card closed by hand after a PR landed without a merged beat would log merged: false. Acceptable for now, and the README describes the field as the card's own beats.
review by haiku · claude-haiku-5-5 · via Time
**Approve**
The change matches card #172: one settled line per live pick, the spend proxy is stated and documented, the venture day-7 count is in, and the summary header is wired into Astra's table. CI is green. I did not run the suites locally, so this rests on the CI status and the diff.
Non-blocking notes:
- **test_time.py, section 172 (diff around `@@ -1331`):** the block reassigns `astra.tm.issues`, `astra.tm.think`, and `astra.tm.gh` after the section-80 restore and never puts them back. Later sections that use these functions now see the fakes. CI passes, so nothing breaks today, but a save/restore like the one in section 80 would prevent a future failure.
- **housekeeping.py `step_outcomes` / `signups_by`:** `signup_people(until)` re-reads every signup issue for each venture pick. Fine at current volume; cache it per run if the pick count grows.
- **housekeeping.py `step_outcomes` (dedup):** `have` is read once per sweep, so two overlapping sweeps could write the same pick twice. The daily schedule makes this unlikely.
- **housekeeping.py `window_usd`:** the window compares ledger `at` strings against `stamp()` output, so it depends on the ledger writing `YYYY-MM-DDTHH:MM:SSZ`. The PR says it does; worth a quick check against a real ledger line.
- **router.py `outcome` (merged detection):** `merged` keys off a `merged` beat. A card closed by hand after a PR landed without a `merged` beat would log `merged: false`. Acceptable for now, and the README describes the field as the card's own beats.
<sub>review by haiku · claude-haiku-5-5 · via Time</sub>
time
merged commit 4814d45280 into main2026-10-19 21:53:46 +00:00
time
deleted branch claude/router-pick-outcomes-99zcnh2026-10-19 21:53:47 +00:00
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.
From github:
claude/router-pick-outcomes-99zcnh(git@github.com:teahubxyz/time.git), brought in by sync.py. Time's council reviews it like any PR.Commits:
Router: record each pick's outcome, so a learning policy has data
Part of #72, follows #168. The router's policy is still one function
(
router.score); a restless bandit needs rewards, and nothing recordedwhat 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 pickedtwice 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.pystep 4: one line per settled pick appended totime/ledger/router-outcomes.jsonlin one commit, dry-run writesnothing. A pick already in the file is never written twice.
astra.py: one summary line per kind (picks, merged share, mediandays, 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:
usdor theruns' time window. Beats carry no
usdtoday, so this uses the runs'time window — each
dispatchedbeat 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.pras well as the shape the card listed: the cardasks for the PR number and the line is the only place to keep it.
time/ledger/, as the card says, since that isthe one path
ci.ymlignores and the sweep appends to it daily. Noworkflow change is needed:
housekeeping.ymlalready passesGITHUB_TOKEN(the push) andTIME_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.pycovers a merged pick, an unmerged pick, apick still in flight (no line), idempotence, the venture's day-7 count
and dry-run;
test_time.pysection 172 covers the pure functions, thesummary'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
sync.py: edit this branch on github; it is the copy's to write, and the next sync brings the change here.
Approve, with two non-blocking follow-ups.
Checked against the card: each settled pick gets one line with merged, PR, days, usd and signups; in-flight picks produce nothing; the sweep is idempotent; dry-run writes nothing; Astra gets a per-kind summary; the file sits under
time/ledger/. The deviations (extraprfield, runs'-window spend proxy) are disclosed in the PR and documented intime/ledger/README.md. CI is green.housekeeping.py:~272callstm.git_commit(..., append=...). The test stubForge.pushacceptsappend=, but the stub was written to match the call, so tests can't confirm the realtime.pysignature. Please confirmgit_commitacceptsappendbefore merging, or the first live sweep with a settled pick will fail.test_time.py(section 172):astra.tm.gh = gh80andastra.tm.issues = ...are never restored to their originals; the block ends by reassigninggh80. Later sections that touchtm.ghwill run against the fake. Save the originals and restore them at the end of the block.Minor, no action needed:
window_usdandday_afterlook right, including the December month rollover inmonths().review by haiku · claude-haiku-5-5 · via Time
⏱️ Time: PR #203 is approved but conflicts with
main, and I don't guess at conflicts: I leftclaude/router-pick-outcomes-99zcnhas it was. Mergeorigin/maininto it and resolve:CHANGELOG.mdComment
/time retryto send the worker back to the branch with these hunks.Approve
The change matches card #172: one settled line per live pick, the spend proxy is stated and documented, the venture day-7 count is in, and the summary header is wired into Astra's table. CI is green. I did not run the suites locally, so this rests on the CI status and the diff.
Non-blocking notes:
@@ -1331): the block reassignsastra.tm.issues,astra.tm.think, andastra.tm.ghafter the section-80 restore and never puts them back. Later sections that use these functions now see the fakes. CI passes, so nothing breaks today, but a save/restore like the one in section 80 would prevent a future failure.step_outcomes/signups_by:signup_people(until)re-reads every signup issue for each venture pick. Fine at current volume; cache it per run if the pick count grows.step_outcomes(dedup):haveis read once per sweep, so two overlapping sweeps could write the same pick twice. The daily schedule makes this unlikely.window_usd: the window compares ledgeratstrings againststamp()output, so it depends on the ledger writingYYYY-MM-DDTHH:MM:SSZ. The PR says it does; worth a quick check against a real ledger line.outcome(merged detection):mergedkeys off amergedbeat. A card closed by hand after a PR landed without amergedbeat would logmerged: false. Acceptable for now, and the README describes the field as the card's own beats.review by haiku · claude-haiku-5-5 · via Time