Run the merge queue on a state-driven coordinator

This commit is contained in:
Ian Gulliver
2026-10-21 11:28:07 -07:00
parent a971cce9d6
commit 7fbdf1669a
13 changed files with 509 additions and 561 deletions
+6
View File
@@ -84,6 +84,12 @@ Vendor setup and protocol quirks are in [Claude Code](claude-protocol.md), [Code
- An audit checks only the newest commit of the default branch, one at a time per project, so it doesn't run on every revision but the newest one is always audited eventually. byre never fetches for an audit, so a commit pushed outside byre is audited only once a byre merge has fetched it.
- A team task that comes up with some members failing to start isn't repaired; discard it and create it again.
## Coordination
Merges to the default branch run on a coordinator: a single goroutine (`internal/agents/coordinator.go`) that every stored event wakes, and that decides only from the database's current state, never from which event woke it. Its decisions run inside one store write transaction, so no other write lands while it decides. Slow work (git, starting a turn) runs after that transaction, and shows in state while it runs (a session `running`, a task `finalizing`), so the next pass doesn't start it twice. Startup is just one more pass, with no separate recovery code. Other task progress (discards, reviews, nudges, workflow stages, lead delivery) is driven by the events themselves.
A project's merge slot is held by a task with `merging_by`, `finalizing` or `pushing_to_main` set. A Merge request takes the slot at once when it's free and nothing is queued, else queues (`merge_queued_by`). Each pass drops a merge whose turn ended without finalizing, gives a free slot to the oldest queued task, starts a claimed merge turn (`merge_after_turn`) once none of the task's agents is mid-turn, and lands each finalizing merge. A session that hands off to a fresh context stays `running` until the new session takes over, so no pass sees the task idle in between.
## git
- Merge notes live under `refs/notes/byre`. byre adds the fetch refspec `refs/notes/byre*:refs/notes/byre*` to each checkout; it's a pattern because git refuses a fetch whose exact refspec names a ref the remote doesn't have yet, as it doesn't before the first note is pushed. Another clone adds it with `git config --add remote.origin.fetch 'refs/notes/byre*:refs/notes/byre*'`; `git log --notes=byre` shows the notes.