# Parallel batch (`--parallel N`)

> Root-only: not inlined in --role bundles.

Opt-in only. Default `/rite-build` stays [`one-slice-cycle.md`](one-slice-cycle.md).

Autocomplete executes the shared [global pass](../../devrites-lib/reference/parallel-workspaces.md#continuous-global-frontier)
before local writers: actual immediate-successor preparation or narrow disclosed
deferral, then global cohort admission. Consume fresh dispatch-return scheduling.
Standalone Build stays local; select notices never broaden its picks. A placeholder
is not preparation; one successor pipelines safely without preparing the whole roadmap.

## Entry

| Input | Behavior |
| --- | --- |
| omitted / `--parallel 1` | Serial one-slice |
| `--parallel N` (**2≤N≤10**) | N is a **cap**, not a quota: run the largest eligible set ≤ N |
| non-integer / `N≤0` / `N>10` | Hard refuse (no silent clamp) |
| Autocomplete `--parallel N` | Global cap for this run; controller entry upserts mode and explicit capacity, preserving other AFK fields |
| Autocomplete, no flag | Same path, cap from sentinel `max_parallel` else 10; serial only when ineligible |

AFK caps `N`. Charge each green sibling once after its successful landing;
rejected/unlanded work charges zero, without undoing prior landed accounting.
`parallel create` refuses another retained repository controller.
Cold resume reconciles retained repair artifacts by exact fingerprint. A blocked
label, round count or distinct finding alone does not stop: establish current
review coverage under step 4 and repair unless a genuine stop remains.

## Dynamic selection and re-batching

Size each batch by what is runnable **now**, never by the requested number:

```text
N_eff = min(
  cap                  -- this invocation's --parallel N (wins over leftover sentinel max_parallel)
                       | else sentinel max_parallel | else 10 (autocomplete default)
  eligible set         -- dependency-satisfied pending slices, pairwise path-disjoint
  AFK headroom         -- outside /rite-autocomplete only (max_slices, max_agents,
                          max_minutes, review queue). Autocomplete ignores those;
                          do not shrink this batch for them
  host capacity        -- concurrent worktree writers + the runtime isolation table
)
```

Select **greedily in plan order** with
`devrites-engine parallel select --slug <slug> --cap N --from-plan`: ready set
(dependencies built — state.md checkbox, or an evidence.md heading that says that slice was checkpointed plus a `Checkpoint: <sha>` line — minus running-lease slices), Writer allowlist
(else Files likely touched), plus manifest/lockfile when `External dependencies:`
names a package (module path, npm scope, backticked name, or a non-toolchain version).
`context --phase build` with no `--slice` stays on `active_slice` unless AFK
`max_parallel` is at least 2 or a lease is running; a HITL `--parallel N` run
passes `--whole` so the other slices stay visible.
`next` prints `eligible_now:`/`batch:`. `n_eff` counts new picks only.
With a running lease, a single new pick still uses `parallel add`;
`n_eff=0` with a running lease waits for siblings, not Prove. Without one:
≥2 → parallel batch, 1 → standalone serial; Autocomplete cap>1 on a capable
host instead isolates that first writer for global refill. No pending work
returns to the caller's next phase; no ready local work does not exhaust its
authorized global frontier. A cap need not be filled.

**Refill per landing; never wait for the batch.** Each sibling lands alone when
green ([Lifecycle](#lifecycle) step 6). Re-read state, pending slices, and the
tree; reselect: unlanded siblings hold
their slots and paths, landed ones count as built, and only new picks print;
`parallel add` starts each from the control tip and its wright dispatches at once.
No lease → plain `select` + `create`. AFK/autocomplete is one run; HITL stops after
each landing. **Failing case:** three siblings are green, one is repairing, and no
new slice starts.

## Path-disjoint

Exact project-relative source/test paths only. Normalize `\`→`/`. Reject `..`,
duplicates, absolutes, `.devrites/**`. Empty pairwise intersection required.

**SSOT:** `devrites-engine check path-disjoint [--root <dir>] [<json-file>|-]`
(N≥2 only; pass `--root`). Input is `{"slices":[{"id":"SLICE-002","paths":["src/policy.rs","src/policy/mod.rs"]}]}`
(or a top-level array) built from each candidate's `Files likely touched`.
Exit `0` proves path disjointness only; fan-out also requires independent
validation. A shared interface change and its implementers that must compile
together form one validation unit, even in different files. Repair the slice
contract into one bounded slice or sequence compatible steps before dispatch;
do not label coupled siblings independently green. Else force serial. Inspect-time
overlap → sibling **gap** (a shared path is a contract defect). **Failing
case:** two parallel wrights share a path and fan-out proceeds without that
check.

Before `parallel select`, add each slice's hidden write surfaces (list owner:
[repository-topology.md § Ownership rules](../../devrites-lib/reference/standards/repository-topology.md#ownership-rules))
— lockfile/manifest, the generated target of an edited source, migration chain,
snapshot directory, formatter sweep — and compare paths case-folded
(`src/Api.ts` = `src/api.ts`); `--from-plan` adds the manifest/lockfile for a package.
Slices colliding on either run serially; the rest
stay eligible. Record `serial: <ids> — <surface>` in `evidence.md`. AFK cap≥2
refuses narrower Build-wright waves until other picks are started/reserved or
each skipped pick has a substantive serial reason. Repairing/already-started/in-wave
alone is not a reason. Controller admission also includes ready foreign picks:
only exact `serial: child/SLICE-001 — <reason>` can defer that foreign lane; bare
`SLICE-001` is local. Preparation `frontier-blocked:` cannot waive ready Build
picks or safety gates. Qualified unsupported-isolation deferral preserves truthful
owner dispatch/ordered handoff, never early same-tree foreign Build. HITL stays unchanged.

## Control vs workers

Control owns the controller lease; source workspaces own their bookkeeping.
Use returned worker `worktree_path`/`branch`: controller-local siblings retain
`<batch>/<slice>`, foreign siblings use `<batch>/_workspaces/<source>/<slice>`.
Workers never write `.devrites/**`.

Lease includes `batch_id`, `created_at`, `base_sha`, `capacity`, `n`, `status`,
`control_pid_or_session` and `slices[]` (`id`, optional `workspace`, `paths`,
`worktree_path`, `branch`, `wright_status`, `transfer_commit`). Omitted workspace
means controller; CLI lease `--slice` uses source-qualified foreign keys.

## Lifecycle

1. Orient/gate; parse the cap; compute `N_eff` and select the eligible set
   ([Dynamic selection and re-batching](#dynamic-selection-and-re-batching)) —
   local `parallel select --slug <slug> --cap N --from-plan`, or Autocomplete's
   authorized mixed-source query from the shared controller reference.
   Preflight each selected slice with `devrites-engine check slice <slug>
   <id>`, plus `check preconditions <slug> --slice <id>` when it has a
   `pre-dispatch <id>` preflight row; a malformed contract or non-zero
   precondition is dropped from the batch and recorded as a gap, not dispatched.
2. Write lease with `parallel create --cap N`; freeze `B=HEAD`; create isolated
   worktrees, including Autocomplete's first lone writer at cap>1.
3. Dispatch ≤N active wrights globally (absolute cwd=returned worktree; exact
   `devrites-slice-wright` native profile; source slug; allowlist; prove `HEAD==B`).
   Source-local waves use one role per sibling:
   `--role slice-wright.<SLICE-ID> --slice <SLICE-ID>`.
   `start` every selected role across sources before waiting on any.
   Shared `slice-wright` role is refused: one handle cannot track siblings.
   Linked worktrees run source checks with `DEVRITES_ROOT=<control>/.devrites`;
   source overrides never leak into controller lease mutations.
4. Inspect each returned path list, cumulative diff from `B`, transfer identity,
   and required result fields against the immutable contract. Reject malformed,
   stale, or out-of-scope work. Freeze and probe the sibling in its worktree:
   `candidate freeze <slug> --wave <w> --slice <id> --base <B> --worktree <worktree_path>`,
   `check perturb ... --worktree <worktree_path> --slice <id>`. Apply
   [independent Build review](phase-contract.md#independent-build-review): first
   full inventory, then bounded owning-role rechecks after repairs. Run approved
   proof in the same window as that review. Gap or Critical/Important → `gap`;
   else proof → `green|red|gap`.

   Group recheck findings by owning role and changed surface. Each exact
   specialist rechecks its own fingerprints and the correction diff; code, test,
   and doubt remain separate dispatches, even on the same diff.
   Reviewer fan-out does not shrink the writer batch. Reviewers queue at the
   same cap as writers (10; see
   [`parallel-dispatch.md`](../../devrites-lib/reference/parallel-dispatch.md#scale-limits)).
   `max_agents` does not shrink it under autocomplete. Past that cap, queue a
   second pass — a batch never assumes unbounded reviewers.
5. Non-green siblings → **repair round** (below) in the same worktrees; lease
   stays `running` — no abort while repair budget remains. A green sibling →
   `parallel record-green`, then step 6 for it alone.
   A recoverable RED blocks integration but does not cancel independent safe running siblings.
   Expected test-first RED is not a terminal gate failure. Cancel only for a shared
   invalid foundation, safety/access failure, resource boundary, or explicit stop;
   preserve completed and in-progress recoverable work and classify each result.
6. `parallel integrate --slice <id> --apply-to-control` **only after its steps
   4–5 are green**: `transfer_commit` descends from the sibling's base and diffs
   path-exact vs it; apply as its own local `WIP(<source-slug>):` commit
   (imperative summary, never a slice id). Staging branch, durable exact landing
   intent, then FF; retry the same operation after interrupted persistence.
   The engine reconciles only that recorded landing and refuses unrelated
   HEAD/ref movement. Conflict → `integrate-failed`; repair in place, retaining
   slot/paths while independent safe landings/refill continue.
   Never integrate because a wright returned.
7. Each landing: FF control (the branch that held `HEAD` when the batch started —
   often `main`/`master`, otherwise whatever the user was on) by one local commit,
   never pushed; union only that sibling's paths into SOURCE `touched-files.md`;
   update its checked BUILT state/evidence and regression floor; AFK +1 once.
   Once every worker-bound reviewer/proof handle is terminal and source bookkeeping
   is durable, run `parallel prune --root <repo> --slug <controller>` to release
   clean integrated checkouts before refilling. It keeps transfer branches, proof
   identities, lease rows and pending siblings. A dirty, ignored-data-bearing,
   switched or unknown checkout stays untouched; record its recovery path and
   continue safe independent work, never force-delete it to reclaim a slot.
   Land every independently green sibling and refill globally immediately
   ([Dynamic selection](#dynamic-selection-and-re-batching)), then run source-owned
   feature gates for the train ([Proof tiers](phase-contract.md#proof-tiers));
   refilled wrights do not wait for unrelated source proof.
8. Whole cleanup runs only after the last landing marks the lease `complete`.
   `parallel cleanup` removes verified disposable checkouts/branches; late edits,
   ignored/untracked data and extra commits stay with reported recovery paths.
   Blocked/human-stopped batches keep lease, worktrees, branches — nothing is
   removed while the batch may resume. `parallel abort` + `cleanup --force`
   abandons the batch — the explicit human discard path; the single
   orchestrator-emitted use is the automatic re-batch under Repair rounds,
   where it acts as the salvage step only (uncommitted allowlisted changes →
   slice-branch commit; branches past `B` kept; a failed salvage keeps the
   worktree) and prints retained refs for the record. `abort` marks the lease
   only — never rewinds control: a moved HEAD is external work, reported not
   reset. Never integrate a rejected transfer commit.

## Repair rounds

Repair rejected work in place; never discard/rebuild while budget remains.

- Record each round as `build-parallel-<batch>-repair-<round>.json`: each
  non-green sibling's status, verbatim findings, transfer_commit/null.
- Use step 4's initial inventory or affected recheck as appropriate; never start
  a correction from an early reviewer result while required accounts are pending.
- Classify the complete inventory: **implementation defect** (review
  Critical/Important on the diff, red wright/proof) → one repair-all wright in
  the same worktree with the full inventory. **Durable-plan defect**
  (falsified contract assumption, AC unchanged) →
  [`spec-drift-guard.md`](spec-drift-guard.md): batch-sweep contract
  assumptions, one folded `/rite-plan repair` + one `/rite-vet` recheck
  inline, resume repair in the same worktrees under the amended contract;
  never emit `Fix:`. **Frozen-lease scope defect** (correction needs
  out-of-allowlist paths or re-slicing; lease allowlists are immutable) →
  **automatic re-batch, no human gate**: `parallel cleanup --force` salvages
  and records refs; `parallel create` starts from control `HEAD` under the
  corrected decomposition. Give each new wright its salvaged
  `devrites/parallel/<slug>/<old-batch>/<slice>` ref. If the inventory proves the accepted
  approach unsound and no corrected decomposition exists, that is exhaustion
  → blocked, preserve, STOP. **Reviewer stall/cancel or missing
  verdict** → reconcile the old host handle under
  [terminal reconciliation](../../devrites-lib/reference/parallel-dispatch.md#cancellation-and-terminal-reconciliation)
  before re-dispatching the reviewer, not the wright. **Product/policy/
  irreversible** → stops for the human.
- Dispatch one fresh repair-all `devrites-slice-wright` (cwd=worktree; step 3
  rules) with the original contract + complete inventory +
  "preserve what works; fix every finding; **then attack your own correction** —
  probe the changed hunks for defects the fix itself introduced (adjacent cases of
  the same rule, new observation/ordering windows, alias or boundary escapes) and
  fold them in before returning; return a new `transfer_commit`". A correction whose
  own diff is unexamined is not ready: a green writer gate plus an unexamined fix is
  how the same defect family reappears one case deeper each round. Never reset
  to `B` — that destroys the work under repair. The commit must still descend
  from `B` and diff path-exact vs `B`.
- Budget and exhaustion follow only
  [the canonical retry contract](../../devrites-lib/reference/standards/afk-hitl.md#retry-cap-no-progress-loops-and-self-resolve).
  Record one failed invariant, mechanism, reproduction, correction, and decisive
  recheck per fingerprint; never use an umbrella like "covering" or "review is red again".
- A distinct post-repair Critical/Important gets a new fingerprint: refresh the
  inventory and affected owning-role rechecks. It proves neither plan defect nor
  exhaustion. Use plan path only if inventory proves the approach unsound.
- Stop under the canonical retry/authority/resource conditions, or when evidence
  proves the approach unsound and no in-scope corrected decomposition exists.
- A spent strategy is not abort: escalate per the canonical retry contract,
  preserving lease/worktrees/branches; a fully escalated fingerprint parks and
  the batch keeps its independent members. Repair resumes in place on
  escalation or reinvocation. Human-owned decisions use the drift-guard
  question and wait.

## Engine verbs

```text
devrites-engine parallel select --cap <1-10> [--root <dir>] [--slug <controller>] [--from-plan [--workspaces <controller,authorized-source,...>] | <json-file>|-]
devrites-engine parallel create --root <repo> --slug <controller> --batch <id> --base <B> --cap <N> --json <file>|-
devrites-engine parallel add --root <repo> --slug <controller> --cap <N> --json <file>|-
devrites-engine parallel record-green --root <repo> --slug <slug> --slice <id> --commit <sha>
devrites-engine parallel integrate --root <repo> --slug <slug> --slice <id> --apply-to-control
devrites-engine parallel integrate --root <repo> --slug <slug> --apply-to-control
devrites-engine parallel status --root <repo> --slug <slug>
devrites-engine parallel lease-write|lease-read|lease-clear --root <repo> --slug <slug>
# abandoning the batch is human discard only; a frozen-lease re-batch may emit
# cleanup --force as its salvage step (see Repair rounds):
#   parallel abort --root <repo> --slug <slug>
#   parallel cleanup --root <repo> --slug <slug> --force   (salvages, prints retained refs)
```

`integrate` without `--apply-to-control` stages commits; lease stays `running`,
control stays at its expected tip, cleanup refuses. `record-green` and `integrate`
accept `integrate-failed` for retry. Applied landings advance `control_tip`
(initially `B`), permitting landings/refill. External control movement or
uncommitted slice-path edits with `--apply-to-control` cause refusal:
stop for the human (never stash or commit their work); the batch is untouched.
Go is SSOT; never ad-hoc git/bash orchestration.

## Runtime isolation

Each parallel worktree must own isolated runtime resources. Before fan-out,
assign per-slice env (document in the lease or worktree README):

| Resource | Pattern |
| --- | --- |
| HTTP port | `PORT=<base+N>` or `DEVRITES_PARALLEL_PORT_<slice>` |
| Compose | `COMPOSE_PROJECT_NAME=devrites-<slug>-<batch>-<slice>` |
| SQLite/DB file | separate path under the worktree or `/tmp` |
| Dev server | one process per worktree; never share a listener |

Reusing the control worktree's running server, DB, or compose stack across
siblings causes cross-slice pollution and flaky proof. Force serial if
isolation cannot be established.

## Host

Claude: N concurrent Task wrights (`acceptEdits`, cwd=worktree). Codex: require
host-explicit concurrent worktree writers + native reconcile; else force serial.
Never two writers in one worktree; never root-emulated concurrency.
