What
run.replicas races N independent containers on one forge event, each on its own branch, each opening its own review request — and it works on GitHub triggers only. The refusal message already admits the truth (worker/src/triggers.mjs:541-543): "not yet covered for gitlab/forgejo/azure triggers (github only in this version); every forge mints its branch the same way, so this is a gap to close, not a limit." Close it: replicas on label, comment and pull_request triggers for GitLab, Forgejo and Azure DevOps.
What already works (don't redo this)
Almost everything below the surface is forge-generic today:
- The enqueue primitives.
enqueueForgeJob (worker/src/queue.mjs:118) takes replica/replicas for any kind; forgeDeliveryJobId (worker/src/job-id.mjs:86-99) suffixes -r<i> onto any forge's prefix; the semantic dedup key (queue.mjs:159) already composes the per-forge separator (#/!, forges.mjs:145-148) with the :r<i> suffix — a replicated GitLab job would dedup as project!5:flow:r2 with no new code.
- The receiver config layer.
loadTriggers writes replicas onto every forge's rules already (receiver/src/config.mjs:278-284) — it is just always undefined because parseTriggers refuses first.
- The branch minter.
issueBranch(number, replica) (worker/src/branch.mjs:76-79) has no forge concept; the pi/issue-* safety floor in guardrails/HARD_RULES.md:35-41 is a glob, not a GitHub noun.
- The shared preparer.
prepare-github.mjs:195-201 already delivers replica/replicas to all four prompt builders — the gitlab/forgejo/azure builders destructure them away today.
- The per-forge "open the review request" logic. Each single-run path already pushes a branch and opens an MR/PR its own way (
gitlab-prompt.mjs:77-90, forgejo-prompt.mjs:77-87, azure-prompt.mjs:71-82); replicas just multiply what exists.
- The record, the image gate, the budget.
replica/replicas on the run record are forge-neutral (interfaces.md:1780-1781); the replicas image-capability preflight is forge-agnostic (worker/src/image-preflight.mjs:87-89); N replicas make N honest budget reservations by construction (requirements.md:1232-1236).
The actual gap, enumerated
- The schema refusal —
worker/src/triggers.mjs:541-543 throws on any non-github kind. Delete the arm; keep the cron/local refusal above it untouched (that one is a hazard, not coverage — see below).
- The fanout loop — only the two GitHub producers loop (
receiver/src/receiver.mjs:200-204, receiver/src/poller.mjs:783-788). The gitlab (receiver.mjs:247-262), forgejo (:298-310) and azure (:355-370) arms enqueue once and never read result.job.replicas. Each needs the same 5-line loop, with the same partial-failure idempotence the GitHub arm documents (receiver.mjs:196-199): 503 on failure at replica k, retry converges because queued ids dedup.
- The per-forge filters —
filter-gitlab.mjs, filter-forgejo.mjs, filter-azure.mjs carry no replicas field (zero grep hits): one job-literal line plus three rule-read lines each, mirroring filter.mjs:123-127/165/211/305.
- The prompt builders — the three non-GitHub builders call
issueBranch with one argument (gitlab-prompt.mjs:71, forgejo-prompt.mjs:72, azure-prompt.mjs:65) and have no replica paragraph. Pass the index through and add the divergence lines analogous to github-prompt.mjs:98-110/116-127, including the [r<i>/<n>] title marker (agent-honored, not host-enforced — the existing posture, requirements.md:1246-1248).
Constraints to preserve (each is load-bearing today)
- The
resume × replicas mutual refusal holds per forge (triggers.mjs:547-549, branch.mjs:18-21, session-key.mjs:81-87): a resumed run continues one lineage; replicas exist to fork it.
REPLICAS_MAX = 3 stays derived from PI_CONCURRENCY's default (triggers.mjs:95-101); this issue does not touch the ceiling.
- Chain fanout stays bounded:
outbox.mjs:51 returns early for every non-local kind, so no new bound is needed.
- The dedup rule that made GitHub replicas safe (
interfaces.md:1543-1545: a duplicate queue.add under a taken id is silently ignored, hence the distinct semantic keys) must be asserted per forge in tests.
OQ-017 (the PR-head-branch sharing hazard) is written in GitHub nouns but applies verbatim to MR targets — widening coverage widens its blast radius without changing its argument; the OQ gets a scope note, not a redesign.
- Non-GitHub forges have webhook arms only (the poller is GitHub-only by construction,
poller.mjs:105), so replicas there are webhook-only; say so rather than implying parity.
Explicitly NOT proposed
- Replicas on cron/local. That refusal is a hazard, not coverage: a local job's
/workspace IS the operator's folder, and two replicas would edit one working tree with no gate and no undo (triggers.mjs:538-540, design.md:1859-1861, constitution.md:674). It stays, message and test intact.
- Touching the budget: N reservations is the honest count, and softening caps for replicas would turn a cost multiplier into a cap bypass (
requirements.md:1232-1236).
- A panel key or model-callable path for the field (
interfaces.md:1551-1554).
Acceptance
run.replicas: 2 on a gitlab, forgejo or azure label/comment/pull_request trigger loads in both services; one delivery mints N jobs with distinct <prefix><id>-r<i> jobIds and distinct :r<i> semantic keys; each replica's prompt names its own pi/issue-<n>-r<i> branch and the [r<i>/<n>] title marker; the run records carry replica/replicas; the image capability gate refuses per replica set exactly as on GitHub; partial enqueue failure 503s and converges on retry; cron/local and resume combinations keep their existing refusals verbatim; REQ-REPLICA-RUNS' scope clause, INT-TRIGGERS-FILE-CONTRACT's schema note and acceptance rows, DES-REPLICA-INDEX-REACHES-THE-BRANCH, docs/replicas.md:107 and the four forge docs are amended in the same PRs.
What
run.replicasraces N independent containers on one forge event, each on its own branch, each opening its own review request — and it works on GitHub triggers only. The refusal message already admits the truth (worker/src/triggers.mjs:541-543): "not yet covered for gitlab/forgejo/azure triggers (github only in this version); every forge mints its branch the same way, so this is a gap to close, not a limit." Close it: replicas onlabel,commentandpull_requesttriggers for GitLab, Forgejo and Azure DevOps.What already works (don't redo this)
Almost everything below the surface is forge-generic today:
enqueueForgeJob(worker/src/queue.mjs:118) takesreplica/replicasfor any kind;forgeDeliveryJobId(worker/src/job-id.mjs:86-99) suffixes-r<i>onto any forge's prefix; the semantic dedup key (queue.mjs:159) already composes the per-forge separator (#/!,forges.mjs:145-148) with the:r<i>suffix — a replicated GitLab job would dedup asproject!5:flow:r2with no new code.loadTriggerswritesreplicasonto every forge's rules already (receiver/src/config.mjs:278-284) — it is just alwaysundefinedbecauseparseTriggersrefuses first.issueBranch(number, replica)(worker/src/branch.mjs:76-79) has no forge concept; thepi/issue-*safety floor inguardrails/HARD_RULES.md:35-41is a glob, not a GitHub noun.prepare-github.mjs:195-201already deliversreplica/replicasto all four prompt builders — the gitlab/forgejo/azure builders destructure them away today.gitlab-prompt.mjs:77-90,forgejo-prompt.mjs:77-87,azure-prompt.mjs:71-82); replicas just multiply what exists.replica/replicason the run record are forge-neutral (interfaces.md:1780-1781); thereplicasimage-capability preflight is forge-agnostic (worker/src/image-preflight.mjs:87-89); N replicas make N honest budget reservations by construction (requirements.md:1232-1236).The actual gap, enumerated
worker/src/triggers.mjs:541-543throws on any non-github kind. Delete the arm; keep the cron/local refusal above it untouched (that one is a hazard, not coverage — see below).receiver/src/receiver.mjs:200-204,receiver/src/poller.mjs:783-788). The gitlab (receiver.mjs:247-262), forgejo (:298-310) and azure (:355-370) arms enqueue once and never readresult.job.replicas. Each needs the same 5-line loop, with the same partial-failure idempotence the GitHub arm documents (receiver.mjs:196-199): 503 on failure at replica k, retry converges because queued ids dedup.filter-gitlab.mjs,filter-forgejo.mjs,filter-azure.mjscarry noreplicasfield (zero grep hits): one job-literal line plus three rule-read lines each, mirroringfilter.mjs:123-127/165/211/305.issueBranchwith one argument (gitlab-prompt.mjs:71,forgejo-prompt.mjs:72,azure-prompt.mjs:65) and have no replica paragraph. Pass the index through and add the divergence lines analogous togithub-prompt.mjs:98-110/116-127, including the[r<i>/<n>]title marker (agent-honored, not host-enforced — the existing posture,requirements.md:1246-1248).Constraints to preserve (each is load-bearing today)
resume×replicasmutual refusal holds per forge (triggers.mjs:547-549,branch.mjs:18-21,session-key.mjs:81-87): a resumed run continues one lineage; replicas exist to fork it.REPLICAS_MAX = 3stays derived fromPI_CONCURRENCY's default (triggers.mjs:95-101); this issue does not touch the ceiling.outbox.mjs:51returns early for every non-local kind, so no new bound is needed.interfaces.md:1543-1545: a duplicatequeue.addunder a taken id is silently ignored, hence the distinct semantic keys) must be asserted per forge in tests.OQ-017(the PR-head-branch sharing hazard) is written in GitHub nouns but applies verbatim to MR targets — widening coverage widens its blast radius without changing its argument; the OQ gets a scope note, not a redesign.poller.mjs:105), so replicas there are webhook-only; say so rather than implying parity.Explicitly NOT proposed
/workspaceIS the operator's folder, and two replicas would edit one working tree with no gate and no undo (triggers.mjs:538-540,design.md:1859-1861,constitution.md:674). It stays, message and test intact.requirements.md:1232-1236).interfaces.md:1551-1554).Acceptance
run.replicas: 2on a gitlab, forgejo or azurelabel/comment/pull_requesttrigger loads in both services; one delivery mints N jobs with distinct<prefix><id>-r<i>jobIds and distinct:r<i>semantic keys; each replica's prompt names its ownpi/issue-<n>-r<i>branch and the[r<i>/<n>]title marker; the run records carryreplica/replicas; the image capability gate refuses per replica set exactly as on GitHub; partial enqueue failure 503s and converges on retry; cron/local andresumecombinations keep their existing refusals verbatim;REQ-REPLICA-RUNS' scope clause,INT-TRIGGERS-FILE-CONTRACT's schema note and acceptance rows,DES-REPLICA-INDEX-REACHES-THE-BRANCH,docs/replicas.md:107and the four forge docs are amended in the same PRs.