Commit Graph
266 Commits
Author SHA1 Message Date
istos ba3b050c40 board: 50 → review (istos) 2026-08-01 10:33:03 +02:00
istos 646db08b6e board: 50 Work report filed (istos) 2026-08-01 10:33:03 +02:00
istos 7198c17332 board: 49 → done (istos) 2026-08-01 10:04:38 +02:00
Ronald AshriandGitHub d2f7ddd4bf Merge pull request #41 from 12vectors/task/49-a-phase-runs-itself-on-its-own-branch
49 — A phase runs itself, on a branch of its own
2026-08-01 10:04:35 +02:00
istos 2be30456e9 board: 49 PR update filed (istos) 2026-08-01 10:03:29 +02:00
istosandClaude Opus 4.8 1f8f5f6fab Address Copilot review: no-identity beat, unwritable log, remote phase base
Three points from the PR #41 Copilot review, each a robustness gap on a path
the happy case never takes:

- phases._mine() gated the beat on the assignee even when this checkout has
  no git name — but agents.claim_for_launch() cannot gate a launch there and
  lets it through, so a phase could start (branch cut, run recorded) and then
  advance nowhere. _mine() now treats "no local identity" as the lone actor,
  matching the launch it mirrors.

- phases._record() ignored whether the log line landed. The log is the
  durable memory a restart reads to tell "already started" from "not reached
  yet"; a launch or merge with no line behind it is what a restart repeats.
  _record() now raises _Halt when the write fails — before the action it was
  meant to precede — split from a best-effort _write_log() the halt path and
  _start() use so recording a halt can never itself raise.

- github._pr_base() switched a member PR's base to the phase branch only when
  it existed locally. A board that did not run the phase knows it only through
  the remote (sync fetches origin/main and nothing else), so _pr_base() now
  also honours a phase branch the remote carries, and _open_pr() only pushes
  the base when it is a local branch.

Four new tests in tests/test_phase_runs.py cover each. Full suite green (740).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-01 10:03:08 +02:00
istos df28b6aec6 board: 50 → in-progress (istos) 2026-08-01 09:59:25 +02:00
istos da01ef6c1f board: 49 PR opened (istos) 2026-08-01 09:46:12 +02:00
istos 5621fcb144 board: 49 → review (istos) 2026-08-01 09:44:37 +02:00
istos 89e21753de board: 49 Work report filed (istos) 2026-08-01 09:44:37 +02:00
istosandClaude Opus 5 35c83f5f70 Say what "the board never merges" always meant
It meant main. A phase branch is the board's own, and merging into it
is bookkeeping in the same family as committing a move — so the
absolute in AGENTS.md, and the same claim on the landing page, both
become "never merges into main". Nothing about the human gate changes;
the sentence just stops overstating it.

The rest is the section the runner needs: how a phase is started, why
the branch exists at all, what makes the beat stateless, what green
means and what halts, and who runs it when there are replicas.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 09:44:02 +02:00
istosandClaude Opus 5 da8984d0e6 A phase runs itself, on a branch of its own
Starting a phase cuts phase/<stem> from the newest main it can see and
works the list into it: each member branched from the phase's tip, run
headless, merged back when its checks are green, the next one started.
At the end one PR into main, for a human. The human gate moves from
every card to the phase boundary, and the promise survives: the board
merges into a branch it created, inside a scope you opened.

The runner is a beat, not an agent — everything it decides is already
structured state, and an agent paid to poll would be the wrong tool at
the wrong price. It holds no registry of where a phase is. Two durable
things carry the memory, and the board already writes both: git, where
a member is finished when its branch is contained in the phase branch,
and the card, which grows a ## Phase log the runner adds one line to
per decision. The log is what tells "this member has run and it ended
badly" from "the phase has not reached it yet" — without it a
restarted board would relaunch a run that died.

Containment alone is not enough to call a member merged: a clean exit
that committed nothing leaves an empty branch that is contained. The
card has to have settled into review/ too, or a broken launch would
hide exactly where it always tries to.

Five conditions halt, each already a visible state on the card, and a
halt is written once and then held. Running the phase again is the
person's decision and is what clears it — the run is scoped to its own
log line, so a member whose run died is launchable again. A dependency
that has not landed is a wait, not a halt.

Merges are additive throughout: main into the phase branch on every
beat so a long run does not drift into one enormous conflict, members
into it as they go green, nothing rebased and nothing force-pushed. A
conflict aborts, leaves the branch as it was, and halts naming the
files that collided.

The actor rule decides who runs it, written where it already lives:
the phase card's assignee. A replica renders the phase and advances
nothing. Reachable through /api/phase/run and the ticker; the header
chip and the card actions are a separate card.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 09:43:57 +02:00
istosandClaude Opus 5 af24a3fb39 A card's branch may be a phase's, and a member's PR is based on it
github asked one question in four places by writing task/<stem> out
each time. It asks branch_of() now, which answers phase/<stem> for a
phase card — so from review/ onwards a phase is an ordinary card: its
PR opens, its worktree is driven, and merge & clean up finds the
branch it is meant to take apart.

A phase member's PR is opened against its phase's branch rather than
main. Its branch was cut from there, so that is the only base whose
diff is the member's own work — and a PR into main carrying a whole
phase would invite exactly the merge this design refuses to make. The
base is published first, so the remote has something to open against;
the main-is-ahead guard still stands in front of every PR into main.

A phase's own PR says what is in it: the member list, in run order.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 09:43:45 +02:00
istosandClaude Opus 5 ecde8abb53 A member of a phase branches from the phase, not from main
Two pieces the runner needs, in the modules that own them.

taskfiles grows a third door: append_to_section, one line under one
heading. The phase log is a running record, and append_to_task would
scatter its lines through the file as other sections landed between
them — the record would stop being readable in the one place a person
looks. It commits like every other board-made write.

agents learns where a phase member starts. That is the whole reason a
phase has a branch: related cards run one after another, so card two
branched from main could not see card one's work while card one sat
unmerged in review/ — it would conflict, or quietly build the same
thing twice. A card in no phase, or one whose phase has not been
started, takes the ordinary fresh branch point, and the ticker names
the unusual base as it already does.

claim_for_launch loses its underscore: a phase run claims its card the
same way starting work on one does, from another module.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 09:43:35 +02:00
istos 4bccbd4a4c board: 48 → done (istos) 2026-08-01 09:12:34 +02:00
Ronald AshriandGitHub c80f23a313 Merge pull request #40 from 12vectors/task/48-a-phase-is-a-card-that-lists-its-cards
48 — A phase is a card that lists its cards
2026-08-01 09:12:27 +02:00
istos 83de3820df board: 48 PR review filed (istos) 2026-08-01 09:05:57 +02:00
istos 9d4cf30c4c board: 48 PR opened (istos) 2026-08-01 09:02:17 +02:00
istos a76db5efcd board: 49 → in-progress (istos) 2026-08-01 09:02:15 +02:00
istos d5ea992618 board: 48 → review (istos) 2026-08-01 09:02:04 +02:00
istos c017639165 board: 48 Work report filed (istos) 2026-08-01 09:02:04 +02:00
istosandClaude Opus 5 9621437479 A phase is a card that lists its cards
`**Type:** Phase` plus a `## Cards` section is the whole model. The phase
card is the only authority on membership: it lists its members in run
order, and a member's phase and position are derived from that list
rather than stored on the member — one direction, one place to edit.

- taskfiles.py reads the section (numbers only; `31`, `#31` and `031` are
  one card, indented lines are a member's own continuation) and parses
  `Depends on` at last, into the numbers it names. Nothing acts on it:
  the list says what runs next, the line says whether it may.
- collect() weaves the board: members resolved in document order, each
  member given its phase and 3/5 position. What will not resolve is
  flagged rather than skipped — a number no card has, a card two phases
  both list (both flagged), a card listed twice, a line naming no number,
  a phase listed by a phase.
- board.html gives a member card a `⟶ <phase> 3/5` chip in the footer
  row beside CI and PR ↗, opening the phase card, and wears phase drift
  as a pill next to status drift.

Nothing runs yet — no branch, no launches, no advancing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 09:01:37 +02:00
istos b4f1282ab4 tasks: card 52 — a redraw throws away where you were looking
renderBoard() wipes #board and rebuilds every column, so each .drop is a
new node with scrollTop 0 — and render() runs on every SSE frame, so the
board resets under you continuously while agents work. The log already
solves this for itself with S.logStick; the columns never got it.
2026-08-01 09:00:43 +02:00
istos 146751c77f board: 48 → in-progress (istos) 2026-08-01 08:48:11 +02:00
istos a7096c0280 board: 51 → to-do (istos) 2026-08-01 08:48:03 +02:00
istos 35a4f1f36c board: 50 → to-do (istos) 2026-08-01 08:47:55 +02:00
istos 19717a30bd board: 49 → to-do (istos) 2026-08-01 08:47:44 +02:00
istos 0eee8d87a3 board: 48 → to-do (istos) 2026-08-01 08:47:36 +02:00
istos 9f4b3cae7f board: 37 → done (istos) 2026-08-01 08:47:23 +02:00
Ronald AshriandGitHub 5d3f22717f Merge pull request #39 from 12vectors/task/37-sync-resolves-its-remote-and-says-when-it-cannot
37 — Sync resolves its remote like the rest of the board, and says so when it cannot
2026-08-01 08:47:15 +02:00
istos 88c625188e board: 28 → done (istos) 2026-08-01 08:47:11 +02:00
Ronald AshriandGitHub 9e0ae30f33 Merge pull request #38 from 12vectors/task/28-a-pinned-port-stays-pinned
28 — A pinned port stays pinned: fix the TIME_WAIT hop that rewrites .env
2026-08-01 08:47:07 +02:00
istos 0224f3a73e tasks: phases — 48 the model, 49 the runner, 50 the UI, 51 the action
A phase is a card with Type: Phase and a ## Cards section listing its
members in run order; members carry Depends on, which guards rather than
orders. It runs on its own branch, each member branched from the phase
tip so card two sees card one's work, merged back on green, one PR into
main at the end — the human gate moves from every card to the phase
boundary, and 'the board never merges' stays true of main.
2026-08-01 08:46:09 +02:00
istos de4e7dc762 board: 37 PR opened (istos) 2026-08-01 08:03:03 +02:00
istos 97263c3691 board: 28 PR opened (istos) 2026-08-01 08:02:59 +02:00
istos 11e2f61ac7 tasks: 47 — 44 is done, so say so
The card warned that archiving does not commit and suggested sequencing
behind card 44. That landed before 0.3-alpha: archive_task goes out
through _relocate, attributed and committed under the same gate a move
is. Nothing blocks 47.
2026-08-01 07:43:01 +02:00
istos 51196cd06c tasks: card 47 — an archive button on the card
The tray works but the gesture is a drag across the whole board. Puts
the same glyph on the card's footer row, on backlog/to-do/done only,
arming on the first click as the other two-step actions do.
2026-08-01 07:39:04 +02:00
istos 038d258cb2 board: 37 → review (istos) 2026-08-01 07:35:57 +02:00
istos d57d3036fd board: 37 Work report filed (istos) 2026-08-01 07:35:57 +02:00
istos 7a27201dff board: 28 → review (istos) 2026-08-01 07:35:25 +02:00
istos f4796485c0 board: 28 Work report filed (istos) 2026-08-01 07:35:24 +02:00
istosandClaude Opus 5 58b658389b sync: ride the resolved remote, and say when there is none
REMOTE/UPSTREAM were module constants, so a team-mode board whose remote
is named anything but origin synced nothing at all — silently, with a
healthy header. The remote is now resolved per use (config's answer, the
one PRs already honour) and threaded through fetch, publish, replay and
integrate; every message names whatever was resolved.

The silence is the other half. Where _converge()/push_now() returned a
bare 'no-origin' that both callers discarded, _remote() now _note()s at
stalled level first: no remote at all names both fixes (add one, or set
BOARD_GIT_REMOTE), and a BOARD_GIT_REMOTE naming a remote this checkout
does not have stalls naming it rather than quietly using another. It
clears with a recovery line like the offline path, and install() asks at
startup so the chip is there from first paint, not the second beat.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 07:33:39 +02:00
istosandClaude Opus 5 8194a043da config: one resolver for which remote this board rides
github.remote() decided it for PRs; sync assumed 'origin'. The answer
moves to config, which already owns BOARD_GIT_REMOTE, resolved on demand
so config still shells out nothing at import. github.remote() becomes a
thin call to it, and git_remotes() lets a caller check whether the name
resolves to a remote that exists rather than falling back silently.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 07:33:32 +02:00
istosandClaude Opus 5 6829136f42 start: a pinned port stays pinned across a restart
The free-port probe bound without SO_REUSEADDR while the board's own
ThreadingHTTPServer sets it, so the socket a just-stopped board left in
TIME_WAIT read as "taken by something else": a routine stop/start walked
the board to the next port and wrote that over the user's BOARD_PORT pin.
The probe now binds exactly as the server does, which is the whole race.

Behind it, a held port gets a few seconds (BOARD_PORT_WAIT, 5s) to clear
before the walk, re-asking is_our_board each beat — a restart races its
own predecessor far more often than a stranger takes the port. Walking
off a pinned port still persists, since the hooks and agents read
BOARD_PORT and must reach the live board, but it now says so in full:
the right file (manager/local/.env, not manager/.env), old → new, and
how to reclaim the pin.

Tested end to end against a scratch host with a stub board.py, over real
sockets: a genuine TIME_WAIT remnant, a listener that lets go mid-wait,
a listener that does not, and our own board answering.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 07:32:54 +02:00
istos 57b3073496 board: 37 → in-progress (istos) 2026-08-01 07:23:10 +02:00
istos adec6a3c37 board: 28 → in-progress (istos) 2026-08-01 07:21:58 +02:00
istos 727354487b README: the docs site, and the board it documents
A link to bench.12vectors.com under the opening paragraph, and the board
screenshot beneath it — the fastest way to say what this is.

The image is an absolute raw.githubusercontent URL rather than a repo
path, for two reasons. README.md ships in the release artifact and
site/ deliberately does not, so a relative link would break in every
vendored install. And the site's own copy is served under an immutable
year-long cache, so linking that would pin readers to whatever
screenshot was current when they first loaded the page.

The three-layer-law paragraph gains a clause and loses one. That section
is sliced into /concepts/three-layer-law/, so "or read them at
bench.12vectors.com" rendered as a page on that site telling you to go
to that site. It now says AGENTS.md is the file the docs site is cut
from, which is true in both places — the rule this repo sets for its own
prose.
2026-07-31 17:42:52 +02:00
istos 4660d0cb34 0.3-alpha
And un-pin the landing page's version test: it named 0.2-alpha as the
string to replace in VERSION, so the first bump after it was written
failed the suite. It reads the file now, which is what the test is about
in the first place.
v0.3-alpha
2026-07-31 17:28:20 +02:00
istos b031711e60 board: 44 → done (istos) 2026-07-31 17:22:33 +02:00
istos 22215753c6 board: 38 → done (istos) 2026-07-31 17:22:33 +02:00