- name
- parallel-agent-dispatch
- description
- Dispatch contract for spawning parallel agents covering worktree collisions, scope overflow, and silent exits. Use when fanning out concurrent agents or authoring a lead prompt.
- user-invocable
- false
- allowed-tools
- Read, Glob, Grep, TodoWrite
- model
- opus
- created
- 2026-04-21T00:00:00.000Z
- modified
- 2026-09-02T00:00:00.000Z
- compatibility
- claude-code
- reviewed
- 2026-09-02T00:00:00.000Z
# Parallel Agent Dispatch
Conventions that apply every time more than one agent runs in parallel. Prevents
the top failure modes observed across real multi-agent sessions: dirty-worktree
cross-contamination, context overflow mid-task, and silent exits that require
manual salvage from orphan branches.
Supporting material is split across `references/` by the path that needs it;
[REFERENCE.md](REFERENCE.md) is the index. Sections below link the specific file.
## When to Use This Skill
| Use this skill when... | Use `agent-teams` instead when... |
|---|---|
| Spawning >1 agent via plain `Agent` tool fan-out (N concurrent invocations) | Single-agent delegation or one-off subagent spawn |
| Using the implicit team + teammate spawn for coordinated parallel work | A simple background task with no parallel siblings |
| Running worktree-isolated parallel implementation across repos/features | A read-only inline subagent that does not write to disk |
| Coordinating parallel investigation or audit swarms | The work fits in the current session without forking |
## Dispatch from the Main Thread When Possible
`Agent` and other parallel-spawn tools may be absent from a sub-agent's sandbox
even when available in the main conversation, so designing a fan-out from inside
a coordinating sub-agent risks silent degradation to sequential execution.
- **Default**: dispatch from the main conversation — the full tool surface is
guaranteed.
- **Sub-agent orchestrator**: only when the team's outputs need not feed back
into the main thread. Brief it to verify tool availability up front and report
sequential fallback as a first-class outcome (`agent-teams` → "Sub-Agent
Caveat").
## The Three Pillars
### 1. Worktree Preflight
Before spawning, the orchestrator must verify:
| Check | Rationale |
|-------|-----------|
| Main working tree is clean (`git status --porcelain` empty) | Agents inherit cwd; uncommitted changes cross-contaminate worktrees |
| No existing worktree at each planned path (`git worktree list`) | Nested or duplicate worktrees are the #1 source of salvage work |
| Each agent gets a **unique** branch name | Prevents commits landing on the wrong branch when cwd resolution drifts |
| **Fixed target branch name not already taken** (see Target-branch preflight below) | A conventional per-issue/milestone name is one two sessions pick identically; the collision otherwise surfaces only at end-of-task rename |
| Shared counters snapshot (next ADR/PRP number, feature-tracker IDs) | Prevents numbering collisions in parallel doc writes |
If any check fails, **refuse to dispatch** and report the blocker. Do not
"clean up" uncommitted user work — surface it and ask.
**Target-branch preflight (#1969).** `isolation: "worktree"` auto-names the
branch; renaming onto a **fixed** conventional name another session's worktree
already holds is refused only at the end-of-task rename, deep into the run (real
case: two sessions both reached PR-open → duplicate-PR reconcile). Check the name
is free first (`git branch -a --list "$target"`, `git worktree list`,
`git ls-remote --heads origin "$target"`); any hit ⇒ **stop and reconcile**, not
race to a duplicate PR (`.claude/rules/concurrent-session-pr-check.md`).
**Mitigation:** push via explicit refspec (`git push origin HEAD:$target`)
instead of renaming. See
[references/worktree-hazards.md → Target-branch preflight](references/worktree-hazards.md#target-branch-preflight-1969).
**Transient worktree leaks (#1319).** While a wave runs, a file a child wrote
inside its worktree can briefly appear in the **parent** as an untracked entry
at the same relative path, then vanish when the child commits. Do not stash,
restore, or commit untracked parent files during a wave; wait for the child's
completion, then let its branch reclaim the file. `/git:coworker-check` raises
`worktree_leak_suspected` for this — run it before every parent-side commit.
**cwd-reset leaking git writes (#1480).** Distinct from the transient leak: an
agent thread's bash cwd resets between calls and can land on the main repo root,
so a git-**write** agent's bare commands mutate `main` instead of its worktree.
Brief every git-write agent: pin the root once
(`git rev-parse --show-toplevel` → `$WORKTREE`) and prefix every call with
`git -C "$WORKTREE" …`; forbid bare `git checkout -B` / `git rebase --autostash`
until inside the worktree. After the agent returns, run the post-run main-repo
integrity check (see [references/worktree-hazards.md → cwd-reset
guardrail](references/worktree-hazards.md#worktree-cwd-reset-guardrail-1480)) —
a changed branch or new dirty state is silent main-repo mutation.
**`GIT_DIR`/`GIT_WORK_TREE` export leak (#1692 sibling).** A worktree reporting
`core.bare = true` is shared-checkout corruption — **STOP and report it**; never
"work around" it by exporting `GIT_DIR`/`GIT_WORK_TREE`, which **override
`git -C`** so every later git call targets the shared common config, breaking
**all** sibling worktrees at once. A subprocess that must run git in a sandbox
neutralizes inherited env first:
`env -u GIT_DIR -u GIT_WORK_TREE git -C "$dir" …`. See
[references/worktree-hazards.md → GIT_DIR-export leak](references/worktree-hazards.md#worktree-git_dir-export-leak-1692).
**Nested-repo workspaces — `isolation: "worktree"` isolates the *outer* repo (#1838).**
The harness worktrees the **session's** repo, not the repo the agent was told to
edit, so in a portfolio layout the target files are **absent** from the worktree
and the agent's only path to them is the shared checkout — which the Edit-tool
isolation guard correctly blocks. Detect it before dispatch:
`git -C <target-dir> rev-parse --show-toplevel` ≠ `git rev-parse --show-toplevel`;
when they differ, isolate the **nested** repo explicitly. See
[references/worktree-hazards.md → Nested-repo isolation](references/worktree-hazards.md#nested-repo-worktree-isolation-1838)
for the detection script and rules.
**Concurrent agents default to the *same* scratchpad path (#2370).** Sibling
subagents share the session scratchpad, so two agents each told to "make your own
clone" pick the identical path and silently operate **one working tree**. Give
each an explicit, distinct path (`<scratchpad>/<agent-name>`) whenever they work
outside worktree isolation — which the nested-repo case above forces. See
[references/worktree-hazards.md → Shared scratchpad collisions](references/worktree-hazards.md#shared-scratchpad-collisions-2370).
**A deleted worktree kills the agent's shell, not its reasoning (#2372).** Every
Bash call then fails, and a spawned subagent inherits the same dead cwd. See
[references/worktree-hazards.md → Deleted worktree](references/worktree-hazards.md#deleted-worktree-kills-the-shell-not-the-agent-2372).
**`isolation: "remote"` may resolve to a LOCAL worktree (#2447).** The tool
result never says which mode ran; `git worktree list` and the notification's
`worktreePath` are the tells. An empty remote is evidence about the **push**,
not the **work** — audit local worktrees alongside `gh pr list` / `git
ls-remote` before calling work lost, and brief agents to open a **draft PR
early**, pushing after each commit. See
[references/worktree-hazards.md → isolation: "remote"](references/worktree-hazards.md#isolation-remote-may-resolve-to-a-local-worktree-2447).
### 2. Scope Budget (per-agent prompt rules)
Every agent prompt must declare:
- **File scope**: exclusive write paths (glob or explicit list). Out-of-scope
discovery → stop and report (see `agent-teams`).
- **Read budget**: soft cap on files examined (default "≤10 files per hop, ≤3
hops before returning").
- **Output budget**: expected length of the return summary — discourages echoing
full file contents when a diff or line reference will do.
- **The user's ask, verbatim**: quote the user's instruction and any stated
boundary rather than paraphrasing — drift starts there (Fable 5.1 system
card: distorts user intent briefing agents).
- **No borrowed authority**: a brief never speaks as the user or asserts
approvals not given this session (system card: fabricated user quotes
were observed).
These budgets prevent the "agent hit context limits" and "prompt too long"
failure modes — without them an agent exhausts its window on exploration and
truncates its deliverable.
**Orchestrator-only files.** Even with disjoint write scopes, shared files must
be excluded from every agent's write-path under an `### Orchestrator-only files`
heading in the brief: the blueprint manifest (ID registry), the feature tracker,
top-level plan/roadmap docs, build manifests, `justfile`/`Makefile`, and local
task-queue stores. Last-writer-wins silently destroys earlier work on these. See
[references/brief-templates.md](references/brief-templates.md) for the full
template and evidence.
**Pre-allocated IDs.** The shared-counter snapshot must expand into **explicit
per-agent ID assignment** in each brief ("Use WO-012; others claim WO-013/014").
"Pick the next free ID" is a race under parallelism. Applies to any shared
monotonic identifier (ADR, migration, PRP).
**Wave splits for exclusive locks.** An agent needing an exclusive lock (Ghidra
project lock, shared git index, migration lock, taskwarrior bulk ops,
single-writer caches) cannot share a wave with another lock-contender. Dispatch
it alone, or pre-compute its artefacts so downstream agents are read-only. See
`exclusive-lock-dispatch`.
**Refactor briefs.** For bulk content rewrites, use the per-step / PRECIOUS /
per-file-cap shape — see [references/brief-templates.md → Refactor-brief template](references/brief-templates.md#refactor-brief-template).
### 3. Return Contract (mandatory structured summary)
Every parallel agent must end its run with a structured `## Result` summary as
its final message, regardless of success or failure (status / branch / pr /
commits / worktree, plus Scope delivered, Deferred, Issues encountered, and
Orchestrator action needed). Include the schema **verbatim** in every dispatched
agent's prompt under a heading like `### Return contract (mandatory)` — agents
follow concrete schemas more reliably than prose. Copy the full schema from
[references/dispatch-contract.md → Return Contract schema](references/dispatch-contract.md#return-contract-schema);
for the failure-mode → schema-field rationale, see
[references/dispatch-contract.md → Failure modes](references/dispatch-contract.md#failure-modes--schema-field).
Orchestrator edits needed must be **verbatim patches, not prose** (literal CMake
blocks, full justfile recipes, literal doc paragraphs), and the agent writes the
final prose for any docs update its slice requires. See
[references/brief-templates.md → Verbatim patches](references/brief-templates.md#verbatim-patches--detail-and-rationale).
#### Loud-failure contract (never surrender silently)
A dispatched agent that hits a wall must say so **loudly**. The dominant failure
shape (issue [#1422](https://github.com/laurigates/claude-plugins/issues/1422))
is an agent that runs 50–200 tool calls, thrashes against hooks, then emits a
one-word final message — `Terminal.`, `Done.`, `Stopped.` — with no PR URL and
no blocked list. That is **indistinguishable from success** to the orchestrator,
so the harness reads "no changes", cleans up the worktree, and the work is lost.
Tie the escalation to the Return Contract's `status` field:
| Outcome | The agent must return |
|---------|-----------------------|
| **Success** | PR URL **plus one summary metric** (test/line delta) — `status: success` |
| **Partial blocker** | Push the WIP, open a **draft PR**, return its URL **plus an explicit "what's blocked" list** — `status: partial` |
| **Total blocker** | Explain *exactly* what blocked it, which tools were denied, what it tried — `status: failed`. Never a bare `Terminal.` / `Done.` / `Stopped.` |
The one-sentence contract to paste into every brief: **"Your final message is
the only thing I can act on — a one-word summary loses all your work. On any
blocker, push what you have, open a draft PR, and tell me exactly what stopped
you."** Optional enforcement: a `SubagentStop` hook that flags sub-~20-char or
bare-surrender final messages (see `hooks-plugin`).
A workflow harness does not replace this contract; it turns what prose can only
*request* into what a runtime *enforces*. The prose→primitive mapping, its two
cautions, and the cost gate before reaching for one:
[references/dispatch-contract.md → Workflow primitives](references/dispatch-contract.md#the-return-contract-as-workflow-primitives).
### 4. Agent self-verification in bulk-edit briefs
When fanning out agents to bulk-edit content covered by a regression script, the
brief **must** include the script as the agent's own final verification step.
Exit 0 means ship; non-zero means fix-and-re-run inside the same agent's budget —
shifting validation from commit-time to edit-time.
| Bulk edit | Agent's final verification step |
|-----------|--------------------------------|
| SKILL.md description rewrites | `python3 scripts/audit-skill-descriptions.py --strict-all` |
| Context-command edits in skill bodies | `bash scripts/lint-context-commands.sh` |
| `allowed-tools` / bash-permission edits | `bash scripts/plugin-compliance-check.sh` |
Treating the script as advisory defeats the purpose — the regression lands in
the agent's diff and the agent already has the context to fix it. See
[references/brief-templates.md → Bulk-edit self-verification](references/brief-templates.md#bulk-edit-self-verification--worked-example)
and `.claude/rules/regression-testing.md`.
**Closed-list mechanical batches need a completion manifest, not just a
self-report.** A `refactor` agent assigned a fixed list (symbols to delete, files
to touch) must emit a machine-checkable manifest of what it completed — and
**never** trust that manifest alone: re-run the authoritative checker (`knip` /
build / test) afterward and diff against the assignment. A truncated or
optimistic summary reads as success even when the batch fell short (issue
[#1601](https://github.com/laurigates/claude-plugins/issues/1601): a ~23-symbol
batch completed only ~5, invisible until `knip` was re-run). Cap the per-agent
batch so an early stop costs little. See
[references/brief-templates.md → Refactor-brief template](references/brief-templates.md#refactor-brief-template).
### 5. Reviewer-agent verification (verify-then-fix)
Self-attestation is unreliable. For high-stakes dispatches (PR "ready to merge",
security audits, shared-state mutations), spawn a **separate reviewer agent**
*after* the worker reports done and *before* trusting it. The reviewer runs in
its own worktree, ideally a different model, receives the claim and branch (not
the reasoning trace), and re-derives a verdict from the diff. On a flag, fix
inline or dispatch a follow-up worker — do not close on the worker's self-claim.
**Self-author guard for `gh pr` flows**: `gh pr review --reviewer <user>` returns
HTTP 422 when the target is the PR author; brief reviewers to post inline
comments instead. See [references/brief-templates.md → Reviewer-agent verification](references/brief-templates.md#reviewer-agent-verification--evidence).
## Who Pushes?
Agents push their own commits in the normal case — worktree isolation plus
per-agent branches makes this safe and keeps the lead context lean. The lead
pushes instead only for: **web sandbox sessions** (`CLAUDE_CODE_REMOTE=true`,
where teammates may hit TLS errors on push — see `agent-teams`), **cross-agent
dependencies** where Phase 1 commits must land as a single merge base for Phase
2, and **explicit user instruction** ("I'll push manually").
## Handling a Missing Return
If an agent exits without emitting the Return Contract, treat it as a **silent
stall, not a success**. Before deciding, **discriminate empty vs dirty
worktree**:
```bash
git -C <worktree> status --porcelain
git -C <worktree> log --oneline origin/main..HEAD
```
- **Dirty / commits present** → the agent did the work; **salvage** it
(commit/push the WIP, open the PR) rather than re-dispatching.
- **Empty / trivial diff** → nothing to salvage; resume or re-dispatch.
Do **not** report the parent task complete until every spawned agent has produced
a Return Contract (or been explicitly accounted for). Two causes leave the work
intact: a pre-commit hook blocking `git commit`, or a rate-limit cut-off after
the implementation but before the StructuredOutput call (issue
[#1491](https://github.com/laurigates/claude-plugins/issues/1491)).
Defensive mitigation: instruct worktree-isolated agents to commit
**WIP at checkpoints** — after each substantive slice and before they would
terminate — so partial work survives a lost structured result. See
[references/failure-recovery.md → Agent stalled at commit / push](references/failure-recovery.md#agent-stalled-at-commit--push--salvage-routine)
and [references/failure-recovery.md → WIP salvage before re-dispatch](references/failure-recovery.md#wip-salvage-before-re-dispatch-1491).
### Idle without report (#2039)
An implementer can **finish its work** (clean commit + tree) then go idle
emitting only an `idle_notification` — the work isn't lost, the *communication*
is (intermittent; siblings can deliver fine). Not a failure signal: run the
empty-vs-dirty check above, then `SendMessage` the named agent to resend the
Return Contract (read-only, so the #1546 caveat below does not apply). Never
respawn — a fresh agent lacks context and can't take the branch. Prevention:
implementers `SendMessage` the report to the lead as their final act. See
Voir sur GitHub