Team mode
BOARD_SYNC makes origin/main the truth and every board a converging replica: moves commit and push themselves, and a beat pulls what the other boards published.
BOARD_SYNC=1 makes origin/main the truth and every board a converging
replica. It implies BOARD_COMMIT_MOVES — a move that never commits has
nothing to publish — and off (the default) nothing below runs: no fetch,
no push, no thread, no behaviour change at all.
- Push is event-driven. The commit a move makes is pushed as soon as it lands. A rejected push means another board got there first, so the board fetches, replays its own commits on top and pushes again.
- Pull is a beat. Every
BOARD_SYNC_INTERVAL(30s by default) and once at startup: fetch, then fast-forward. The watcher narrates what arrived, attributed to the commit's author rather thandisk. - Losing a race is a toast, not a mystery. When replaying collides
with a card someone else already moved, origin wins: the local move is
dropped, the file reverts to origin's version and the board says
07 claimed by elena — your move was undone. - A human's unpushed commit is never published. Before any auto-push
every local-ahead commit on
mainmust beboard:-prefixed. One that isn't stops the push (and the replay), names itself in the ticker and holds the header'ssync stalledchip until you push it yourself or move it off main. - Offline is quiet. An unreachable origin says so once, then works
locally; commits queue on
mainand go out on the next reachable fetch. - Which remote is one answer, not two. One remote and one branch, by
design — but the remote is the one
BOARD_GIT_REMOTEnames, else the checkout's first, resolved in the same place PR opening asks, so the two halves of team mode can never publish to different places. It is used as named: aBOARD_GIT_REMOTEthis checkout has no remote for stalls saying so rather than reaching past it for another one. A checkout with no remote at all stalls the same way, from startup — team mode syncing nothing is exactly the state a board must not render as healthy, and it is the likeliest first state of a fresh installation. Add a remote (or set the setting) and the chip clears with a line saying sync is converging again.
State syncs; reactions don't
The board does not only render state, it reacts to it: a card entering
review opens a PR. With N replicas watching one truth, a reaction must
fire on exactly one of them, so only the board whose user made the move
acts on it. A move a pull applied renders and narrates — attributed to
its author — and triggers nothing. watch.py answers the question, since
that is where the attribution already lives, and every future automation
hung off a stage transition inherits it: am I the actor?
The file-carried gates stay in place behind that rule, so the rare double
is harmless rather than loud: the **PR:** line before gh pr create
(and a create that races anyway adopts the open PR), an existing branch
and worktree before a work launch. Both layers, deliberately — the
actor-only rule prevents the duplication, idempotency survives it.
Two consequences you can see:
- A half-done side effect is nobody's to finish automatically. The actor's board can die between moving a card and opening its PR; no other board picks that up, and in team mode the startup catch-up stands down for the same reason. The card wears ↑ open PR instead — a person decides.
- Ownership gates work launches. A card someone else holds refuses ▸ start work, naming them, and offers ▸ take over as the deliberate second path. Shared liveness is not part of this: a teammate's running agent is a static "in-progress, assigned to them" on your board, because agent registries stay in each board's own memory.
Two disciplines make this safe, and team mode assumes both:
- Local
mainadvances only through the board and origin. Code work lives in worktrees and PRs — that is what keeps the main checkout clean and fast-forwardable. Uncommitted changes to tracked files, a checkout sitting on another branch, or a divergence the guard won't replay all stall sync rather than risking your work; each one is narrated once and shown as a chip in the header until it clears. - Sync never merges. It fast-forwards, or rebases the board's own bookkeeping commits. Nothing here force-pushes, and nothing reacts to what it pulled beyond narrating it.
One board fetches twice a minute at the default interval; N boards make
N times that. Against GitHub this is nothing, but on a rate-limited or
metered remote raise BOARD_SYNC_INTERVAL.