Skip to content
Back to skills

Swarm Dispatch

ASecurity

Execute a swarm plan wave by wave with filesystem isolation via git worktrees. For each wave, main context decides the scenario recipe + implementation contract for every task, then spawns one swarm-worker per task in parallel. Each worker executes its recipe and reports JSON status. Worktree merge-audit verifies write-set discipline before changes land on main. Aborts remaining waves on any audit or task failure.

  • 14 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added October 4, 2026
ai-agentsnodegitsecurity

Works with

  • cli

Security analysis

A100/100

Pro scans all 5 files and shows the line behind each finding

Scanned October 4, 2026

npx -y skills add friedbotstudio/baseline --skill swarm-dispatch --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Swarm Dispatch?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Swarm Dispatch
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/friedbotstudio-swarm-dispatch/badge)](https://www.skillsdirectory.com/skills/friedbotstudio-swarm-dispatch)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: swarm-dispatch
owner: baseline
description: Execute a swarm plan wave by wave with filesystem isolation via git worktrees. For each wave, main context decides the scenario recipe + implementation contract for every task, then spawns one swarm-worker per task in parallel. Each worker executes its recipe and reports JSON status. Worktree merge-audit verifies write-set discipline before changes land on main. Aborts remaining waves on any audit or task failure.
argument-hint: "<slug — matches .claude/state/swarm/<slug>.json>"
---

# swarm-dispatch — wave runner with worktree isolation

Invoked after `/swarm-plan` + `/approve-swarm`. The architecture is the user's principle made concrete:

> **Main context decides. Workers execute.**

Per task, before dispatch, you (main context) produce two recipes:
1. The **scenario recipe** — exactly which failing tests the worker should write.
2. The **implementation contract** — exactly which source files the worker may touch and what behavior they must implement.

The worker's prompt contains both recipes verbatim. The worker invokes `Skill(scenario)` then `Skill(implement)` and reports JSON. It makes no design decisions.

## Isolation modes

Read `project.json → swarm.isolation` (default `"auto"`):

- `"auto"` → choose `worktree` if the project root is inside a git repo (`git rev-parse --is-inside-work-tree` succeeds), else `shared`.
- `"worktree"` → require a git repo; bail if absent.
- `"shared"` → never use worktrees; rely on `swarm_boundary_guard` for runtime enforcement.

**Default path is `worktree`.** The rest of this document describes that mode. The `shared` fallback is at the end.

> **Worktree mode = single-wave only (D1).** The worktree base commit is chosen by the Agent tool's `isolation:"worktree"`, not by baseline (observed forking from a stale ref on the first real swarm run), and `swarm_merge.mjs` applies wave output without committing — so a multi-wave worktree plan's wave-N+1 worktrees branch from a base lacking wave-N output. Baseline cannot fix this, only refuse it. **Before dispatch, call `assertWorktreeWaveSafety({isolation, waves, baselineRef, worktreeBase})` from `.claude/skills/swarm-dispatch/worktree-safety.mjs`** (worktreeBase = `git -C <wt> merge-base HEAD <wt-HEAD>` once a worktree exists). On `ok:false` (multi-wave under worktree, or `baseline_ref` ≠ the worktree's real merge-base), **abort and steer the plan to shared isolation** — do not run the wave. Multi-wave plans belong in `shared` mode.

## Prereqs (worktree mode)

Verify in order, abort on any failure:

1. `.claude/state/swarm/<slug>.json` exists, has `status: "planned"`, and a non-null `waves` array.
2. `.claude/state/swarm_approvals/<slug>.approval` exists and begins with `APPROVED`.
3. `.claude/state/swarm/active_wave.json` does **not** already exist (stale/racing dispatch — ask before clobbering).
4. `git rev-parse --is-inside-work-tree` succeeds at the project root.
5. Working tree is clean (`git status --porcelain` empty) **if** `project.json → swarm.refuse_dirty_tree` is true (default).

Record the baseline: `git rev-parse HEAD` → this SHA is the reference every worktree will be compared against at merge time.

## Per-wave loop

For each wave in `plan.waves`, in order:

### 1. Decide the recipes (main context)

For every task in the wave, produce:

- **Scenario recipe** — list of failing tests to write. Each: `name`, `covers`, `assertion`, `fixtures`. Plus `out-of-scope` list and `test target paths`.
- **Implementation contract** — `failing_tests` (the paths the scenario step will produce), `write_set` (from the plan), behavior contract (the spec's §Behavior excerpts for the task's ACs, plus §Design data model + contracts), project conventions (from `project.json`).
- **Style anchors** — 1–2 existing test files and 1–2 existing source files in the touched modules so the worker matches the project's idioms.

This is where the heavy thinking lives. Do it before dispatch — once a worker is running, the recipe cannot be changed.

### 2. Park the harness, then raise the barrier

**Park first.** Write `.claude/state/harness_state` as `{state: "parked", slug, reason: "swarm wave <n> in flight"}`. This dispatch owns the session until the wave resolves, and `harness_continuation`'s Path A would otherwise re-fire the loop into a phase whose predecessor is still running — the marker is present by definition, because the wave runs inside an armed loop.

Park is a **declaration, not a detector**. Nothing infers that workers are running; this skill says so, and says when it stops. Clearing it is step 7's job and is unconditional — see **Unparking** below.

Then write `.claude/state/swarm/active_wave.json`:

```json
{
  "slug": "<slug>",
  "wave": <n>,
  "isolation": "worktree",
  "baseline_ref": "<HEAD SHA>",
  "started_at": <epoch>,
  "write_sets": [
    {"task_id": "T-001", "files": [...]},
    {"task_id": "T-003", "files": [...]}
  ]
}
```

In worktree mode this file is consumed by `swarm_merge.mjs` (which reads `baseline_ref`). `swarm_boundary_guard` is dormant — writes happen inside worktrees that don't contain `active_wave.json`.

### 3. Update plan status

Set each wave task's `status` to `"running"` inside `.claude/state/swarm/<slug>.json`.

### 4. Dispatch the wave

One message, N parallel `Agent` calls — one per task. Each uses:

- `subagent_type: "swarm-worker"`
- `isolation: "worktree"`
- `run_in_background: true`

Worker prompt template (self-contained — the worker has no memory of this conversation):

```
You are executing swarm task <T-XXX> from plan <slug>, in your own isolated
git worktree. Your write_set is the ONLY set of files you may modify.

# Task metadata
- task_id: <T-XXX>
- slug: <slug>
- ACs covered: <AC list>
- Component: <component id>

# Spec excerpt (behavior contract)
<paste §Behavior sequences for this task's ACs + §Design data-model/contract
 rows the task touches. Keep under ~200 lines.>

# Scenario recipe — what tests to write
out-of-scope: [<scenarios explicitly NOT to write>]
test target paths: <test file paths>
style anchors: <1-2 existing test files>

scenarios:
- name: test_when_X_then_Y
  covers: AC-001
  assertion: "<one plain sentence>"
  fixtures: [<paths/factories>]
- name: test_when_A_then_B
  covers: AC-002
  assertion: "..."
  fixtures: [...]
- ...

# Implementation contract
write_set (STRICT — anywhere else fails the merge audit):
- <file 1>
- <file 2>
- ...

read_set (advisory):
- <file 1>
- ...

style anchors: <1-2 existing source files>
project conventions:
  test.cmd: <...>
  lint.cmd: <...>
  tdd.test_globs: <...>

# Your job
1. Invoke Skill(scenario) with the scenario recipe + test target paths.
2. If all expected tests are RED, invoke Skill(implement) with the failing test
   paths, the write_set, the behavior contract above, and the project
   conventions. During the RALPH loop run ONLY your own test file
   (node --test <your test target paths>), never the full suite — sibling
   workers run concurrently and a full-suite run races the
   live-objtemplate-rebuild-races landmine; the full suite runs at /integrate.
3. You SHALL complete BOTH step 1 (scenario) and step 2 (implement) before
   reporting. Do not emit a final message until the JSON status line; a
   scenario-only stop is status:failed.
4. Report JSON on your final line per the swarm protocol:
   {"task_id": "<T-XXX>", "status": "done" | "failed",
    "files_touched": [...], "note": "<one short line>"}
```

The `swarm-worker` agent's body already knows the protocol. The prompt contains the recipes; the worker executes them.

### 5. Wait

Do not respond to the user until every task in the wave has completed. Each `Agent` return gives you the worktree path (if the worker made changes) and the JSON summary line.

### 5.5 Classify each worker result (D4)

For every worker return, run `parseWorkerResult(<final message text>)` from `.claude/skills/swarm-dispatch/parse_worker_result.mjs` (or the CLI `parse_worker_result.mjs <result-file>`). A worker is **complete** only when its final non-empty line is a valid `{task_id,status:"done"}` JSON line. A missing/garbled line, trailing prose after the JSON, or `status:"failed"` → **incomplete**: do NOT treat the task as done. Route it to **resume-if-possible** (re-dispatch / `SendMessage` where available) else **main-context completion** from the worker's RED tests. This closes the first-run gap where workers rested after scenario and were silently passed.

### 6. Per-task merge-audit

For each completed task:

```
.claude/skills/swarm-dispatch/swarm_merge.mjs \
  .claude/state/swarm/<slug>.json \
  <task-id> \
  <worktree-path>
```

Outcomes:
- **Exit 0**: audit passed, patch applied to main, worktree removed. Update task `status: "done"`.
- **Exit 1**: audit failed OR `git apply` failed. Worktree preserved for inspection. Update task `status: "failed"` with a `note` naming the offending file(s).
- **No worktree path returned** (worker made no changes): the harness auto-cleans the empty worktree. Mark task per the worker's self-reported JSON.

### 7. Clear the barrier and unpark

Delete `.claude/state/swarm/active_wave.json`.

**Unparking.** Rewrite `harness_state` back to `{state: "continue", slug, reason: "<n> waves done; next: <phase>"}` when the plan continues, or to `{state: "yielded", slug, reason: "<one sentence>"}` when the wave failed and the human must look. Do this on **every** exit from a wave — success, task failure, audit failure, and an aborted prereq after step 2 ran — so a park never outlives the thing that set it.

If the session dies mid-wave the park stays on disk. That is the correct residue and the reason park beats a background registry: the human returns, types `/harness`, and preflight rearms. The failure mode is one command, not a loop that spins or a hook silenced with no signal.

### 8. Decide the wave's fate

- Every task `done` → advance to the next wave.
- Any task `failed` (worker-reported OR audit failure) → set plan `status: "failed"`, stop, surface the failed task(s) with their `note` and (for audit failures) the preserved worktree path.

## After the last wave

1. Set plan `status: "complete"`.
2. **Record the Phase-6 completion marker.** Append the canonical Phase-6 phase-name `"tdd"` to `workflow.json → completed` (alongside the swarm phases already there). The swarm path IS Phase 6 (6a/6b/6c), but `track_guard` resolves the `tdd` ordering slot by completed-membership; recording `"tdd"` is the SOP companion to `hooks/lib/track-order.mjs → phaseSatisfied` (which also accepts a completed `swarm-dispatch`) — belt-and-suspenders so downstream phases (`/security` etc.) are never false-blocked whether or not the guard fix is present.
3. Run `/integrate` on the full codebase — per-wave success is necessary but not sufficient; cross-component integration must be re-verified.
4. If `/integrate` passes: tell the user "Swarm `<slug>` complete. `<N>` tasks across `<M>` waves. Next: `/document`."

## Shared-mode fallback

When isolation is `"shared"`:

- No worktrees. Each `Agent` call uses `isolation` omitted or `"none"`.
- `active_wave.json` carries `isolation: "shared"` and the union of write_sets (no `baseline_ref`). **At wave start, also record `pre_wave_changed`** — the set of already-changed paths (`git status --porcelain`) before the wave runs — so the post-wave audit attributes only this wave's changes.
- `swarm_boundary_guard` is the runtime enforcer: writes in enforced paths must be in the union of active write_sets, else denied. **But the guard exempts `.claude/` (D2 blind spot)** — so for baseline self-dev under `.claude/skills/**` it enforces nothing.
- **Post-wave diff-audit (D2).** After each shared wave completes, run `swarm_wave_audit.mjs <plan-path> <wave-index>`. It diffs the wave's actual changes (current `git status` minus `pre_wave_changed`) against the union write_set **directly — not via the guard's exempt list** — so `.claude/skills/**` drift IS caught. Exit 1 (a path outside the union) → treat the wave as failed: stop, surface the offending paths, do not advance. This is the shared-mode analogue of worktree mode's per-task merge-audit.
- Cross-task bleed *within* a wave (two tasks in the same wave writing each other's files) remains a known limitation — the audit catches out-of-union drift, not intra-union misattribution.
- After each wave: run the post-wave audit, then clear `active_wave.json`, unpark the harness (step 7), update per-task status from the worker's self-reported JSON (classified via §5.5, D4).

Use shared mode deliberately — it trades real safety (physical isolation) for runtime permissiveness. Worktree mode is preferred whenever git is available.

## Failure recovery

- **Unpark before surfacing.** A failed wave still leaves the harness parked unless step 7 ran. Write `harness_state` as `yielded` with the failure reason so the human is notified and `/harness` is not needed to get their attention.
- Plan stays in `"failed"` state for user inspection.
- In worktree mode, failed tasks' worktrees are preserved. The user can `cd` in, read the worker's changes, and either:
  - Manually finish + commit to main, then mark the task done in the plan.
  - Drop the worktree (`git worktree remove --force <path>`) and re-plan.
- In shared mode, partial writes may have landed on main. `git status` shows them; revert or keep as appropriate.
- **Never auto-retry a failed task.** Failures warrant human attention.

## Constraints

- **Recipes are decided before dispatch.** Once a worker is running, you cannot change its recipe. Plan with that in mind.
- **`run_in_background: true` is mandatory** on every `Agent` call inside a wave. Foreground calls would serialize the wave.
- **`isolation: "worktree"` is mandatory** in worktree mode. Without it, the merge-audit guarantee collapses.
- **One message, N parallel `Agent` calls.** Sequential issuance defeats parallelism.
- **`subagent_type` is always `swarm-worker`.** No per-stack variants — stack-specific skill loading is handled by the worker template's `{{SKILLS}}` token at `/init-project` time, not by spawning different agents.
- **Never touch source files from this skill.** This orchestrator only reads and updates `.claude/state/`. File edits happen inside workers; merges happen via `swarm_merge.mjs`.
- **`active_wave.json` lingering** after abnormal termination is recoverable: delete it, inspect per-task status, re-dispatch the first incomplete wave.

Files in this skill

  • SKILL.md14.3 KB
  • parse_worker_result.mjs3 KB
  • swarm_merge.mjs6.6 KB
  • swarm_wave_audit.mjs4.2 KB
  • worktree-safety.mjs1.2 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.