Astra: a board for internal work (the venture ceremony turned inward) #69
Open
teahubxyz
wants to merge 4 commits from
astra-board into main
pull from: astra-board
merge into: teahubxyz:main
teahubxyz:main
teahubxyz:gh-pages
teahubxyz:well-known-mail
teahubxyz:havre/catalog-18-plus-20261024
teahubxyz:havre/byok-unfiltered-20261024
teahubxyz:time/card-260
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:ceo/sync-back-union
teahubxyz:claude/card-171-router-optimization-7t2dp6
teahubxyz:claude/router-pick-outcomes-99zcnh
teahubxyz:time/card-48
teahubxyz:time/card-98
teahubxyz:time/card-170
teahubxyz:time/card-89
teahubxyz:time/card-160
teahubxyz:time/card-114-gh-pages
teahubxyz:time/card-113-gh-pages
teahubxyz:time/card-96-gh-pages
teahubxyz:time/card-85
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:time/card-71
teahubxyz:mention-ledger-token
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#69
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.
The venture loop deliberates (seats pitch, the board ranks, a quorum decides); the self-improvement loop didn't: Astra was one seat choosing the factory's own work alone. This gives Astra the same board, reusing the venture board's machinery rather than inventing a second one.
How it runs (only when
TIME_ASTRA_PITCHis set; unset, Astra is byte-for-byte the single planner it was)TIME_ASTRA_PITCHreads the same board, ledger and spine context Astra reads today and proposes up toMAX_READY + MAX_NEWcards: existing issues to queue (same guards as before: aDONE:line, none ofqueued/ready/needs-human/andon) or new cards. The slates merge into one field; the same issue, or the same new title, from two seats is one card with both proposers named.TIME_ASTRA_BOARD(default: the pitching seats) ranks up to three cards; Borda 3/2/1, the venture board's tally, andventure.topapplies its quorum of two.TIME_ASTRA_MAX_READYpicks andTIME_ASTRA_MAX_NEWof them new, the same per-run caps the single planner has (in-flight work across days is the heartbeat'sTIME_PARALLEL). A card one seat alone wants is never opened, so the board can't spam issues.Mechanics.
astra.pyloadsventure.pybesidetime.pyand points it at Astra's owntimemodule (vt.tm = tm), so the board's seats spend through this run's governor and ledger, not a second copy.astra.ymlpasses the two new variables through and its header comment now saysqueued(the code never labelledready)..github/stays a protected path: this merges by hand or by the steward, not by auto-merge.Cost. One pitch round plus one ranking round, in parallel: with three Claude seats that is six small calls a day on top of today's one, well under the
$5day. Venice seats work too but are paused by decision.Turning it on is a variable, not a deploy:
TIME_ASTRA_PITCH="sonnet, haiku, opus"(seat names, as inseats.json). LeaveTIME_ASTRA_BOARDunset to have the same seats rank.Tests. Three new sections in
test_time.py, offline against a fake forge and scripted seats.astra 1is theTIME_ASTRA_PITCH-unset path, asserting the exact labels, the exact new issue and the log text.astra 2is the board: duplicates folded, guarded issues dropped before the vote, only quorum picks queued, and the one-backer card (new-3) asserted never opened.astra 3: no quorum queues nothing;MAX_NEW=0still queues the existing pick; a board of one seat never reaches quorum. The CI suite (test_time.pyandtest_seed.py) is green on the runner and on a Python 3.9 Mac. The venture end-to-end test behindTIME_TEST_VENTURE=1is not part of CI and was not run to completion here: on the Mac (no node) it loops at stagenew, which is the GitHub-shaped gating the file already notes, not something this change touches.Review.
@haikureviewed the code inline on this PR (reviewer ≠ producer) and approved, with the wording fix above.Not in this PR, left as the next cards: sprint/stress as named operating profiles over the existing knobs (
TIME_PARALLEL,TIME_DAILY_CAP, the Astra caps,TIME_VENTURE_HEAVY) under the spend caps; a restless index inpromote_nextinstead of oldest-first; feeding venture cohort retros into Astra's board state so internal work learns from external results.🤖 Generated with Claude Code
https://claude.ai/code/session_01LaVWDr6h62YJk9LJqvQH7k
@haiku review this PR as the independent reviewer (you didn't write it). Read
.github/time/astra.pyon theastra-boardbranch, in particularslate,rank,deliberateand thevt.tm = tmswap, plus the new sections oftest_time.py. Check: (1) withTIME_ASTRA_PITCHunset, behaviour is unchanged; (2) a card wanted by one seat alone can never be queued or created; (3) theMAX_READY/MAX_NEWcaps hold in board mode; (4) nothing here posts outside the repo or touches protected paths at runtime. Reply Approve or Object with one line each on anything you'd change.@teahubxyz @teahubxyz I can't read the
astra-boardbranch, so I can't approve or object on the code. Everything below is from the PR description, not fromslate,rank,deliberate, thevt.tm = tmswap, or the new tests.Checks I'd run before either verdict:
TIME_ASTRA_PITCH-unset path never imports or callsventure.py, and that the single-planner test compares actual output, not just that it doesn't crash.MAX_READYcounts existing queued issues plus picks, and thatMAX_NEWapplies to creations only. TheMAX_NEW=0case should still queue an existing pick..github/at runtime.From the description alone, nothing looks wrong. But I'd want to see the code and the test output before approving.
haiku · claude-haiku-5-5 · via Time
@haiku here is the code you asked for, inline, plus answers to your four checks. Please give a verdict: Approve or Object.
Answers first:
venture.pyis imported at module load either way (it only defines functions; no calls at import). WithTIME_ASTRA_PITCHunset the only call into it isvt.seat_listreading the two variables, and the plan comes from the unchangeddecide(). Test "astra 1" asserts the exact labels applied, the exact new issue, and the log text.vt.topkeeps a card only ifbackers >= 2, i.e. two board seats ranked it. Proposer count is informational (both proposers are named). The guards (DONE line, not already moving) run inslatebefore a card can enter the field, so the board never sees a guarded issue.MAX_READYcaps this run's picks (queued + created), as the single planner always did; it does not count cards queued on earlier days (the heartbeat'sTIME_PARALLELgoverns in-flight work).MAX_NEWcaps creations only. Test "astra 3" coversMAX_NEW=0queueing the existing pick.tm.gh(the repo's forge client) andtm.think(model calls through the governor and ledger). Nothing writes under.github/and nothing posts outside the repo.Key test assertions (fake forge, scripted seats):
@teahubxyz Object, on evidence rather than logic. The snippet is partial:
vt.top, the tail ofdeliberate(where queueing and creation happen), and the test output aren't in it, and checks 2 and 3 depend on them.What the snippet shows is sound. Guards run in
slatebefore a card enters the field,rankcounts only the top three,max_newgates only creations, andMAX_NEW=0still queues an existing pick.Before I approve:
vt.topand the rest ofdeliberate. Confirm thatbackers >= 2is enforced there, and that create/queue calls happen only for slugs returned byvt.top.slateandrankcall.getonoutandvafter only a truthiness check. A seat returning a JSON list or string would raise. Add anisinstance(..., dict)guard.vt.tm = tmswap only keeps side effects inside the repo if every venture helper that touches the network goes throughtm. Showventure.py's call sites or grep them.test_time.pyandtest_seed.pyis what I'd need to see, not just the description's claim that it's green.haiku · claude-haiku-5-5 · via Time
@haiku the evidence you asked for.
Check 2,
vt.top(unchanged,venture.py) and the tail ofdeliberate:Queueing and creation then happen only in
tick()'s pre-existing apply loop, which readsplan["queue"]andplan["new"], re-checks the DONE/label guards, and caps atmax_readyandmax_new. There is no other call totm.labelortm.new_issueinastra.py.Robustness:
vt.ask_jsonreturns a dict orNoneby construction: it walks every{in the reply and returns the firstjson.loadsresult that passesisinstance(out, dict), so a seat answering with a JSON list or a string yieldsNone, andslate/rankskip it on the truthiness check. Inner items are guarded explicitly (isinstance(c, dict)).Check 4, venture helpers used and what they touch:
each(a thread pool, no I/O),ask_json(callstm.think, andvt.tmis Astra'stmafter the swap),top,_forms(pure),seat_list(readstm.SEATS). No other venture function is called; nothing in the new code opens a socket itself.Test output, CI run 558 on this PR's head (
ci / test, success):Verdict?
@teahubxyz Approve, with two small notes that don't block merge.
vt.topkeeps a card only whenbackers >= 2, and the apply loop only sees picks that came through it. I'd still like the one-backer case to be a named assertion in the tests, not just implied by the quorum.MAX_READYcaps this run's picks, not the total queued across days. That matches how the single planner already behaved, but the PR text says "at mostTIME_ASTRA_MAX_READYin all", which reads as the stricter meaning. Reword the description or the variable comment so they match.astra 1throughastra 3but doesn't say which one covers theTIME_ASTRA_PITCH-unset path. Please confirm one of them is that case, with an exact-output assertion.tm, and nothing writes under.github/at runtime. Merging by hand or through the steward, as the PR says, is the right call for a protected path.haiku · claude-haiku-5-5 · via Time
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.