Parallel Add Skill
A user invocation (/parallel-add <issue|potential-entry>) forks the parallel-orchestrator
agent with this procedure in context. The issue reference or potential-entry path to admit is:
$ARGUMENTS
This skill implements the mutation-protocol add operation (spec FR1). It mutates a parallel run
that is already executing: the run's cohorts, its recolor_generation, and its mutations[]
audit log all belong to the parallel-orchestrator checkpoint, and the pinning invariant of the
## Mutation Protocol (F6) section of .claude/skills/parallel-orchestrate/SKILL.md governs what
this operation may and may not disturb. Read that section before applying anything here.
Prerequisites
Reject a pending or not-started run with guidance to consolidate the initial set through /parallel-plan. Admit exactly one item only after execution has started in an open run; closed runs are not eligible.
A parallel run is in progress and artifacts/orchestration/parallel-orchestrator-state.json
tracks its parallel_slug. This skill does not start a run; use /parallel-plan and
/parallel-run for that.
$ARGUMENTS names exactly one item: a GitHub issue number or reference, or a path to an entry
under docs/features/potential/. An argument naming more than one item is rejected; admit one
item per invocation so each admission decision and its mutation entry stay attributable.
Re-Derive Durable State Before Applying Anything
The checkpoint is a CACHE of durable state, not the source of truth
(.claude/rules/parallel-orchestration.md, Cache Doctrine). Before computing any admission
decision, re-derive the run's true state and rewrite the checkpoint from it when they disagree:
git worktree list --porcelain — worktree existence and path per item.
git branch — branch existence and name per item.
gh pr view <pr> --json state,mergedAt,headRefOid — pull-request state and merge outcome per
item.
The in-flight set this operation reads is derived from that re-verified state. Admitting against a
stale in-flight set is the one way this operation can violate the pinning invariant, so the
re-derivation is mandatory and is not an optimization to skip when the checkpoint "looks current".
Procedure
Enter proposed. Add the item to items[] in state proposed with its issue_num as the
primary key. Item keys are integers throughout this surface; there is no string key.
Prepare the item. Run preparation through a preparation-mode child Agent(orchestrator)
run, reusing the existing route_id: preparation contract UNCHANGED: promotion, research,
spec.md, user-story.md, the atomic plan, and preflight clearance. Do not fork a variant
contract for parallel admission. Preparation yields the item's DECLARED blast radius, and only
the planner-computed declared radius is authoritative for scheduling. The item's lifecycle
advances proposed -> admitted -> prepared during this step, recorded as item-state updates in
items[] with the checkpoint's lifecycle timestamps.
Compute conflict edges over ALL items, including in-flight ones. Invoke the contention
relation Test-BlastRadiusConflict from the destination-runtime PowerShell port
.claude/lib/blast-radius/BlastRadius.psm1, which is published by push-down and needs no Python
interpreter (the default PowerShell 5.1 execution policy blocks Import-Module of a .psm1
file, so pwsh is mandatory: run as $repoRoot = git rev-parse --show-toplevel; Import-Module (Join-Path $repoRoot '.claude/lib/blast-radius/BlastRadius.psm1') -Force -ErrorAction Stop).
Its two radius arguments are the two items' radius hashtables, not strings, and the third
argument is the required parsed config/blast-radius.json mapping, which push-down publishes
into the destination workspace. That mapping's optional mergeable_paths list is read by
Test-BlastRadiusConflict, which contributes no path_overlap edge for a path matching it
while the path stays in the declared radius. conflicts(a, b, config) in
scripts/dev_tools/compute_blast_radius.py (defined in
scripts/dev_tools/_blast_radius_conflicts.py) remains the repository authority and the parity
reference. Read the verdict from $result['conflict']; do not test the returned hashtable
itself, since it is always truthy under PowerShell boolean coercion, so a bare if ($result)
check treats every pair as conflicting. Map each conflicting pair onto an (int, int) conflict
edge of items[].issue_num values, normalized so a < b. Do not reimplement the relation and do not
compute edges over the unstarted subset only: an in-flight conflict is precisely what the
admission decision turns on.
Decide admission. Call
decide_admission(candidate, conflict_edges, in_flight, current_cohort_members=current_cohort_members)
from scripts/dev_tools/parallel_mutation_protocol.py.
ADMIT_CURRENT_COHORT — the candidate shares no edge with any member of the current cohort,
pinned or unstarted. Admit it into the current cohort. NO recompute occurs and
recolor_generation is unchanged — precisely because the candidate conflicts with no
current-cohort member, so no cohort assignment needs to change.
DEFER_AND_RECOLOR — the candidate shares an edge with at least one member of the current
cohort, pinned or not-yet-launched. Defer it to a future cohort and recolor by calling
recolor_unstarted(unstarted_items, conflict_edges, pinned, current_generation, current_cohort=current_cohort, highest_pinned_cohort=highest_pinned_cohort).
highest_pinned_cohort is derived from re-verified durable state: the highest
current-generation cohort index occupied by any in-flight item.
The recolor is a recompute: recolor_generation increments by exactly one, and it places every
unstarted item at an index at or above current_cohort, strictly above
highest_pinned_cohort when a pinned conflict exists.
Derive current_cohort_members from the re-verified durable state, not from the cached
checkpoint: it is the full membership of the current-generation cohort at current_cohort,
INCLUDING its not-yet-launched scheduled members. Derive current_cohort from that same
re-verified state; it is F3's top-level current_cohort field, the lowest current-generation
cohort index still holding a non-terminal item. Under the per-edge barrier an in-flight item is
not confined to that index, so derive highest_pinned_cohort from the same re-verified state as
well: the highest current-generation cohort index occupied by any in-flight item. Both matter
because max_concurrency caps simultaneously in-flight items independently
of cohort size and refills each freed slot from the same current cohort — see
## Cohort Barrier and Max-Concurrency Slot Filling in
.claude/skills/parallel-orchestrate/SKILL.md — so the current cohort durably holds scheduled
members that a candidate can contend with.
A conflict with an unstarted member of the CURRENT cohort defers the candidate and recolors,
because the next max_concurrency batch would otherwise launch the two concurrently. A conflict
with an unstarted item OUTSIDE the current cohort does not defer: the cohort barrier keeps the
two from running concurrently, so the coloring's existing separation already resolves it.
Apply the recolor result. Write RecolorResult.cohort_assignments into cohorts[] and set
the top-level recolor_generation to RecolorResult.generation. The result's key set equals the
unstarted set exactly and contains no pinned key: no in-flight item changes cohort or state as a
result of this admission. Verify that before writing, and stop rather than write a result that
names a pinned key. The admit branch performs no recolor at all, precisely because the candidate
conflicts with no current-cohort member.
The returned cohort_assignments values are ABSOLUTE cohort indices. Write them VERBATIM
into cohorts[].index; never re-base them to zero. When the lowest returned index equals
current_cohort — the no-pinned-conflict case, where the offset is not applied — the returned
keys at that index are MERGED into the single existing current-generation cohort entry at
current_cohort alongside its pinned members, and are never written as a second cohort entry
carrying the same index, because F3 invariant 13 requires current-generation cohorts[].index
values to be unique (scripts/dev_tools/_parallel_state_structures.py:282-305 — duplicate-index
detection at 282-293, error emission at 301-305).
Append exactly one mutations[] entry, at admission-decision time, built by
build_add_entry from scripts/dev_tools/parallel_mutation_protocol.py:
| Case |
op |
item_key |
prior_state |
new_state |
disposition |
recolor_generation |
| No-conflict admit |
add |
item key |
null |
scheduled |
null |
g (unchanged) |
| Deferred admit |
add |
item key |
null |
scheduled |
null |
g + 1 |
prior_state is null on BOTH add rows. The accompanying prepared -> scheduled transition is
not lost and is not recorded in the mutation entry: it is recorded as an item-state update in
items[], the same mechanism that records proposed -> admitted -> prepared in step 2. The
at timestamp comes from the engine's injected clock seam.
Validate the checkpoint before treating the admission as applied. Run the
validate_orchestration_artifacts MCP tool with artifact_type: "parallel-orchestrator-state". A non-empty error list means the admission was applied
incorrectly; correct the checkpoint rather than proceeding.
Constraints
- One admission per invocation, one
mutations[] entry per successful admission. A failed
preparation appends no entry and leaves items[] without the candidate.
- No field and no enum member is added to
mutations[], conflict_edges[], items[], or any
state or merge-status enum. The nine parallel enums are owned by
.claude/rules/parallel-orchestration.md and are consumed, never extended.
- This operation never moves, restates, or re-derives an in-flight item's cohort or state.
- This operation performs no destructive side effect: it closes no pull request and removes no
worktree. Those belong to
/parallel-remove with --disposition abandon.
Completion Requirements
- Report the admitted item key, the admission outcome, the resulting
recolor_generation, and the
single appended mutations[] entry.
- Report the cohort index the item landed in, and confirm explicitly that no in-flight item's
cohort or state changed.
- Report the checkpoint validation result.
1---2name: parallel-add3description: Admit one new item into a running parallel run — preparation via a preparation-mode child orchestrator run, conflict-edge computation against all items including in-flight ones, and the admission decision that either places the item in the current cohort or defers it and recolors the unstarted subgraph. Appends exactly one mutations[] entry. In-flight items are never moved.4---56# Parallel Add Skill78A user invocation (`/parallel-add <issue|potential-entry>`) forks the `parallel-orchestrator`9agent with this procedure in context. The issue reference or potential-entry path to admit is:1011$ARGUMENTS1213This skill implements the mutation-protocol add operation (spec FR1). It mutates a parallel run14that is already executing: the run's cohorts, its `recolor_generation`, and its `mutations[]`15audit log all belong to the parallel-orchestrator checkpoint, and the pinning invariant of the16`## Mutation Protocol (F6)` section of `.claude/skills/parallel-orchestrate/SKILL.md` governs what17this operation may and may not disturb. Read that section before applying anything here.1819## Prerequisites2021- Reject a pending or not-started run with guidance to consolidate the initial set through `/parallel-plan`. Admit exactly one item only after execution has started in an open run; closed runs are not eligible.2223- A parallel run is in progress and `artifacts/orchestration/parallel-orchestrator-state.json`24 tracks its `parallel_slug`. This skill does not start a run; use `/parallel-plan` and25 `/parallel-run` for that.26- `$ARGUMENTS` names exactly one item: a GitHub issue number or reference, or a path to an entry27 under `docs/features/potential/`. An argument naming more than one item is rejected; admit one28 item per invocation so each admission decision and its mutation entry stay attributable.2930## Re-Derive Durable State Before Applying Anything3132The checkpoint is a CACHE of durable state, not the source of truth33(`.claude/rules/parallel-orchestration.md`, Cache Doctrine). Before computing any admission34decision, re-derive the run's true state and rewrite the checkpoint from it when they disagree:3536- `git worktree list --porcelain` — worktree existence and path per item.37- `git branch` — branch existence and name per item.38- `gh pr view <pr> --json state,mergedAt,headRefOid` — pull-request state and merge outcome per39 item.4041The in-flight set this operation reads is derived from that re-verified state. Admitting against a42stale in-flight set is the one way this operation can violate the pinning invariant, so the43re-derivation is mandatory and is not an optimization to skip when the checkpoint "looks current".4445## Procedure46471. **Enter `proposed`.** Add the item to `items[]` in state `proposed` with its `issue_num` as the48 primary key. Item keys are integers throughout this surface; there is no string key.49502. **Prepare the item.** Run preparation through a preparation-mode child `Agent(orchestrator)`51 run, reusing the existing `route_id: preparation` contract UNCHANGED: promotion, research,52 `spec.md`, `user-story.md`, the atomic plan, and preflight clearance. Do not fork a variant53 contract for parallel admission. Preparation yields the item's DECLARED blast radius, and only54 the planner-computed declared radius is authoritative for scheduling. The item's lifecycle55 advances `proposed` -> `admitted` -> `prepared` during this step, recorded as item-state updates in56 `items[]` with the checkpoint's lifecycle timestamps.57583. **Compute conflict edges over ALL items, including in-flight ones.** Invoke the contention59 relation `Test-BlastRadiusConflict` from the destination-runtime PowerShell port60 `.claude/lib/blast-radius/BlastRadius.psm1`, which is published by push-down and needs no Python61 interpreter (the default PowerShell 5.1 execution policy blocks `Import-Module` of a `.psm1`62 file, so `pwsh` is mandatory: run as `$repoRoot = git rev-parse --show-toplevel; Import-Module63 (Join-Path $repoRoot '.claude/lib/blast-radius/BlastRadius.psm1') -Force -ErrorAction Stop`).64 Its two radius arguments are the two items' radius hashtables, not strings, and the third65 argument is the required parsed `config/blast-radius.json` mapping, which push-down publishes66 into the destination workspace. That mapping's optional `mergeable_paths` list is read by67 `Test-BlastRadiusConflict`, which contributes no `path_overlap` edge for a path matching it68 while the path stays in the declared radius. `conflicts(a, b, config)` in69 `scripts/dev_tools/compute_blast_radius.py` (defined in70 `scripts/dev_tools/_blast_radius_conflicts.py`) remains the repository authority and the parity71 reference. Read the verdict from `$result['conflict']`; do not test the returned hashtable72 itself, since it is always truthy under PowerShell boolean coercion, so a bare `if ($result)`73 check treats every pair as conflicting. Map each conflicting pair onto an `(int, int)` conflict74 edge of `items[].issue_num` values, normalized so `a < b`. Do not reimplement the relation and do not75 compute edges over the unstarted subset only: an in-flight conflict is precisely what the76 admission decision turns on.77784. **Decide admission.** Call79 `decide_admission(candidate, conflict_edges, in_flight, current_cohort_members=current_cohort_members)`80 from `scripts/dev_tools/parallel_mutation_protocol.py`.81 - `ADMIT_CURRENT_COHORT` — the candidate shares no edge with any member of the current cohort,82 pinned or unstarted. Admit it into the current cohort. NO recompute occurs and83 `recolor_generation` is unchanged — precisely because the candidate conflicts with no84 current-cohort member, so no cohort assignment needs to change.85 - `DEFER_AND_RECOLOR` — the candidate shares an edge with at least one member of the current86 cohort, pinned or not-yet-launched. Defer it to a future cohort and recolor by calling87 `recolor_unstarted(unstarted_items, conflict_edges, pinned, current_generation, current_cohort=current_cohort, highest_pinned_cohort=highest_pinned_cohort)`.88 `highest_pinned_cohort` is derived from re-verified durable state: the highest89 current-generation cohort index occupied by any in-flight item.90 The recolor is a recompute: `recolor_generation` increments by exactly one, and it places every91 unstarted item at an index at or above `current_cohort`, strictly above92 `highest_pinned_cohort` when a pinned conflict exists.9394 Derive `current_cohort_members` from the re-verified durable state, not from the cached95 checkpoint: it is the full membership of the current-generation cohort at `current_cohort`,96 INCLUDING its not-yet-launched `scheduled` members. Derive `current_cohort` from that same97 re-verified state; it is F3's top-level `current_cohort` field, the lowest current-generation98 cohort index still holding a non-terminal item. Under the per-edge barrier an in-flight item is99 not confined to that index, so derive `highest_pinned_cohort` from the same re-verified state as100 well: the highest current-generation cohort index occupied by any in-flight item. Both matter101 because `max_concurrency` caps simultaneously in-flight items independently102 of cohort size and refills each freed slot from the same current cohort — see103 `## Cohort Barrier and Max-Concurrency Slot Filling` in104 `.claude/skills/parallel-orchestrate/SKILL.md` — so the current cohort durably holds `scheduled`105 members that a candidate can contend with.106107 A conflict with an unstarted member of the CURRENT cohort defers the candidate and recolors,108 because the next `max_concurrency` batch would otherwise launch the two concurrently. A conflict109 with an unstarted item OUTSIDE the current cohort does not defer: the cohort barrier keeps the110 two from running concurrently, so the coloring's existing separation already resolves it.1111125. **Apply the recolor result.** Write `RecolorResult.cohort_assignments` into `cohorts[]` and set113 the top-level `recolor_generation` to `RecolorResult.generation`. The result's key set equals the114 unstarted set exactly and contains no pinned key: no in-flight item changes cohort or state as a115 result of this admission. Verify that before writing, and stop rather than write a result that116 names a pinned key. The admit branch performs no recolor at all, precisely because the candidate117 conflicts with no current-cohort member.118119 The returned `cohort_assignments` values are **ABSOLUTE cohort indices**. Write them VERBATIM120 into `cohorts[].index`; never re-base them to zero. When the lowest returned index equals121 `current_cohort` — the no-pinned-conflict case, where the offset is not applied — the returned122 keys at that index are **MERGED into the single existing current-generation cohort entry at123 `current_cohort`** alongside its pinned members, and are never written as a second cohort entry124 carrying the same `index`, because F3 invariant 13 requires current-generation `cohorts[].index`125 values to be unique (`scripts/dev_tools/_parallel_state_structures.py:282-305` — duplicate-index126 detection at 282-293, error emission at 301-305).1271286. **Append exactly one `mutations[]` entry**, at admission-decision time, built by129 `build_add_entry` from `scripts/dev_tools/parallel_mutation_protocol.py`:130131 | Case | `op` | `item_key` | `prior_state` | `new_state` | `disposition` | `recolor_generation` |132 | --- | --- | --- | --- | --- | --- | --- |133 | No-conflict admit | `add` | item key | null | `scheduled` | null | `g` (unchanged) |134 | Deferred admit | `add` | item key | null | `scheduled` | null | `g` + 1 |135136 `prior_state` is null on BOTH add rows. The accompanying `prepared` -> `scheduled` transition is137 not lost and is not recorded in the mutation entry: it is recorded as an item-state update in138 `items[]`, the same mechanism that records `proposed` -> `admitted` -> `prepared` in step 2. The139 `at` timestamp comes from the engine's injected clock seam.1401417. **Validate the checkpoint** before treating the admission as applied. Run the142 `validate_orchestration_artifacts` MCP tool with `artifact_type:143 "parallel-orchestrator-state"`. A non-empty error list means the admission was applied144 incorrectly; correct the checkpoint rather than proceeding.145146## Constraints147148- One admission per invocation, one `mutations[]` entry per successful admission. A failed149 preparation appends no entry and leaves `items[]` without the candidate.150- No field and no enum member is added to `mutations[]`, `conflict_edges[]`, `items[]`, or any151 state or merge-status enum. The nine parallel enums are owned by152 `.claude/rules/parallel-orchestration.md` and are consumed, never extended.153- This operation never moves, restates, or re-derives an in-flight item's cohort or state.154- This operation performs no destructive side effect: it closes no pull request and removes no155 worktree. Those belong to `/parallel-remove` with `--disposition abandon`.156157## Completion Requirements158159- Report the admitted item key, the admission outcome, the resulting `recolor_generation`, and the160 single appended `mutations[]` entry.161- Report the cohort index the item landed in, and confirm explicitly that no in-flight item's162 cohort or state changed.163- Report the checkpoint validation result.