Pool and publish rules. Shared rules (autonomy, git identity, CI read, collisions, exit): gm. Recovery: docs/recovery.md. Incidents: docs/incidents.md.
ceiling = host spawn limit (N in a refusal), not a throughput target or memory budget. live = ListAgents count. floor = min(2, ceiling). shortfall = live < floor with independent work open.
Standing pair (every run)
Every /gm run starts one Workflow, skills/gm/workflows/standing-pair.js, in its first turn, before any other fan-out. Three phases, in this order of precedence:
- Drain, concurrently. Lenses file findings as PRD rows. One lane per file group resolves each row's unknowns with live runs, edits, commits by explicit path, pushes, and closes the row with a live witness. Rows drain as they are filed: each row starts editing the moment its finder reports, and rows on the same file group run in sequence while every other group runs in parallel. Concurrency is set by the open work, never by a fixed number: any free slot with work open is filled in the same turn. Nothing in this phase waits on CI or tests.
- Validate and fix, after commit. Once a batch of rollout work is committed and pushed, the test and CI follow-up validates that batch: it reads
ci-statusfor each pushed sha, debugs red jobs withci_log failed_only, and fixes them on their file groups. Red jobs become rows the next drain takes. Validation never runs on uncommitted work. - Traverse, when nearly drained. When open rows fall under 20 and slots are free, node traversal (gm-hop over
skills/dream-rsi/gm-graph.json) fills those slots, alongside phases 1 and 2. Each hop files its rows withprd-add. Traversal never takes a slot that drain or validation needs. A row attempted in a run is not retried in that run; a blocked row is retried only after its blocker changes. A run that closes no row is stalled: the dominant blocker is written to.gm/witness-log.md, and the next run waits for that blocker to change. There is no agent-count cap. A run ends when no open row, no red sha and no unvisited node remains.
Core loop
Repeat until a full pass adds no work:
- Drain concurrently. Rollout lanes resolve, commit and publish open rows as they are filed.
- Validate after commit. The test follow-up validates each committed batch and turns red jobs into fix rows on their file groups.
- Sweep first: one subagent per failing job log, changed file group, stub/TODO. Each returns located
path:lineand counts only; each finding becomes a row. Fix only after the sweep completes. - One fix executor per independent file group; fixes sharing a file run in sequence in one executor. One commit per group, never per finding.
- Sweep first: one subagent per failing job log, changed file group, stub/TODO. Each returns located
- Traverse when nearly drained. At fewer than 20 open rows, graph hops fill free slots and file new rows. They run alongside steps 1 and 2.
- Repeat from 1.
Parallelism: graph workflows
- Fan-out unit: a
Workflowgraph. As many as open work needs; parallel where independent, sequential where a result feeds the next. - Drainage strategy: drain the remaining PRDs in bounded slices, each slice run as its own dynamic
Workflowgraph. Take the first slice of at most 20 pending rows (prd_list {status:"pending", limit:20}), run onegm-prdper row, return{pending_before, resolved, blocked, failed, new_blockers}, then take the next slice from a fresh listing. Refusals and stalls are answered by the next slice, never by stopping. A slice that resolves nothing is re-run once with its blockers split, then the next slice starts. - One wave per run, except the standing pair (Standing pair), which loops until its listing is empty. Any other run takes at most one slice (20 pending rows), resolves them with
gm-prdagents (connectivity restarts per Connectivity restarts), returns{pending_before, resolved, blocked, failed, new_blockers}and ends.pending_after > pending_beforehalts new blockers until a merge pass closes duplicates. - A run never exceeds 150 agents. Cap-hit or stalled runs are reported, never relaunched unchanged.
- Three graphs, each run to exhaustion:
- Row drain:
pipelineover pending rows, onegm-prdper row, aparallelblock per wave; until pending = 0. - Traversal:
pipelineover every principle node inskills/dream-rsi/gm-graph.json, onegm-hopper node per surface, emitting rows; until every node has a receipt per surface. - Audit: a completeness critic; findings become rows for graph 1.
- Row drain:
- Drain before make. Pending ≥ 20: graph 1 takes every free slot. Pending < 20: graph 2 takes free slots (computer-science disciplines first) until pending reaches 20.
- Repeat 1-3 until a full cycle adds and resolves nothing. A wave resolving 0 rows with pending rows left is a stall: change the brief, rerun. Never stop on a stall alone.
- Ceiling and floor bound live subagents (Lane budget). Refill a freed slot in the same turn.
Publish first, test concurrently
- Publish first: each rollout lane publishes its commit without waiting on CI or browser tests. Tests, CI and CD run in the concurrent test follow-up, never as a gate on rollout.
- Fix after commit: the test follow-up validates committed batches, groups failures by cause and file, and hands each group to the lane that owns that file, or to a new fix lane when no lane holds it. Only one writer per file group at a time.
- Witness: a rollout lane closes its own row with a live witness dispatch on its pushed sha. The test follow-up reads CI for that sha; a red job reopens the row with a
prd-addfix row, which the next pass takes. - No
cargo,go build,go vet,go test, or local compile unless the user turns builds on. Check that setting first (gm config or served instruction); a local run is allowed when it is on. Witnesses are live dispatches, which never wait on CI. The test follow-up readsci-statusfor each pushed sha. A failing sha becomes a fix slice, never a pause. - Writers are light; writers are not limited. Only browser arms and per-run servers cause thrash (Lane budget). Many writers may run when each owns separate files.
- File ownership before launch: each brief names its files. Two lanes never write one file. A shared manifest has one owner; others add only their own directory.
- Contracts before code: a producer publishes its contract doc as its own commit first; the consumer reads it from remote
main. - Use maintained libraries under permissive licences for solved problems. Pick the simplest correct path a lane finishes in one pass. Split a module across lanes only at a file boundary in the brief.
- Rewrites of published history need the owner's approval (gm, Section 4). Lanes append; they never rewrite.
Run ledger
.gm/pool/runs.jsonl: one JSON line per run, append-only; the last line perrunid is its state.- Fields:
run(<parent>-<kind><k>),kind(exec,verify,ci,fix,hop),roworsurface,sha(full, when known),state(launched,handoff,verified,ci-green,ci-red,fix,blocked,done,stale),session,started,updated(UTC ISO-8601). - Every transition writes its line in the same turn. Never rely on a lane's own log for state.
- Shared append logs (
.gm/pool/ledger-<wave>.txt,.gm/witness-log.md,.gm/lean-walk/new-nodes.jsonl) have many writers: each line goes byfs_write {"path":"<path>","content":"<line>\n","append":true}. A whole-file write to these paths is refused (append_only_rewrite_refused), so a concurrent lane's line is never dropped. - Each tick, before
pool-observe: outstanding = last state notdone,blocked,stale. Closable only whenverifyisverifiedandciisci-greenfor the samesha. - Reconcile: outstanding
cirun:ci-status {sha}replaces its conclusion.exec/verifyrun whose.gm/pool/<session>.liveis older than 5 minutes is markedstale; relaunch its row fresh; never count stale as live. - A
handoffwith no followingverifyorcirun is a defect: spawn the missing lane this tick. - Summaries (outstanding, closable, stale, red) are derived each tick, never stored.
Verification and CI lanes
- Executor ends
STATUS: handoff <sha>. Orchestrator spawns, same turn, outside the executor: verifier (gm-validateasmode=witness, session<parent>-v<k>, a session other than the executor's) and CI reader (gm-publishsteps 3-5, session<parent>-c<k>). STATUS: fix(verifier) orSTATUS: ci-red(CI reader) starts a fresh executor for the same row with the sha and failing check or job. Takes a slot; never inline.- The rollout lane that made the edit calls
prd-resolvewith its live witness. The test follow-up's CI read for that sha decides whether the row stays closed; a red job reopens it. Log each lane result to.gm/witness-log.mdwith its session id. - Verification and CI never block the pool.
Floor and dip
- Pending rows open: keep live ≥ floor. Pass ListAgents count as
body.liveon everypool-observeandtransition. floor_gate_denied(live < floor,open_rows > 0, launchable rows advertised but not launched) is the launch signal, never a retry.state: supply_exhausted(ok, no error) onpool-observe: no open row is launchable and no traversal node is due.held_rowsare running and are the only supply: wait for their completion, then re-observe. Never retry-loop.- Live under ceiling with independent work open is a shortfall: log
FAILURE: <UTC> live=<n> pending=<m>to.gm/witness-log.md, then launch to target this turn. Causes:- Lane stopped: one successor per freed slot, never batched. Session killed: relaunch
.gm/pool/<session>.nextor a pending row. - Lane stood down on a modified file: compare the blob with remote
mainfirst; no comparison, take another slice. - Browser or GPU lock held: run node-only and non-GPU work meanwhile.
Prompt is too long: relaunch compact (row, surface, session, one action). Never resume.
- Lane stopped: one successor per freed slot, never batched. Session killed: relaunch
Tick
pool-observe {live, held}. Takeslots.launch(node-first, max 32). Empty under shortfall is a defect row, unlessstateissupply_exhausted.- Launch one block, up to ceiling. Rows:
Skill(skill="gm-prd", args="row=<id>; session=<SID>"). Hops:Skill(skill="gm-hop", args='{"node":{...},"principle":"<id>","surface":"<path>","session":"<SID>","wave":"<wave>","depth":1,"rhetoric":""}').<SID>=<parent>-<k>, unique. A cause-asserting row or hop runs its ACH step (gm-hop 3a, gm-prd SPECIFY) in the same launch. - Append (
fs_writewithappend:true)launch <id>to.gm/pool/ledger-<wave>.txt, alaunchedline to.gm/pool/runs.jsonl,tick <n> event=<id> live=<count> <UTC>to.gm/witness-log.md. wait {"ms":60000}. Eachdone <id>refills its slot this turn. Re-count live after each launch and completion.
Launch only advertised ids. A slot stays empty only when no pending row and no untraversed node remain.
Hop payloads
- A hop is an edge: nominate FROM its node TO a neighbour in
node.links. - Read
skills/dream-rsi/gm-graph.jsonat launch. Never take counts fromnode_countoredge_count. node:id,label,book,author,kindfromnodes[].advocate="<author> - <book>", or barebookwhenauthorisunattributed.links: one{to, guards}peredges[]entry withfrom=node.id, both kinds, sorted byto.guards = []whenconditionis null, else[condition].tonot innodes[]: skip;prd-addgm-graph-dangling-edge-<from>-<to>.links: []still launches; receipt saysnext: none. Zero links on launch is a derivation defect.- Receipt
next:outsidelinks: lognomination-not-from-links;prd-addgm-hop-nomination-not-from-links-<from>-<to>. - Witnesses are CI witnesses: the row's pushed sha, read through
ci-status. A row needing a local compile is written with its CI run as witness.
Refill and successors
- Each completion refills one slot this turn from
slots.candidates. Blocker rows are never candidates; a worker ending at a blocker nominates a node-only successor. - A hop spawns its own successor; the orchestrator spawns one only when a hop returns
next: none. - Candidates empty with rows pending:
prd-list {status:"pending", limit:<n>}; log the empty list as a defect row. - Rows resolve faster than the pool refills: pause row-creating hops. Row supply low: run row-creating hops before more resolvers.
- Each completed hop writes
.gm/lean-walk/<node.id>.<session>.json. Missing:prd-addgm-lean-walk-node-emission-missing-<session>; not counted walked. Count of record isemitted.
Heartbeat
- Every brief first writes
.gm/pool/<session>.live(session,row,startISO-8601 UTC; a hop'srowis its surface path) and deletes it on finish. A surface-only heartbeat ismalformed_heartbeats. - Skip any id held by a live row before launch. Older than 5 minutes = stale.
Shortfall and FAILURE
- Two restarts per walk. Repeated shortfall means these rules are wrong: dispatch
instruction, correct this skill, restart at tick 1. - A third repeat files
<row>-blocker-<session>and launches the blocker next wave.
Never leave a row open
- A run ends as
prd-resolvewith a witness, or as blocked. Stalls, blocked workers, refused witnesses and ended sessions never close the walk. - Every gm call in every brief passes
cwd: "C:/dev/gm"(or the row's project root). A call withoutcwdreads another project: "row not found" or "pending 0" from a brief is a wrong-project defect, not a blocker. Re-check withcwdbefore any blocker is filed. - A resolver runs every stage of its row in its one run (orient through publish, then prd-resolve). Stopping after one stage with the row open is a defect: re-dispatch the same row once as
<session>-r<n>naming the stage reached, never a fresh orient. - A blocked row keeps its id and names
blocked_by: <fact and verb>in its text. File a new blocker row only when no open row names that fact. Counts are unique open rows. - Blockers launch next wave. A blocker that blocks again is split until one step remains; that step runs. No blocker-of-blocker past one level.
- Convergence guard: pending rows rise two waves running: no new blockers; run only open blockers and rows until the count falls.
- A worker returning with no receipt or blocker is re-dispatched once per wave as
<session>-r<n>. A connectivity-stopped worker is restarted (Connectivity restarts). floor_gate_deniedonprd-resolveor phase advance: passbody.liveand launch the denial'sslots.launch.
Decided work lands in the same turn
- A decision to do more work is executed in that turn. The orchestrator picks the most defensible default, records it as one line in
.gm/witness-log.md, and runs it. Ending a turn with a decision waiting on the user is a defect. - Only a Section 4 door (world-scoped, or one that reaches every session or production) is raised to the user, and it is raised as a final-message question while every other row keeps running. The orchestrator never blocks the whole pool on one.
- A live lock held by another lane (
store_busy,gm.db.lockowner alive) is never killed or removed by the orchestrator. Default: retry the store call withwait {"ms":60000}up to 5 times. Still held after that: every affected row takes the store-free path (codesearchliteral or filename,Readof located paths,git_*verbs), records the lock as its named fact, and is re-launched the moment the lock clears. No row waits idle on it. - A row whose acceptance needs a live write to shared served state (a prose override, a store row, a witness that changes every session) is run against a disposable key the orchestrator names in its own brief, with the key recorded in the receipt. Only a Section 4 door is not defaulted this way.
Starvation
pool-observedropped at 120 s withdispatch_starved_waiting_for_serial_lane: re-dispatch once, verbatim (neverresume_task). First read.gm/exec-spool/.dispatch-wait.json: position 1 or holder near budget: re-dispatch now; behind a fresh holder: wait; not shrinking: slow holder.- Commit ahead of push: wait. A push that waited out its budget keeps its place; re-dispatch once.
dispatch_orphanedon a read-only verb: re-dispatch at once. Before re-dispatchinggit_commit,git_finalize,git_pushorfs_write, re-read real state.- Never preempt an executing holder. Never delete a
.livefile to free a slot, except the stopped worker's own claim under Connectivity restarts. - Two starvations of one git verb:
prd-addthe paths and both dispatch ids, then stop git dispatches until a completion drains the queue.
Connectivity restarts
- A subagent stopped by
ECONNRESET,ECONNREFUSED,server_error, timeout or no response is restarted, never resumed, never a blocker. Fresh subagent, same row, session<session>-r<n>, same turn. Row stays open; slot stays held; never count it live. - Claim release before restart: the stopped worker's
.gm/pool/<session>.liveis deleted by the orchestrator once its error reply is read (the error proves it stopped), so the Heartbeat skip rule does not hold the row for 5 minutes. The restart writes its own.liveunder<session>-r<n>. Exactly one.liveper row at any time; no duplicate row is filed for the restart. wait {"ms":30000}between attempts while the error repeats. Logrestart <row> <n> <UTC>to.gm/witness-log.md.- Cap: at most 4 restarts per row (
<session>-r1to<session>-r4). After the 4th failure, the row stops being restarted and files a blocker row naming the last error and its dispatch id, so a dead endpoint cannot hold a slot forever. - Mass drop:
docs/recovery.md, "Mass API-error drain".
Headroom and leases
- Browser and server contention: Lane budget. A browser arm that cannot take the lease waits; node-only work continues. Log any wait with its cause and UTC; a wait does not end the turn.
- Rows needing an exclusive lock wait; the pool launches the rest. A timed-out lock wait is a failed run.
- One browser or GPU arm at a time, pool-wide;
gpulockserialises the adapter only. One browser arm plus one node-only lane is fine; two browser arms are not. Lease held: take node-only work; never open a second browser. - Browsers are CDP-driven and headful. Use the gm browser verbs (
crawl,chrome;skills/gm/docs/verbs.md) only. Never install or download puppeteer, playwright, chromium or any browser package or binary, and never launch a headless browser (lightpanda, obrowser,headlesslaunches are removed). A project's browser opens once and stays open: it closes only after 15 minutes with no lease holder or waiter, so runs never open and close it per call. Never touch the user's Chrome. - EADDRINUSE: the witness did not run. Identify the owning process. Never kill another lane's process.
- Browser-last, minimal surface, cold-start and reload budgets, live witnesses in place of new test files, counted units, loud refusals: gm-prd, Witness economy.
Lane budget (concurrency and thrash)
- Concurrency cap: 20 subagents (
ceiling, the host spawn limit). It is not a throughput target and not a memory or thrash budget. Launch up to it while independent work is open. - Thrash has two causes, and only these two:
- Multiple browser arms running at once. On 2026-10-14 several GPU browser arms ran together and the machine became unresponsive at roughly 10-16 lanes, needing a reboot.
- A new server plus bundle booted per run. Per-run servers leaked processes when a run hung.
- Target setup: one shared, HMR-capable dev server and client. Browser captures and probes attach to it and reuse it (page reload, or an HMR module edit). Never boot a new server and bundle per run.
- One browser arm at a time, pool-wide. Browser work queues behind
gpulock.gpulockserialises the GPU adapter only: it does not serialise CPU, memory or disk, and it is not a machine-wide limit. Node-only and non-GPU work continues while an arm waits. - A dropped connection is answered by restarting the lane (Connectivity restarts). Thrash is answered by fewer browser arms and shared servers. Neither is answered by a concurrency cap.
- No extra checkouts.
git archive <remote sha>read-only is the sanctioned clean base (gm, READ questions). - Publish on the first passing witness: time-box a lane to its witness, resolve, publish, keep going.
Fan-out
- Independent slices (file, row, hop) launch in one block. Slices sharing a surface run in turn.
- Walk workers are gm-prd or gm-hop, never a fork. Each brief opens with the brick-wall opener and its own SESSION_ID (gm §1).
- Every brief reads the pending listing inside one
gm_codemodebody:prd-list {"status":"pending"}filtered in code to its own ids and subjects, returned as that list only. A brief never pulls the full listing into context. - Verify a brief's source path (
codesearch {mode:"filename"}) before launch. A wrong path is a blocked slot. gm_processor_capacityqueues dispatches beyond it; it is not the subagent count.
Shared-tree publish
One work tree, one git lane. Main only; no worktree, side branch or scratch checkout (gm, Autonomy).
- No extra checkouts. Clean base for measurement or bisect:
git archive(gm, READ questions). - A measurement taken in the shared work tree measures other lanes' uncommitted edits, not the project. Compare against remote
mainby content hash (GET /contents/<path>?ref=main, orgit archive <remote sha>) before calling a difference a regression; local HEAD lags remote and lies. Three false alarms in one day came from work-tree measurements: a "stale bake" whose two files were byte-identical to remote main, a bake pin whose inputs "were identical" and were not, and a 0.1506 m hover that reads 0.0000 m on main. - Publish via the Git API: POST blobs; tree with
base_tree: <remote tip>; commit parented on that tip; PATCHrefs/heads/main,forceunset; re-read each blob at?ref=mainand verify it. Then read CI: wait up to 60 s for apush-event run on the full sha (GET /repos/<o>/<r>/actions/runs?head_sha=<full sha>). If none appears,POST /repos/<o>/<r>/actions/workflows/<file>/dispatcheswith{"ref":"main"}for each workflow that declaresworkflow_dispatchon gm main: bump-pins, contracts, discipline-tree, doc-drift, gh-pages, house-clean, link-check, npx-route, process-leak, skill-release (all ten; re-check the list if a workflow is added), then read the runs for that sha again. A dispatch-only run is never a witness for a row whose acceptance names a push-event run: it proves the workflow runs, not that the push triggered it. Such a row stays pending until a push-event run exists on the sha, or the sha is recorded as a residual naming the workflows with no push-event run. A sha with no run after that is a residual, not success. Witnessed case: agentplug a9007880 editedrelease.ymland got onlyworkflow_dispatchruns, nopushrun. git_finalizerefusing to push over a sibling's dirty path is redundant when its content is already published. Never resetrefs/heads/mainunder running lanes.remote_movedon a dirty tree:git_status {"paths":[...]}, commit or revert each path in its owning lane, thengit_push {rev:"<sha>"}.remote_movedplus conflictedgit_pull(conflicted: true,MERGE_HEADpresent):git_merge_abortonly for a merge this lane opened (gm, Collisions); reapply each lane path on the current remote tip and publish it through the Git API (above). Nevergit_resetrefs/heads/main. Confirm withgit_remoteandci-status.- Collision and commit identity: gm, Autonomy.
Editing the skills
Skills under skills/ are live on the next dispatch. Check .git/MERGE_HEAD first. Publish path-scoped, push the full sha, then ci-status. Steps: docs/recovery.md, "Editing the skills".
Finish the work
- Finishing the requested work is the goal of every run of this skill. A run that stops with work left undone is incomplete.
- When an issue blocks a step (a failing check, a fault in a tool or service, a missing input, a network or environment fault), fix it in the same run: retry on a bounded or resumable route, use an alternate route to the same result, repair the fault where it originates, then continue the remaining work.
- Stop only when the requested work is complete, or when a step is still blocked after every route has been tried. Then report the blocked step, the routes tried, and the evidence for each.