Parallel Orchestrate Skill
A user invocation (/parallel-orchestrate <parallel-manifest-path>) forks the
parallel-orchestrator agent with this procedure in context. The parallel manifest path (or
parallel slug) for this run is:
$ARGUMENTS
This skill frames work for the parallel-orchestrator agent, parallel to how
.claude/skills/orchestrate/SKILL.md frames work for orchestrator. It documents manifest
consumption, cohort consumption and ordering, the cohort barrier and max_concurrency slot
filling, the per-item branch and worktree lifecycle, the child kickoff parameter, model selection,
per-item merge to main, per-item merge-conflict handling, worktree cleanup,
parallel-status.md maintenance, checkpoint persistence, and completion, so that the procedure is
not re-derived ad hoc on each parallel run.
Each item on this surface ships independently: an item's pull request targets main directly.
There is no integration branch, no final integration pull request, and no fan-in path anywhere in
this procedure. Scheduling order comes only from computed blast-radius contention recorded in the
cohort table; there is no depends_on field on this surface.
Every section below is self-contained. A cross-reference names another section by its exact
heading text, never by position or number, so a section can be added or extended without
reflowing, reordering, or renumbering anything else in this file. The sections
## Mutation Protocol (F6), ## Enforcement Hooks (F7), and ## Radius Drift Detection (F8)
are reserved for the wave-4 features named in them: each is appended to by its own feature and
must not be relocated.
Prerequisites
Before proceeding, parallel-orchestrator must:
- Read
CLAUDE.mdfor repository tone policy and architectural context. - Read the applicable
.claude/rules/files for the languages in scope, including.claude/rules/parallel-orchestration.md, which is the schema authority for this surface. - Read the policy files listed in the compliance reading order section of
CLAUDE.md.
Parallel Manifest Consumption
The run manifest is the YAML frontmatter of docs/features/parallel/<slug>/parallel.md, authored
by parallel-planner on the planner-owned branch parallel/<slug>-plan. That branch is not an
integration branch: no item branch merges into it, it merges into no item branch, and it holds
only run-level artifacts under docs/features/parallel/<slug>/.
This section is not a schema authority. The manifest schema, the checkpoint schema, and all
nine parallel enums are defined once as prose invariants in
.claude/rules/parallel-orchestration.md (manifest invariants M1 through M7) and are enforced by
the F3-owned validators scripts/dev_tools/parallel_manifest_contract.py and
scripts/dev_tools/validate_parallel_orchestrator_state.py, with the manifest half reachable on the
destination-runtime path as bash .claude/lib/bash/validate-parallel-manifest.sh. This is a deliberate delta from
.claude/skills/epic-orchestrate/SKILL.md, whose manifest section carries its schema inline. Read
the schema from the rule file and the validators; consume it here and never redefine or extend it.
Consumption rules:
issue_numis the primary key for every item reference. It is the stable GitHub issue number, so it does not drift when an item's folder moves fromdocs/features/active/todocs/features/completed/.- Read
mode(closedoropen, defaulting toclosed),max_concurrency(an integer from 1 through 32, defaulting to 4), and each item's identity and state:feature_folder,kind,state, andblast_radius. - The manifest is read-only to
parallel-orchestrator. It is static input authored byparallel-planner: never write it, rewrite it, or back-fill a field into it. - The manifest carries no
depends_onkey at any level and no top-levelintegration_branchkey. Presence of either is an explicit rejection, not a tolerated extra field. - A malformed manifest is rejected before any kickoff, recorded as a synthetic Blocking finding in
the checkpoint. Do not guess a repair, do not silently skip the offending item, and do not launch
a partial cohort. Validate with the destination-runtime bash entry point, which needs no Python
interpreter and is published by push-down alongside
.claude:bash .claude/lib/bash/validate-parallel-manifest.sh docs/features/parallel/<slug>/parallel.md. Its non-zero exit is the rejection signal and its printed error list, one error per line on stdout, is the content of the Blocking finding; exit 1 means the manifest is invalid and exit 2 means the file is unreadable or uses a YAML construct outside the supported subset. Consumemodeandmax_concurrencythrough the same entry point's--print-modeand--print-max-concurrencysubcommands rather than reading the frontmatter directly.validate_parallel_manifest_textinscripts/dev_tools/parallel_manifest_contract.pyremains the repository authority and the parity reference. Manifest validation is deliberately not an MCP artifact type.
Cohort Consumption and Ordering
cohorts[] is an input, not a computation. Each entry has the shape
{ index, generation, item_keys[] } and is seeded by parallel-planner at generation: 0 from
the deterministic Welsh-Powell coloring in scripts/dev_tools/parallel_cohort_computation.py.
parallel-orchestrator consumes the recorded partition and never computes or recolors one:
- Cohort computation belongs to the cohort-scheduler library that
parallel-plannercalls. - Recoloring after a membership change belongs to F6; recoloring after a drift event belongs to F8.
- Read
recolor_generationand schedule only from the cohorts whosegenerationequals it.
Items within a cohort are non-conflicting by construction — a cohort is an independent set in the
conflict graph — so they may branch from the same main tip and may merge in any order. After one
same-cohort item merges, another same-cohort item's pull request, based on the older tip, remains
mergeable, and GitHub produces a merge commit.
The conflict graph the cohorts project was computed by a contention relation that fails closed. Since the blast-radius corrections of issue #452 (merged as PR #453), that relation reports more contention rather than less: separator-free repository-root shared surfaces are reached from plan and specification text, and path comparison honours listed-directory prefixes on both sides. Build on that corrected behaviour. Never narrow a radius, re-derive a partition, or reinterpret an edge in order to combine two cohorts or widen a launch batch.
Cohort Barrier and Max-Concurrency Slot Filling
Two independent controls govern every launch. The cohort barrier governs when an individual item
may start. max_concurrency governs how many eligible items run at once. Neither substitutes for
the other.
Cohort barrier (per-edge). An item may start only when every conflicting neighbour
(conflict_edges[]) that sits in a strictly prior current-generation cohort has merge_status of
merged or worktree_removed. ci_green does not satisfy the barrier: the pull request is not
merged, so its work is not on main. Same-cohort and later-cohort neighbours do not hold an item
back, and items with no conflicting prior-cohort neighbour may start regardless of other cohorts'
progress. The barrier is a predicate over one item's own conflict edges, not a global gate over
whole cohorts.
Evaluate the predicate only against durable state read from git worktree list --porcelain,
git branch, and gh pr view --json state,mergedAt,headRefOid — never from an in-memory
completion notification. A blocked item (blocked_ci_loop_limit or blocked_drift) is neither
merged nor worktree_removed, so it holds every conflicting later-cohort neighbour and,
transitively, the tail of its own conflict component; items outside that component are unaffected.
current_cohort is a progress indicator, not a gate. current_cohort is the LOWEST
current-generation cohort index that still contains a non-terminal, non-withdrawn item. It is
recomputed and written only on durable confirmation from git worktree list --porcelain,
git branch, and gh pr view --json state,mergedAt,headRefOid — never from an in-memory
completion notification. It gates nothing: no item's eligibility is decided by comparing it to
current_cohort, because eligibility is the per-edge predicate above. It is reported in the status
document, is the base index the mutation engine recolors from, and is bounded by rule invariant 14,
whose text is unchanged. Because the barrier is per-edge, in-flight items are not confined to
current_cohort; the highest current-generation index any pinned item occupies is a separate value
(highest_pinned_cohort, see ## Membership Mutation Protocol (F6)).
Safety argument. An item that starts under the per-edge rule while a non-conflicting
prior-cohort item is still open branches from a main that lacks only non-conflicting merged work.
That is byte-for-byte the situation the same-cohort merge-order text above already accepts as safe:
"Items within a cohort are non-conflicting by construction — a cohort is an independent set in the
conflict graph — so they may branch from the same main tip and may merge in any order." The
per-edge barrier extends that accepted situation across cohort boundaries without weakening it,
because the only work the starting item can be missing is work it does not conflict with. That
same-cohort text is unchanged by this rule.
Availability argument. Under a global barrier a single blocked_ci_loop_limit or
blocked_drift item halts every lane, because no item of the next cohort may start until every
item of the current one is terminal. Under the per-edge rule the blocked item holds only its own
conflict component's tail: its conflicting later-cohort neighbours, and transitively theirs.
Unrelated lanes keep advancing. That is the difference between one stuck item stalling a 13-lane
run and one stuck item stalling one lane.
max_concurrency slot filling. max_concurrency caps the number of simultaneously in-flight
items independently of cohort size: a cohort of twelve items executes at most max_concurrency
items at a time. Fill slots in ascending item-key order, keyed on issue_num, and refill each
freed slot with the next unstarted item of the current cohort in that same ascending item-key
order. A cohort larger than max_concurrency therefore launches in several batches, each from the
main tip recorded for that batch. The batching is a pure function, reached on the
destination-runtime path as
bash .claude/lib/bash/compute-concurrency-batches.sh --keys "<k1> <k2> ..." --max-concurrency <n>.
It prints a compact JSON array of arrays, returns the batches in order, and sorts the keys itself,
so determinism does not depend on caller ordering.
compute_concurrency_batches(cohort_item_keys, max_concurrency) in
scripts/dev_tools/parallel_cohort_computation.py remains the repository authority and the parity
reference.
Mechanical enforcement of the barrier is F7 scope, not this feature's. F7 delivers a two-layer
design, because no single PreToolUse hook can validate a batch of concurrent Agent calls: hooks
fire per call with no cross-call state visibility.
- Layer 1, per-call deterrent:
.claude/hooks/enforce-parallel-cohort-barrier.ps1, aPreToolUsehook on theAgentmatcher. It fires whensubagent_type == "orchestrator"and the serialized prompt carries the parallel kickoff marker, resolves the target item, readsartifacts/orchestration/parallel-orchestrator-state.json, and denies with reasonPARALLEL_COHORT_BARRIER_BLOCKEDunless every conflicting item in a prior cohort ismergedorworktree_removed. - Layer 2, retrospective backstop: a cohort-ordering invariant inside
validate_parallel_orchestrator_state_text, enforced atparallel-orchestratorSubagentStoptime, appendingPARALLEL_COHORT_BARRIER_VIOLATIONwhen the ordering invariant is violated.
Neither layer is shipped by this feature; both are named here so the obligation is legible to an operator and to the F7 planner. Until F7 lands, the barrier is enforced by this procedure alone.
The two layers fail closed differently, and the difference is deliberate. Do not read either layer's silence as permission.
- Layer 1 is PROSPECTIVE and evaluated per launch, so it denies fail-closed on every condition that
leaves the target's own eligibility unknowable: a missing or unparseable checkpoint, an unresolved
feature-folder token, a missing
items[]record for the target, a target with no current-generation cohort assignment, a missing neighbour record, and a missing neighbourmerge_status. Its one permissive case is neighbour-side: a NEIGHBOUR that carries no current-generation cohort assignment is skipped rather than denied, because such a neighbour sits in no prior cohort and therefore constrains nothing. - Layer 2 is RETROSPECTIVE and evaluated per edge, so it is deliberately silent on an edge it cannot
judge; a malformed edge is invariant 15's to report, not the barrier's. It applies three readings
of the same edge. The STRUCTURAL reading rejects two conflicting items colored into the same
current-generation cohort outright — a violation Layer 1 has no counterpart for, because Layer 1
only ever asks about strictly prior cohorts. The STATUS reading is the retrospective
contrapositive of the per-edge launch rule. The TEMPORAL reading rejects
merged_at(earlier) > worktree_created_at(later), and degrades to the status reading alone when either timestamp is absent or is not a string.
Per-Item Branch and Worktree Lifecycle
- Run one
git fetch origin mainimmediately before each launch batch, so every item in that batch branches from the same current remotemaintip rather than from a stale local ref. The unit is the launch batch, not the cohort: under the per-edge barrier a cohort's items become eligible at different times, and a cohort larger thanmax_concurrencylaunches in several batches, so a per-cohort fetch would leave later batches on a stale tip. Record the fetched tip for each batch. - Each item's worktree is created by that item's delegation spawn,
Agent(orchestrator, isolation: "worktree", run_in_background: true), branched fromorigin/main. Do not create or check out item worktrees by hand. parallel-plannercreated and pushed each item's feature branch fromorigin/mainat preparation time, and that branch is reused unchanged as the item's execution branch, so the spawn checks out an existing branch whose base isorigin/main. Because that base may be older than the fetched tip, the item's own run reconciles its branch against the fetchedorigin/maintip at execution start. Real path overlap that survives that reconciliation is drift; the conflict outcome is handled per## Per-Item Merge-Conflict Handling, and drift recording itself belongs to F8.- Each item's pull request base branch is
main. Recordworktree_path,branch_name,pr_number, andpr_urlfor the item, and setmerge_status: worktree_createdat spawn. - Worktree removal is the terminal step of this lifecycle and is specified in
## Worktree Cleanup.
No integration branch is created, fetched, pushed, or referenced at any point in this lifecycle. There is no final integration pull request and no fan-in path: the epic surface's integration-branch lifecycle has no counterpart on this surface, and its absence is structural rather than an omission.
Parallel-Mode Kickoff Parameter
When parallel-orchestrator delegates an item to Agent(orchestrator), the delegation prompt
carries exactly these five elements.
The literal marker line:
Parallel mode: true. parallel_slug: <slug>. parallel_checkpoint_path: artifacts/orchestration/parallel-orchestrator-state.json. cohort_index: <n>. PR base branch MUST be main; pass --base main to gh pr create.The token
Parallel mode: truemust appear exactly: it is the marker F7's Layer 1 barrier hook matches on. The clausePR base branch MUST be mainis the child's explicit base-branch instruction, recorded as prompt text rather than left to a base-branch ancestry heuristic.The item's active feature folder path, written literally as
docs/features/active/<basename>. The child needs it for its own operation, and F7's Layer 1 hook resolves the target item by scanning the prompt for exactly that path shape, so the path is emitted as a bare path token.The canonical issue number line, which is the item key.
The item's committed
plan-path, together with the resume instruction: resume at atomic execution from that plan rather than re-running promotion, research, or planning. The item's prepared feature folder and approved atomic plan are already committed and preflight-clear on the item's own pushed feature branch.The model-budget marker line
model_budget.fable_policy: <disabled|available|preferred>.
Spawn parameters, passed on the Agent call and never written into the prompt text:
isolation: "worktree", run_in_background: true, branch base origin/main, and model equal to
that item's model routing receipt's resolved model.
Negative obligations on the prompt:
- It never carries
Preparation mode: true. Preparation fan-out belongs toparallel-plannerand to F6's admission path; an item that reaches this procedure is already prepared. - It never carries the epic-mode marker line that
.claude/skills/epic-orchestrate/SKILL.mdemits — the marker whose text isEpic modefollowed by the valuetrue— so the epic wave-barrier hook does not fire on a parallel child. That marker's value is deliberately not written out anywhere in this file, so no file this feature delivers carries an epic-mode marker string. - It contains no instruction for the child to merge its own pull request. The parent performs each
item's merge, as specified in
## Per-Item Merge to Main (Merge-on-Green).
Excluded from the prompt as parent-side concerns: the item's declared blast radius,
max_concurrency, and mode. Keeping the prompt minimal preserves the child contract unchanged.
Model Selection
When parallel-orchestrator delegates an item to Agent(orchestrator), the prompt appends the
session model-budget kickoff marker line, following the existing kickoff-marker pattern:
model_budget.fable_policy: <disabled|available|preferred>.
The item's own orchestrator reads that line and applies the two-axis model-selection mechanism
documented in .claude/skills/orchestrate/SKILL.md (## Model Selection): it assesses a
judgment-based complexity_band, records complexity_assessments[] and
model_routing_receipts[], and resolves each of its own delegations' model tier under the given
fable_policy. The canonical, tested reference implementations are Get-ComplexityFloor and
Resolve-DelegationModel in .claude/lib/model-routing/ModelRouting.psm1. Default fable_policy
is disabled when the marker is absent.
parallel-orchestrator spawns exactly one delegation channel, Agent(orchestrator), one
delegation per item. It applies the same per-delegation resolution to that channel and passes
model equal to the routing receipt's model on the spawn call. It MUST NOT omit model — an
omitted model falls back to the delegate's frontmatter default, opus, which suppresses a
fable resolution — and MUST NOT hard-code model=opus in a way that overrides the resolved
routing model.
route is never an input to model selection. route remains file-count driven and governs only
agents, skills, and MCP tools. A skill whose frontmatter context field holds the value fork
inherits the parent model and ignores a model override, so model selection applies to agent
delegations, not to fork-routed skill invocations.
Per-Item Merge to Main (Merge-on-Green)
The parent — parallel-orchestrator — performs each item's merge to main. The child contract is
unchanged: .claude/skills/orchestrate/SKILL.md is not modified by this feature. There is no
parallel_mode clause in its step 9, no parallel_merge object in the child checkpoint, and no
additional condition on the child's PR Creation Gate.
Procedure, per item:
- The item's child orchestration runs unmodified, with
epic_modefalseor absent, and finishes at its own DONE. The child's PR Creation Gate condition 6 already requiresci_gate.conclusion == "success", so at child DONE the item's pull request is open againstmainand its checks are green. The child is never instructed to merge its own pull request. - On child completion, durably confirm pull-request state and check conclusion with
gh pr view --json state,mergedAt,headRefOid, and withgh pr checkswhen the check conclusion must be re-read — never from an in-memory completion notification — then recordmerge_status: ci_green. - Execute
gh pr merge --merge <PR>for that item's pull request, whose base ismain. - On success, record
merge_commit_sha,merged_at, andmerge_status: merged, then regeneratedocs/features/parallel/<slug>/parallel-status.md. - On a merge failure caused by a conflict, follow
## Per-Item Merge-Conflict Handling.
Merge-gate authorization. .claude/hooks/enforce-epic-merge-gate.ps1 is a project-wide
PreToolUse Bash-matcher hook that denies any gh pr merge --merge unless a checkpoint satisfies
one of its allow conditions; its block reason is EPIC_MERGE_GATE_BLOCKED. The gate now authorizes
a parallel per-item merge when the parallel-orchestrator checkpoint has route_id == "parallel",
the target item's merge_status == "ci_green", and, when a PR number is named, it matches the
item's pr_number. Step 3 above is therefore permitted for a legitimate parallel merge; a missing,
unreadable, or invalid parallel checkpoint, a target item whose merge_status is not ci_green, or
a PR number that matches no item still fails closed with EPIC_MERGE_GATE_BLOCKED.
Branch protection on main affects only the pacing of step 3, not its ownership: if main
requires branches to be up to date, an automated gh pr update-branch plus re-green cycle is
inserted between same-cohort merges, which serializes those merges in practice while remaining
unattended.
Per-Item Merge-Conflict Handling
Remediation is child-owned and parent-initiated. The conflict is always between one item's own
branch and origin/main; there is no integration branch and therefore no fan-in conflict path on
this surface.
Project-file conflicts are resolved by the parent first (issue #643); the numbered steps below are the escalation path.
- (a) On a conflicted
gh pr merge --merge, the parent runs the two commandsgit -C <worktree_path> fetch origin mainandgit -C <worktree_path> merge --no-commit origin/main. - (b) The parent then runs
pwsh -NoProfile -File .claude/lib/project-file-merge/Resolve-MergeableConflict.ps1 -Worktree <worktree_path>and parses its single JSON object. Aresultofescalatemakes the parent rungit -C <worktree_path> merge --abortand continue with step 1 below, includingescalate_pathsin the Blocking finding. - (c) On
resolvedthe parent stages and commits the resolved paths with a message body listing every entry added from each side and every version choice, then runs the three commandsdotnet tool restore --tool-manifest <worktree_path>/.config/dotnet-tools.json,dotnet csharpier check <worktree_path>, anddotnet build <worktree_path>/<solution>, each with path arguments rather thancd. A failing check reverts withgit -C <worktree_path> reset --hard HEAD~1and escalates with the tool output as the finding. - (d) On success the parent pushes the item branch, records
mergeable_conflicts_resolvedon the item, setsmerge_status: pr_open, regeneratesparallel-status.md, and re-enters## Per-Item Merge to Main (Merge-on-Green)at the durablegh pr checksconfirmation step.
- On a conflicted
gh pr merge --merge, the parent detects the failure and re-delegates that item's child orchestration, passing the conflict signal and the instruction to resolve againstorigin/main. The conflict capture and the finding write both belong to the child'satomic-executor, which works inside the item's own worktree, the only working tree holding the item's branch. It runsgit fetch origin main, thengit merge --no-commit origin/main, and on non-zero exit capturesgit diff --name-only --diff-filter=Ufor the conflicted-file list together with the raw conflict-marker content of each conflicted file. The child'satomic-executorthen writes that evidence as a synthetic Blocking finding to the item's ownremediation-inputs.<timestamp>.mdin the item's active feature folder underdocs/features/active/, not to the run's parallel folder. Assigning both the capture and the finding write to the child's chain matches.claude/skills/epic-orchestrate/SKILL.md, whose equivalent capture and finding write also belong to the child'satomic-executor. - The parent re-delegates that item's child orchestration. The child processes the finding through its unmodified R1 through R5 remediation loop exactly as it processes any local Blocking finding. No new remediation loop is introduced by this procedure.
- The child's
remediation_passcounter is shared with its local-finding and CI-failure passes, with the cap of 3, unmodified. - Each remediated pass ends again at child DONE with the pull request open and CI green, after
which the parent retries the merge per
## Per-Item Merge to Main (Merge-on-Green). During remediation the item'smerge_statuslegitimately remainspr_openorci_green: themerge_statusenum is F3-owned, carries no conflict member, and is not extended by this feature. - On loop exhaustion, the parent records the terminal
merge_status: blocked_ci_loop_limitfor the item; the child's own checkpoint retains its precise blocked status. A blocked item is neithermergednorworktree_removed, so under the per-edge barrier defined in## Cohort Barrier and Max-Concurrency Slot Fillingit holds back exactly its own conflicting later-cohort neighbours — and, transitively, the tail of its own conflict component. Every item outside that component, including every item of a later cohort that shares no conflict edge with it, remains eligible and continues to launch.
Boundary with F8: a merge conflict between two same-cohort items is evidence that the declared
blast radius under-reported, and this feature records the child's blocked or remediated outcome
only, leaving drift recording in drift_events[], quiesce of admission, conflict recomputation
against the observed radius, and requeue of the later-started item to F8.
Worktree Cleanup
After an item reaches merge_status: merged and that state is durably confirmed by
gh pr view --json state,mergedAt,headRefOid, the parent — running from the main repository
checkout, never from inside a child worktree — issues git worktree remove <worktree_path>. On
success it records merge_status: worktree_removed and worktree_removed_at, then regenerates
docs/features/parallel/<slug>/parallel-status.md.
Mechanical gating of this command for parallel worktrees is delivered. Both halves have landed.
.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 exists and is registered in
.claude/settings.json on the Bash matcher; it authorizes a removal from the
parallel-orchestrator checkpoint and owns the block reason
PARALLEL_WORKTREE_REMOVAL_BLOCKED. .claude/hooks/enforce-epic-worktree-removal-gate.ps1 is a
project-wide PreToolUse Bash-matcher hook registered alongside it whose block reason is
EPIC_WORKTREE_REMOVAL_BLOCKED; it now carries a second, parallel allow-branch, so it authorizes a
removal either from a matching epic checkpoint features[] record or from a parallel-orchestrator
checkpoint whose route_id is parallel and whose matching items[].worktree_path record has
merge_status in {merged, worktree_removed}. An unreadable checkpoint, an absent record, or a
non-authorizing merge_status still denies on both branches. The coordination matters because
PreToolUse denials are conjunctive: a new allow-hook alone could not have overridden the epic
gate's independent deny, which is why the epic gate itself had to gain the parallel branch.
The parallel-orchestrator-surface feature (F7) shipped no hook file and made no
.claude/settings.json change of its own.
Documentation Maintenance Boundaries
docs/features/parallel/<slug>/parallel-status.md is a generated projection of
artifacts/orchestration/parallel-orchestrator-state.json. It is regenerated in full, is never
hand-authored, and is never treated as an input. It is never the source of the cohort table and
never the source of the schedule: the run manifest and the checkpoint are authoritative. Generate it
from the template docs/features/templates/parallel/parallel-status.md.
Header block fields: parallel_slug, mode, max_concurrency, current_cohort,
recolor_generation, last_updated.
Item table: one row per items[] entry, carrying issue_num, feature_folder, cohort index,
lifecycle state, merge_status, pr_url, merge_commit_sha, and the item's lifecycle
timestamps. The cohort column takes the place of the epic status document's wave column.
Cohort table: a projection of cohorts[] { index, generation, item_keys[] }, so a recolored
schedule stays traceable by generation.
Read-only projections of F3-owned arrays, rendered by this feature but never written by it: section
## Conflict Edges projects conflict_edges[]; section ## Mutations projects mutations[],
whose rows appear only once F6 populates that array; section ## Drift Events projects
drift_events[], which only F8 populates. An empty array renders an empty section rather than an
omitted one.
The projection's ## Mergeable Conflicts Resolved heading renders
items[].mergeable_conflicts_resolved: for each entry the path, the resolved_at stamp, the
merged_against ref, the merge_commit_sha, the entries added from each side, and the version
resolutions. The same resolution is cited in three further places: the body of the resolution
commit message, the pull-request body, and an evidence artifact at
docs/features/parallel/<slug>/evidence/other/mergeable-conflicts.<yyyy-MM-ddTHH-mm>.md carrying
Timestamp, Command, EXIT_CODE, and the script's JSON output verbatim.
Regeneration boundaries — regenerate at each of the following, not only at final completion:
- Run kickoff, seeding the initial projection from the manifest and the seeded cohorts.
- Every item
stateormerge_statustransition. - Every cohort transition, meaning every
current_cohortincrement. - Every
recolor_generationincrement. - Every append to
mutations[]. - Every append to
drift_events[]. - Every append to an item's
mergeable_conflicts_resolved. - Run completion in
closedmode, or run close inopenmode.
Defining the mutations[] and drift_events[] appends as regeneration boundaries here means F6 and
F8 need no amendment to these projection rules.
Parallel-Level Checkpoint
This section is consumption documentation only. The checkpoint schema is owned by F3, defined once
as prose invariants in .claude/rules/parallel-orchestration.md, and enforced by
scripts/dev_tools/validate_parallel_orchestrator_state.py. Consume that schema;
add no field to it that .claude/rules/parallel-orchestration.md does not declare,
and extend no enum in it. mergeable_conflicts_resolved is the one declared optional item
field.
Fields parallel-orchestrator writes to artifacts/orchestration/parallel-orchestrator-state.json:
objective, route_id: "parallel", parallel_slug, parallel_manifest_path,
parallel_status_doc_path, mode, max_concurrency, completed_steps, next_step,
last_updated, current_cohort, recolor_generation, cohorts[], items[] — each item entry
carrying issue_num, feature_folder, state, blast_radius, worktree_path, branch_name,
pr_number, pr_url, merge_status, merge_commit_sha, and the lifecycle timestamps — and the
three receipt arrays delegation_receipts[], skill_receipts[], and mcp_call_receipts[],
populated with the parallel route's required names from config/orchestration-routing.json.
The merge_status enum has exactly eight members: not_started, worktree_created, pr_open,
ci_green, merged, worktree_removed, blocked_drift, and blocked_ci_loop_limit. An absent
merge_status is treated as not_started.
The transition chain this feature writes: not_started to worktree_created at the item's
delegation spawn; worktree_created to pr_open when the child reports its pull request open;
pr_open to ci_green on durable confirmation after child DONE; ci_green to merged when the
parent's merge succeeds; and merged to worktree_removed after gated removal.
blocked_ci_loop_limit is the mapped terminal for an exhausted remediation loop.
Never written by this feature: blocked_drift, which only F8 writes; conflict_edges[], seeded by
parallel-planner and recomputed only by F8; mutations[], which only F6 appends to; and
drift_events[], which only F8 appends to. These are read for projection and for scheduling context
and are otherwise untouched.
The checkpoint is a cache of durable state, not the source of truth. Every recorded field is
re-derivable on resume from git worktree list --porcelain for worktree existence and path,
git branch for branch existence and name, and gh pr view --json state,mergedAt,headRefOid for
pull-request state, merge time, and merge commit. Where the checkpoint disagrees with those three
commands, the commands win and the checkpoint is rewritten from them.
Validate through mcp__drm-copilot__validate_orchestration_artifacts with
artifact_type: "parallel-orchestrator-state". At the completion gate, pass the
require_complete argument on that same MCP call; no repository-local Python
interpreter is required at a destination runtime.
Completion Requirements
Completion is mode-dependent. Read mode from the manifest: it is closed or open and defaults
to closed.
In closed mode, parallel-orchestrator must not report completion until all of the following
hold:
- Every non-withdrawn item has
merge_statusofmergedorworktree_removed, each durably confirmed bygitandghrather than by an in-memory completion notification. docs/features/parallel/<slug>/parallel-status.mdhas been regenerated a final time and reflects the completed state.- The parallel checkpoint passes validation with
require_completeforartifact_type: "parallel-orchestrator-state". - Each item's acceptance criteria have been checked off in that item's own acceptance-criteria
source files by that item's own run, per the
acceptance-criteria-trackingskill.
In open mode there is no automatic completion. The run is a standing queue and terminates only via
/parallel-close, which is owned by F6 and is neither specified nor shipped by this feature. Do not
synthesize a completion condition for an open-mode run.
No completion condition involves a run-level pull request. There is no final integration pull request on this surface, so completion keys on the per-item terminal states above and on nothing else.
Mutation Protocol (F6)
A parallel run is mutable while it executes. Three slash commands mutate it, and each one appends
exactly one mutations[] entry to the parallel-orchestrator checkpoint on success:
/parallel-add <issue|potential-entry>—.claude/skills/parallel-add/SKILL.md. Admits one new item: the item entersproposed, is prepared through a preparation-mode childAgent(orchestrator)run reusing theroute_id: preparationcontract unchanged, its conflict edges are computed against ALL items including in-flight ones, and the admission decision places it in the current cohort only when it conflicts with no member of that cohort, pinned or unstarted, and otherwise defers it and recolors the unstarted subgraph./parallel-remove <item> [--disposition detach|abandon]—.claude/skills/parallel-remove/SKILL.md. Removes one item per the state-dependent behavior table: an unstarted item is withdrawn and the unstarted subgraph is recolored; an in-flight item requires an explicit disposition and is rejected without one; a merged item is rejected./parallel-close <parallel-slug>—.claude/skills/parallel-close/SKILL.md. Terminates anopen-mode run. Rejected while any item isin_flight.
Every mutation re-derives durable state (git worktree list --porcelain, git branch,
gh pr view) before it is applied, because the checkpoint is a cache of durable state and not the
source of truth. A rejected mutation appends no entry and changes no state.
The decision logic for all three commands is the pure engine
scripts/dev_tools/parallel_mutation_protocol.py. It decides; it never applies. Item keys are
integers (items[].issue_num) everywhere on this surface.
Pinning invariant
In-flight items are pinned. Scheduling is recomputed only over the not-yet-started subgraph, and
recoloring is a pure function of (remaining subgraph, pinned set, pinned cohort indices).
The recolor function takes the induced subgraph of unstarted items (states proposed, admitted,
prepared, scheduled), the pinned set (state in_flight), the current generation, and — as two
further scheduling inputs — current_cohort and highest_pinned_cohort, the highest
current-generation cohort index occupied by any pinned item. Both are derived from re-verified
durable state. Two inputs are required because the per-edge barrier does not confine in-flight items
to one index: an item starts as soon as its own conflicting prior-cohort neighbours are terminal, so
the pinned frontier can span several cohorts. It returns cohort assignments for unstarted items
ONLY: the returned mapping's key set equals the unstarted set exactly and contains no pinned key. A
pinned item is therefore absent from the result rather than reassigned, and that absence IS the
guarantee that a mutation never moves work already running.
Pinned-barrier offset. The returned indices are ABSOLUTE checkpoint cohort indices at or above
current_cohort, and strictly above highest_pinned_cohort whenever any conflict edge joins an
unstarted item to a pinned item. When no such edge exists the lowest returned index equals
current_cohort exactly, so unstarted items may share the running cohort and max_concurrency slot
filling is preserved. Shifting above the highest pinned index — rather than above current_cohort
alone — is what keeps a deferred candidate off the index of any pinned item it conflicts with when
the pinned frontier spans more than one cohort. The offset is a single uniform shift applied to
every color class, so F2's distinct color classes remain distinct cohort indices and independence
within the unstarted set is preserved exactly. When every pinned item sits at current_cohort the
two inputs coincide and the offset is identical to the earlier single-frontier rule.
Write the returned indices VERBATIM into cohorts[].index; never re-base them to zero. cohorts[]
carries exactly ONE current-generation entry per index, so returned keys landing on
…(truncated)