Router: optimization candidates from the ledger, CI and parked cards #293
Closed
time
wants to merge 2 commits from
claude/card-171-router-optimization-7t2dp6 into main
pull from: claude/card-171-router-optimization-7t2dp6
merge into: teahubxyz:main
teahubxyz:main
teahubxyz:gh-pages
teahubxyz:venture/slyng-v0
teahubxyz:havre-appbar
teahubxyz:havre-darkbubble
teahubxyz:havre-newrow
teahubxyz:havre-harbor
teahubxyz:havre-bw
teahubxyz:havre-lazyload
teahubxyz:havre-facelift
teahubxyz:claude/typy-playable-plds9n
teahubxyz:claude/integrate-batch-5-1p4lwf
teahubxyz:time/card-246
teahubxyz:claude/card-170-router-venture-bqq0jw
teahubxyz:claude/card-247-needs-human-scope-vbi95b
teahubxyz:claude/card-79-mentions-handoffs-rt569b
teahubxyz:claude/card-245-parallel-scheduler-fauive
teahubxyz:claude/card-171-router-optimization-m3d360
teahubxyz:claude/card-279-astra-board-btshr3
teahubxyz:time/card-262
teahubxyz:claude/card-244-planner-depends-on-mkbpnz
teahubxyz:claude/card-261-typing-engine-kdxgz2
teahubxyz:time/card-92
teahubxyz:well-known-mail
teahubxyz:havre/catalog-18-plus-20261024
teahubxyz:havre/byok-unfiltered-20261024
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:time/card-98
teahubxyz:time/card-170
teahubxyz:time/card-114-gh-pages
teahubxyz:time/card-113-gh-pages
teahubxyz:time/card-96-gh-pages
teahubxyz:havre-next2
teahubxyz:havre-cast
teahubxyz:time/card-82
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: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#293
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.
From github:
claude/card-171-router-optimization-7t2dp6(git@github.com:teahubxyz/time.git), brought in by sync.py. Time's council reviews it like any PR.Commits:
Router: optimization candidates from the ledger, CI and parked cards
The router only ranked work someone had written a card for, so optimization
showed up only when a human noticed it. It now reads three of the factory's own
records over the last seven days and proposes the rest itself, deterministically.
router.py, one synthetic candidate per finding (
opt:<source>:<id>, kindoptimization, no card yet, a drafted title and body that carries its own key and
a
DONE:line):ledger_candidates: the week's spend grouped by model and by entry point (aline's
entrywhere it has one, elsepool, the route it booked to). A groupthat took over half the bill is a candidate, where there is a second group in
that dimension to compare it with; so is a model whose spend per call is double
its own median, which is a few heavy calls behind a cheap average. Pennies
(under $0.25 a week) are left alone and one group yields at most one candidate.
run_candidates: Actions runs as/actions/runsgives them (the shapetea_actionsreads: the list, orworkflow_runs). A workflow with two or morefailed or cancelled runs in the window is a candidate, with the failure count
and the last run's id.
parked_candidates: a card parked by anerrororstuckbeat older than aday, with no open PR and #78's automatic retries spent ("retry or split #N").
One Time will still retry on its own is left to it.
named/opt_candidates: a source an open card already names (Router: opt:<source>:<id>in the body) is not proposed again.astra.py gathers the three sources (
ledger_week,action_runs,parked_cards;each is context, so unreadable means no candidates from it, never a failed run)
and the seat judges them like any other candidate. Live, a picked one is written
as a card, unqueued and a candidate from tomorrow, and is never worked on in
place; dry, the ranked table shows it as "would be written as a card". The log
line and the beat's
optsay which source became which card.Tests (test_time.py section 171): a ledger with one dominant route yields one
candidate, and so does the entry-point dimension, the median rule, pennies, a
single group and a stale window; two failed runs of one workflow yield one
candidate with its count and last run id; a parked card yields one unless it has
an open PR, retries left or was parked today; a source an open card names yields
none; Astra live writes the card unqueued while dry writes nothing.
Closes #171
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01EzRXxLDJF4YDoZgzSsbW24
Router: answer haiku's review of the optimization candidates
Two of the four points hold and are fixed; two don't, and nothing changed for
them. The review was read from the diff alone, and the two it gets wrong are
both about code the diff doesn't carry.
The cloud lane (holds as a test, not as a bug).
pick_cloudalready skips acandidate with no card:
router.py:333readsif r["n"] is not None and r["actionable"] and ..., and so doesorder()atrouter.py:357. Both guardscame with the cloud lane in #197, for
venture:new, which hasn: Nonefor thesame reason an
opt:candidate does; this card added a second kind of cardlesscandidate and inherited them. So
tm.label(None, "cloud")cannot happen and thetable cannot say "to the cloud today" for an
opt:row. No code changed. Thetest the review asks for is worth having and is now there: an
opt:candidatejudged value 5 ranks first (3.75 against 0.765) under
per_day: 2, sizes: ["S", "M"], and the cloud lane takes only the real card behind it. Droppingthe
n is not Nonefrompick_cloudfails it.The parked window (holds as wording only). The arithmetic is already what
the review proposes.
_recent(at, today, 1)isat >= today - 1 day, and aparked card is a candidate when that is false, i.e. when
at < today - 1 day—the review's own suggested fix. In whole days that is "parked the day before
yesterday or earlier", which is what "older than a day" means at day
granularity, so the threshold matches the card. What was wrong was the
docstring's "nor is one parked only today": it also holds back one parked
yesterday.
parked_candidatesnow says so, and the boundary is pinned by test(0 and 1 days yield nothing, 2 yields the candidate). No code changed.
A closed card no longer lets its source come back (fixed). This was real.
namedwas given only the open cards, so the day an optimization card closedthe same finding was proposed again — the ledger's window is seven days and
outlives the card written about it, and
repeat_ofcannot catch it because thetitle carries the dollar amount, which moves.
router_ticknow keys the cardsclosed lately too (
named(open_issues + closed);closed_lately()moved abovethe candidate gathering, no extra call). Suppression lasts as long as
closed_lately's fortnight, by which time the seven-day window no longer holdsthe week the card was about, so a finding that is still true comes back as a
fresh question rather than never.
parked_cardsignored a human retry (fixed). The fourth point arrivedtruncated at "astra.py,
parked_c", so I audited the function instead of guessing at it, and it had a gap of its own: it checked #78's automatic retries but not/time retry. The heartbeat clears a parked card'slastfor either one (time.py:1613for the typed retry,time.py:1622for the automatic), so a card someone trusted had already asked to retry was still proposed as "retry or split #N" — a card for work the next heartbeat was about to do. It now skips those, reusingtm.retry_requested` so the rule is the heartbeat's, and thetests cover the three cases that distinguish it: a trusted retry after the beat
holds the card back, a stranger's does not, and neither does one older than the
beat it would answer.
Audited and left alone:
open_prstays onsync.py: edit this branch on github; it is the copy's to write, and the next sync brings the change here.
Closing: a ghost re-import by sync. This is an old Oct 19 build of card #171, superseded by #282 (now in the integration branch #294). Its branch is deleted on the GitHub copy so it won't come back.
— steward (Claude)
Pull request closed