Tandem: execute-batch-parallel (Dev/QA hat)
Operate as Dev/QA hat in a fresh chat. This is the concurrent sibling of
execute-batch: where execute-batch runs a chat's stories one-at-a-time, execute-batch-parallel
fans out one sub-agent per story so a batch of independent stories clears in a single chat,
then folds every result back onto the board in one serialised reconciliation pass on the main
thread.
It is built against the settled concurrency model in ADR-0075 — sub-agents-with-merge under a
disjoint-file precondition, main thread as sole writer of serialised state. It composes the
existing per-story skills (execute-story, run-testplan, close-out-story); it does not
re-implement their logic, and it never relaxes the kit's gate contract (status enum, atomic
flip, ADR-on-the-spot, BUG-on-defect).
Default is still serial. Parallel fan-out is opt-in and earned by precise metadata. If a batch cannot be proven file-disjoint, this skill refuses and the operator runs the serial
execute-batchinstead. A batch that cannot be proven safe is treated exactly like an unsafe one.
Inputs needed
- A chat id — e.g.
CHAT-02. If the user didn't supply one, ask, or list the chats in the latest strategy. - The strategy — default to the latest
_00-Project-Management/41-Reports/EXECUTION-STRATEGY-*.json. Fall back to the paired.mdreport if the JSON is absent. The user may name a specific file.
Load into context
Use Read / Glob; treat missing files as "not present", never throw.
- The named chat from the latest
EXECUTION-STRATEGY-*.json: itsstories(withreadyflags),lanes,verifycommand,sub_agents, andexecutedflag. - Each story + its paired testplan under
32-Stories/+33-Testplans/. - SOP —
90-Standards/SOP.md(DoR/DoD, status enum, WIP limits §5, parallel-vs-serial rule). - ADR-0075 — the concurrency model this skill obeys.
Step 1 — Safety gate (refuse BEFORE any fan-out)
The gate runs before a single sub-agent is dispatched. All of the following must pass; on any
failure, do not fan out — report the failing item and fall back to serial execute-batch:
DoR precheck. Every story in the chat must be
status: ready. If any story isready: falsein the strategy or notreadyin its frontmatter, refuse and name the unready story — it isunsafeto start. (The operator runs/tandem:refine-backlogfirst.)Disjoint-file precondition (
pm:batch-check, ADR-0075). Run the static batch-check over the batch's stories:node _00-Project-Management/93-Scripts/batch-check.js STORY-A STORY-B STORY-CIt compares every pair's
files_touched:. ACONFLICT(two stories write the same file, or a file under another's directory) or anyUNKNOWN(directory×directory, or empty/absentfiles_touched:— cannot prove disjoint) is a hard stop: the batch is not fanned out. Only a provably disjoint batch (exit 0) may run in parallel.UNKNOWNnever silently passes. Fail closed: any non-zero exit — a conflict, an unknown, or a batch-check script error — blocks fan-out and falls back to serialexecute-batch; the gate never reads a crash as "safe".
The refusal path precedes the fan-out: the skill computes the gate, and only a clean gate unlocks Step 2.
Step 2 — Fan-out: one scoped sub-agent per story
On a safe batch, dispatch one sub-agent per story, concurrently (a single message with one
Agent call per story so they run in parallel). Each sub-agent is tightly scoped to its own
story and is structurally unable to corrupt shared state:
- Scope = one story only. The sub-agent's prompt contains only that story file, its paired
testplan, and that story's ACs. It implements that story's ACs (using the chat's resolved
sub-agent discipline for that story, e.g.
mcp-developer/build-engineer), runs its testplan TCs, and records AC pass/fail — and nothing about any sibling story. - Board files are FORBIDDEN. The sub-agent prompt explicitly says never MONITOR / ACTIVE: the
sub-agent must not read or write
MONITOR.md/ACTIVE.md, and must not create ADR/BUG files or claim an ADR/BUG number. Sub-agents never write serialised state — that is the main thread's job alone (ADR-0075 single-writer). A sub-agent that thinks it needs an ADR/BUG proposes the body in its handoff; it does not number or file it. - No cross-story coordination. Because the batch is provably file-disjoint, the sub-agents work in the shared tree with no content collision; they do not need to talk to each other.
Sub-agent prompt template (the safety boundary)
Every dispatched prompt is built from this template — the exclusions are load-bearing:
You are implementing exactly ONE story: STORY-NN.M.PP.
Inputs (the ONLY files you own): <story path>, <paired testplan path>.
Write ONLY the files listed in this story's files_touched: <files_touched list>.
Do: implement each AC; run every testplan TC's Command; record PASS/FAIL per TC and per AC.
Do NOT touch MONITOR.md or ACTIVE.md (forbidden);
do NOT create or number any ADR-*/BUG-* file; do NOT edit any sibling story.
If the work requires touching ANY file outside files_touched, STOP and report it in the
handoff — do not create or edit it (an undeclared write breaks the disjoint-file guarantee).
Return: the structured handoff described below — raw data, not prose for a human.
Step 3 — Structured handoff (sub-agents return data, not board writes)
Each sub-agent returns a structured handoff that the main thread later reconciles (the reconciliation pass itself is defined in the next section). Returning data — rather than writing shared state — is what keeps fan-out race-free. The handoff shape per story:
story_id— the story it worked.status_flip_proposal— proposed terminal status (done, orin-progressif a DoD/TC failed) plus thecompleted_atit would stamp. The main thread, not the sub-agent, applies the flip.ac_verification_log— each AC with PASS/FAIL and the evidence (the TC command output).testplan_results— each TC id → PASS/FAIL (+ the failing output if any).proposed_adrs— zero or more draft ADR bodies (title + content) for non-obvious decisions, un-numbered (the main thread assigns the sequentialADR-NNNN).proposed_bugs— zero or more draft BUG bodies for any TC failure / defect, un-numbered (the main thread assigns the sequentialBUG-YYYYMMDD-NN).files_changed— the files the sub-agent actually wrote (for the reconciliation commit).
The main thread collects every story's handoff and applies them in one ordered pass (next section).
Step 4 — Reconciliation (single-writer, serialised)
After all sub-agents return, the main thread alone folds the handoffs onto the board in one ordered pass. Sub-agents proposed; the main thread disposes. This is the single-writer contract of ADR-0075 — there is exactly one writer of serialised state, so nothing races.
Reconciliation order (deterministic — apply in this exact order)
Process the collected handoffs in deterministic story order (the chat's listed order), and within that, in these stages — first collect, then number, then flip, then write the board, then commit:
Collect every story's structured handoff (from Step 3). Partition into succeeded (proposes
done, all ACs/TCs PASS) and failed (any AC/TC FAIL or DoD gap).Assign sequential IDs (main-thread-only). For every
proposed_adr/proposed_bugbody, the main thread computes the next free number at write time by globbing the existingADR-*/BUG-*files — never a number pre-assigned during fan-out — so two stories can never collide on the sameADR-NNNN/BUG-YYYYMMDD-NN. Numbers are allocated in story order, then the files are written by the main thread.Run the per-story DoD gate, then apply atomic status flips. A story only flips to
doneonce its Definition-of-Done gate passes — the main thread runs the same DoD checksclose-out-storywould (AC verification against the handoff'sac_verification_log+ testplan PASS + the R14 AI-code review for that story's diff). Only the board write (stage 4) is batched to once; the DoD gate still runs per story. For each story that passes, flipready → done(the story wasreadyon the board through fan-out — the in-progress work happened inside its sub-agent), setting bothstarted_atandcompleted_atin a single edit (atomic — status + both timestamps together, never half-flipped, never adonestory with an emptystarted_at). This matches ADR-0077'sready → in-progress → donelifecycle, collapsed into one atomic write at reconciliation. For each failed story (DoD gap, TC FAIL, or no usable handoff), see partial-failure handling below.Per-item retro ledger line — run it here, once per flip. Because this stage re-implements the close-out gate rather than delegating to it, the item-level ledger line would otherwise never be written on the parallel path, and the level would be silently empty for every fanned-out batch. So: for each item that flips to
done, runclose-out-storystep 5's capture command verbatim, substituting that item's values. Do not restate the command here — it is defined in exactly one place on purpose (SOP §14.1), and a second copy is precisely the drift that rule exists to prevent. This is a side-effect, not a gate: it never blocks a flip.Write the board once —
MONITOR.md+ACTIVE.md. A single (once)MONITOR+ACTIVEwrite covers the whole batch — one revision-history block for every shipped story, one ACTIVE update — not once per story. Only this board write is batched; the per-story DoD gate in stage 3 is not. Single writer + single write = no interleaving.Per-story commit checkpoint. Commit each finalised story (
STORY-NN.M.PP — <imperative>) so a crashed batch leaves completed stories committed anddone(retained fromexecute-batch).
Partial-failure handling (honest reporting)
A batch where some sub-agents fail must not abort the successful ones:
- A failed story is flipped
ready → in-progress(setstarted_at, leavecompleted_atempty) and leftin-progress(recoverable — never half-flipped, never silentlydone), with a one-line note of why; its proposed BUG body is filed (numbered by the main thread). - A story whose sub-agent returned nothing or a garbled handoff (crash, timeout, or a terminal API error after retries) is treated exactly like a failed story — never silently dropped: left recoverable and named in the shipped-vs-not split below, so the operator always sees it.
- The succeeded stories still finalise to
doneand commit — the failure of one does not roll back the others. - The run reports the split: which stories shipped vs which did not, plus the chat id to resume. Honest partial success beats an all-or-nothing abort.
Two batches back-to-back must not double-count MONITOR's shipped totals — the count blocks are regenerated by
pm:monitorfrom frontmatter (the source of truth), not incremented by hand.
Step 5 — Guardrails (WIP limit + DoR refusal)
Batch mode must never quietly break the kit's own gate contract. Two guardrails sit on top of the safety gate (Step 1):
- DoR refusal (hard stop). The skill will not start a batch if any story has unmet DoR — it
refuses and names the failing story and the specific missing DoR item (e.g. "STORY-17.1.09
— no paired testplan"). An unready story is never fanned out; the operator runs
/tandem:refine-backlogon it first. (This is the Step 1 DoR precheck, restated as a guardrail: refuse, don't degrade.) - WIP-limit interaction (preserve, do not raise — ADR-0077). Batch-parallel preserves the SOP
§5
in-progressWIP limit (max 2) unchanged — it does not raise the limit. This is safe at any fan-out width because fan-out consumes zero WIP slots: sub-agents work in isolation and never flip board status, so stories stayreadyuntil the serialised reconciliation flips them one at a time (boardin-progress≤ 1 during the pass). The only WIP risk is failed stories leftin-progress; to honour the cap, at most §5-limit (2) failed stories stayin-progress; any excess failures are setblocked(recoverable, with a note). Batch width is bounded by the soft batch-size limit (ADR-0026), and a batch beyond that bound queues — distinct from the WIP cap. See SOP §5 + ADR-0077.
Retro capture
After reconciliation completes, append the chat-level retro ledger line (SOP §14.1) — from the main thread, never from a lane. Always exits 0, never blocks the chat; skip silently if the script is absent.
node _00-Project-Management/93-Scripts/retro-capture.js --level chat --id <CHAT-NN> --phase <EPIC-NN> [--stories <n>] [--halts <n>] [--lanes parallel] [--wall-clock-s <n>] [--dispatch-overhead-s <n>] [--fallback-fired|--no-fallback-fired] [--friction "…"] [--artefact-gap <ID>] [--kit-signal "…"]
--lanes parallel— the one field that distinguishes this command's line fromexecute-batch's, and the whole point of capturing it: it is what lets the rollup compare the two lane strategies.--fallback-firedwhen the ADR-0081 nesting wall forced the orchestrator to run a chat itself in the main thread instead of dispatching it.--dispatch-overhead-scovers fan-out and the serialised reconciliation, which is where this command's overhead actually lives.- Sub-agents write no ledger lines. Lanes never flip board status, and capture follows the same single-writer rule as reconciliation.
- This section covers the chat level only. The per-item lines are written during reconciliation stage 3, one per flip — see Step 4. Both come from the main thread.
Output rules
- Status changes are THIS command's job (it is not dry-run — that's
execution-strategist). - Per-story commit messages:
STORY-NN.M.PP — <imperative>(applied by the main thread in reconciliation, not by the sub-agents).
Relationship to subagent-per-batch (sequential-across-phase)
This skill's fan-out is the parallel-within-batch axis (concurrent lanes, disjoint-file safety) — distinct from, and composable with, the sequential-across-phase subagent-per-batch pattern in SOP §18, which chains whole chats one at a time across a phase.
Next command
Next: /tandem:close-phase
When every chat in the phase is executed, close the phase (retrospective + gated merge). Do
not hand a finished batch to run-testplan — the lanes above already ran every story's
testplan inside the fan-out; re-running them after the batch does each step twice.