Router: optimization candidates from the ledger, CI and parked cards #171

Open
opened 2026-10-19 00:59:41 +00:00 by teahubxyz · 6 comments
Owner

Part of #72. Follows #168. Today the router only ranks cards someone wrote, so optimization work shows up only when a human notices it. Astra should find it on its own, deterministically.

Changes (router.py and astra.py):

  • Where it looks, each source a synthetic candidate with key opt:<source>:<id>, kind optimization, and a drafted title and body:
    • The ledger, last 7 days (time/ledger/<YYYY-MM>.jsonl): spend by model and by entry point. If one route is over half the week's spend, or a model's spend per call is double its 7-day median, that's a candidate ("Why did sonnet cost $X on Y?").
    • Actions runs, last 7 days: any workflow with 2 or more failed or cancelled runs (/actions/runs, as tea_actions reads them) is a candidate, with the failure count and the last run id.
    • Parked cards (error / stuck beats) older than a day with no open PR: one candidate each ("retry or split #N"), unless #78's auto-retry already has them.
  • How it's ranked: the seat judges these like any candidate.
    • In live mode, a picked one becomes a written card (unqueued), with the drafted body and a DONE: line. It is never worked on in place.
    • In dry mode, the table shows it.
  • No duplicates: a source with an open card that names it (Router: opt:<source>:<id> in the body) is not proposed again.
  • Tests: a ledger with one dominant route yields one candidate. Two failed runs of one workflow yield one. A candidate with an open card yields none. Live writes the card; dry doesn't.

DONE: PR open with the code above, python3 .github/time/test_time.py green including the new checks, CI green

Part of #72. Follows #168. Today the router only ranks cards someone wrote, so optimization work shows up only when a human notices it. Astra should find it on its own, deterministically. Changes (`router.py` and `astra.py`): - **Where it looks**, each source a synthetic candidate with key `opt:<source>:<id>`, kind optimization, and a drafted title and body: - The ledger, last 7 days (`time/ledger/<YYYY-MM>.jsonl`): spend by model and by entry point. If one route is over half the week's spend, or a model's spend per call is double its 7-day median, that's a candidate ("Why did sonnet cost $X on Y?"). - Actions runs, last 7 days: any workflow with 2 or more failed or cancelled runs (`/actions/runs`, as `tea_actions` reads them) is a candidate, with the failure count and the last run id. - Parked cards (`error` / `stuck` beats) older than a day with no open PR: one candidate each ("retry or split #N"), unless #78's auto-retry already has them. - **How it's ranked:** the seat judges these like any candidate. - In live mode, a picked one becomes a written card (unqueued), with the drafted body and a `DONE:` line. It is never worked on in place. - In dry mode, the table shows it. - **No duplicates:** a source with an open card that names it (`Router: opt:<source>:<id>` in the body) is not proposed again. - **Tests:** a ledger with one dominant route yields one candidate. Two failed runs of one workflow yield one. A candidate with an open card yields none. Live writes the card; dry doesn't. DONE: PR open with the code above, `python3 .github/time/test_time.py` green including the new checks, CI green
time added the cloud label 2026-10-19 16:47:50 +00:00
time added ready and removed cloud labels 2026-10-19 17:15:39 +00:00

☁️ The cloud session's branch came home as PR #199. The heartbeat reviews and lands it like any card PR.

☁️ The cloud session's branch came home as PR #199. The heartbeat reviews and lands it like any card PR. <!-- time {"cloud":"home","pr":199} -->

⏱️ Time: adopted PR #199, built outside the worker. haiku reviewed it on the PR.

⏱️ Time: adopted PR #199, built outside the worker. `haiku` reviewed it on the PR. <!-- time {"beat":"pr","pr":199,"adopted":true,"launched":false,"seat":"haiku","as":"seat","review":"venice","seats":["haiku"]} -->

⏱️ Time: the reviewer objected on PR #199. Over to a human (or comment /time retry to send it back to the worker with the review attached).

⏱️ Time: the reviewer objected on PR #199. Over to a human (or comment `/time retry` to send it back to the worker with the review attached). <!-- time {"beat":"needs-human","pr":199} -->

☁️ The cloud session pushed a revision to PR #199. The heartbeat reviews it again; a verdict from before the push no longer counts.

☁️ The cloud session pushed a revision to PR #199. The heartbeat reviews it again; a verdict from before the push no longer counts. <!-- time {"beat":"dispatched","pr":199,"revised":true,"cloud":true} -->

⏱️ Time: PR #199 is up for this card. haiku reviewed it on the PR.

⏱️ Time: PR #199 is up for this card. `haiku` reviewed it on the PR. <!-- time {"beat":"pr","pr":199,"launched":false,"seat":"haiku","as":"seat","review":"venice","seats":["haiku"]} -->

⏱️ Time: the reviewer objected on PR #199. Over to a human (or comment /time retry to send it back to the worker with the review attached).

⏱️ Time: the reviewer objected on PR #199. Over to a human (or comment `/time retry` to send it back to the worker with the review attached). <!-- time {"beat":"needs-human","pr":199} -->
Sign in to join this conversation.
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: teahubxyz/time#171