# V16.15 — Single-Shot Parallel Coding Runtime

## Why this release exists

V16.12–V16.14 made the runtime fast and honest, but the **write path was still
serial in practice**. The controller had a bounded wave loop, and it already knew
which tasks could run together, yet it made that decision with a root-set
heuristic: two files in the same directory were treated as a conflict, and two
different directories were treated as independent. That rule is wrong in both
directions.

Five bottlenecks were confirmed in the shipped code before any change was made:

| # | Bottleneck (confirmed in the baseline) | Consequence |
|---|---|---|
| A | Independence was decided by comparing two-segment "roots" (`lib/x/a.mjs` → `lib/x`), so `packages/a/src/x.ts` and `packages/a/src/y.ts` were **blocked** although they share only a directory | Real parallelism refused |
| B | A scope that declared **no files at all** produced an empty root set, which compared as disjoint | Silence was read as safety: an unscoped writer could be launched next to another writer |
| C | Two scopes touching `package.json` and `package-lock.json` were "independent" because the exact paths differ | A lockfile refresh racing a manifest edit |
| D | The wave loop applied patches one at a time and reversed only the ones already applied, so a **later** patch could fail **after** the root had been mutated | A half-integrated root, discovered late |
| E | Every child paid for the same wave-level facts (goal, constraints, architecture, requirement ids) in its own context | Duplicated context on every child |

## Laws

- **L1 Root writes stay serial.** Direct writes to the repository root are
  serialized at concurrency 1. Only **isolated sandbox/worktree writes** may
  overlap, and only with other isolated writers or with read-only lanes.
- **L2 The writer bound is hard.** Default 2, hard maximum 3. A request for 99
  writers resolves to 3. The bound is clamped **inside** the scheduler, not at
  the call site, so no caller can widen it.
- **L3 Silence is never safety.** A scope that declared nothing is a **writer
  with an UNKNOWN scope**, and an UNKNOWN scope conflicts with everything. Only
  an explicit `readOnly: true` (or `writer: false`), or a scope that declared
  **only** reads, is a reader. Exactly one function answers this
  (`isWriterScope`), and both the policy and the conflict graph call it.
- **L4 The same directory is not a conflict.** Two writers over different exact
  files are independent. Independence is decided on exact files, read/write
  direction, config family, lockfile↔manifest interaction, generated output,
  declared module edges, shared mutable services, external side effects and
  destructive shell — never on a directory prefix.
- **L5 Writers need PROVEN independence.** One UNKNOWN or CONFLICT edge between
  two writers means the parallel path is refused. The conflict graph is built
  from the caller's OWN edge declarations, so the verdict and the reported graph
  can never disagree.
- **L6 Integration is all-or-nothing.** Every patch is preflighted against the
  current root **before** anything is applied. If any preflight is rejected, or
  two accepted patches rewrite the same file, the root is left untouched. If an
  apply fails after earlier patches landed, **every** applied patch is reversed.
- **L7 Order is declared, never observed.** Root mutation order is a function of
  dependency depth, then wave, then task key. Completion order, arrival order and
  `Promise` resolution order are never inputs.
- **L8 Small work is parent-direct.** TINY and SMALL tasks never spawn a child,
  whatever else is true. The economy gate refuses parallelism whose estimated
  overhead exceeds its estimated benefit, and a missing V16.15 stack falls back
  to the pre-V16.15 bound — **never** to a reason to parallelize.
- **L9 The shared wave context is written once.** One immutable snapshot per
  wave; each child receives a **delta** that references it by id and never
  repeats it inline.
- **L10 A child receipt is evidence, never a verdict.** The compact handoff is
  bounded (first-failure body capped at 600 chars, changed files at 40, warnings
  at 12), raw logs are never inlined, and `canProduceVerdict` is `false`
  everywhere in this stack.
- **L11 Measurement is honest.** Counts are `MEASURED`; provider tokens are
  `NOT_MEASURED`; there is **no** speedup or overlap-saving field, because a wave
  count is activity, not a measurement of wall time saved.

## Capabilities

| # | Owner | Question |
|---|-------|----------|
| A | `lib/parallel-execution-policy-v16-15.mjs` | What should this wave do — and is parallelism even economical? |
| B | `lib/execution-conflict-graph-v16-15.mjs` | Are these two declared scopes provably independent? |
| C | `lib/wave-shared-context-v16-15.mjs` | What is written once for the wave, and what does each child receive? |
| D | `lib/integration-transaction-v16-15.mjs` | How does a wave land in the root without leaving it half-applied? |
| E | `lib/parallel-coding-runtime-v16-15.mjs` | Composition only. It owns **no** policy: every function delegates. |

Owner E exists so the controller has **one** call. It is not a fifth authority:
`planWave` returns the policy's decision unchanged, `runIntegration` is the
transaction, and the completion/retry/cancellation/watchdog functions are the
transaction owner's own. `PARALLEL_CODING_OWNERS` reports the ids it delegates
to, so a receipt can prove which authority produced a verdict.

### Extended in place, not forked

- `lib/worktree-sandbox.mjs` — `preflightTaskSandbox` (read-only, no mutation of
  either tree), `rootWorkspaceIdentity`, `patchesOverlap`. It remains the single
  owner of the actual apply.
- `lib/delegation-safety.mjs` — pair verdicts now come from the conflict graph;
  `PAIR_STRATEGY.LEGACY_ROOTS` keeps the old behavior reachable for diagnosis.
- `lib/task-dag-scheduler-v16-12.mjs` — `SOURCE_WRITE_ISOLATED` overlaps reads and
  other isolated writers up to the isolated lane width, but never a root
  `SOURCE_WRITE` in either direction. `MAX_ISOLATED_WRITE_WIDTH = 3` is clamped
  inside `planTaskDag`.
- `lib/tool-concurrency.mjs`, `lib/tool-scheduler.mjs` — the isolated lane is
  **trusted-only**: read from the third `options` argument, never from tool
  input, so a tool cannot widen its own concurrency.
- `lib/efficiency-metrics-v16-10.mjs` — the single aggregator. It consumes the
  `parallel-coding` observations and reports `speedupClaim: null` and
  `measuredOverlapSavedMs: NOT_MEASURED`.

## Production wiring

`pi/extensions/ues.ts` reaches the stack **only** through the lazy registry
(`hydrateLazy(LAZY_RUNTIME_MODULES.PARALLEL_CODING_RUNTIME)`); it contains no
static import of any V16.15 module, so boot pays for none of it. When hydration
fails, the wave keeps the V16.5 bound with the `SERIAL_STRUCTURED` posture.

One decision per wave drives concurrency, clamped by the pre-existing V16.5 fleet
bound. The bounded loop governor asks the owners before retrying: `retryDecision`
refuses a repeated identical failure, and `progressWatchdog` refuses to re-enter a
wave state it has already reached. Infrastructure errors in the outer catch keep
the bounded-attempt behavior exactly, because the retry owner classifies **child**
failure signatures and would misread a transient Windows `EPERM` as a permanent
policy block.

## Verification

Parallelism never produces PASS. The local verifier remains the only PASS
authority. Every wave child is verified by the existing child verification path,
and the root is re-verified after integration because a child's sandbox PASS does
**not** prove the combined result. DeepSeek, research and children can never
produce a PASS; DeepSeek never edits files; a child never publishes, pushes, tags
or deploys.

## Non-goals / explicit limits

No second task graph, fleet, sandbox, Evidence Store, Metrics authority,
verification broker or workspace-truth owner. No unbounded swarm. No agents for
trivial tasks. No new GitHub Actions. No removing or skipping tests, no timeout
inflation, no lowered reasoning. No automatic commit, push, tag or publish.

## Benchmark honesty

`scripts/bench-v16-15-parallel-coding.mjs` labels every cell with exactly one
provenance: `SIMULATED` (a decision model, `ms: null`), `SYNTHETIC` (real modules,
synthetic input), `MEASURED` (real wall clock) or `LIVE` (a real git repository).

The report carries `summedSpeedupClaim: null`, `tokenSavingClaim: null`,
`qualityClaim: "NOT_INFERRED_FROM_BENCH"` and `providerTokens: "NOT_MEASURED"`
with the reason recorded, because the bench never contacts a provider.

The single `MEASURED` cell runs **both** arms — the same real child process doing
the same deterministic CPU work in real worktrees — and reports both wall times
separately plus the observed ratio, scoped to that cell on that machine, with
`ratioMeaningful` false on a host that cannot run the tasks concurrently. It is
never summed into a headline figure.

The `LIVE` cells run real transactions against a real root: a clean two-writer
integration, a same-file collision refused before any mutation, and a genuine
apply failure after a genuine apply that is then rolled back with the workspace
byte-identical. Cells declare an expectation, the report counts the failures, and
the bench exits non-zero if any cell fails — a bench that can fail silently
proves nothing.
