Plot dispatch
Fan out an approved plan's eligible branches: one git worktree and one detached worker per branch, each claimed atomically. The writing half of the fleet. Use on /plot-dispatch.From its SKILL.md
npx -y skills add plot-pm/plot --skill plot-dispatchAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its file declares
Copied from the file, not written here
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
21.5 KB, as published. Nobody here has run it
Plot: Dispatch
Turn one approved plan into several agents working at once — one git worktree and one detached worker per eligible branch, each branch claimed atomically so no two sessions collide.
This is the writing half of the fleet; /plot-fleet is the reading half.
They never talk to each other: the pulse reports what is eligible, a human
decides to dispatch it. That separation is what makes the whole thing
restartable — kill anything, and the next pulse re-derives the truth from git.
Fanning out is human-paced (Manifesto, Pacing). It commits scope: several
agents, several branches, real tokens. Monitoring is automatable; committing to
parallel work is a decision. This command therefore never runs itself, and
--dry-run exists so the decision can be taken with the facts in hand.
Input: $ARGUMENTS = [--dry-run] [--no-start] [--max N] [--allow-local] <slug>,
or --status / --stop <branch> to inspect or stop running workers.
Model Guidance
| Steps | Min. Tier | Notes |
|---|---|---|
| 1. Preflight | Small | Phase check + one script call |
| 2. Dry run and confirm | Mid | How many agents is a judgment about cost and review capacity; the in flight: lines are facts to relay, and whether a shared file matters is the user's call. A skipped … (held …) line needs no judgment at all — the script decided, and it is relayed as decided |
| 3. Ask about the worker command | Mid | One config read decides whether to ask at all; asking without an example, and recording an empty answer as none rather than leaving it blank, is the judgment |
| 4. Fan out | Small | The script does the work; claims are atomic |
| 5. Write a brief per branch | Frontier | Delegated to /plot-implement, whose brief step is itself Frontier: naming the alternatives the plan rejected is judgment |
| 6. Report | Small | Read the footer counts and worker=; relay a failed Started: booking verbatim |
User interaction: Use
AskUserQuestion(Claude Code) /ask_question(Cursor).No user present? If
PLOT_UNATTENDED=1is set, do not call the question tool — each question below declares what to do instead, and every skipped question is named in the output. See Running unattended.
Steps
1. Preflight
The plan must be Approved and its Impl: answer must be own branches —
fan-out is meaningless for a same-branch or other-repo plan. Read both with
../plot/scripts/plot-plan-meta.sh.
Then look before leaping:
../plot/scripts/plot-fleet-scan.sh <slug>
If the wave is blocked, stop and say why: an earlier wave still has unmerged work, and dispatching would build on a seam that has not been proven.
2. Dry Run, Then Confirm
Always dry-run first, and show the user the result before fanning out:
../plot/scripts/plot-dispatch.sh --dry-run <slug>
Then ask how many to start. Do not assume "all eligible" is what the user
wants — each worker costs tokens and produces a PR someone must review. Name
the real constraint: "4 branches are eligible. Each becomes a PR. How many do
you want running?" Use --max N to honour the answer.
Unattended (
PLOT_UNATTENDED=1): stop. Fan-out spends tokens and creates PRs a person must review, and "all eligible" is precisely the assumption this step exists to prevent — an unattended run must not make that call by defaulting to it. Report the dry run in full, including anyin flight:lines, and start nothing. A caller that genuinely wants a fixed number passes--max Nexplicitly, which is the answer and leaves nothing to ask.PLOT-UNASKED: How many of <n> eligible branches to start? — stopped — dry run shown; no worktree created, no worker started
Read the in flight: lines
A candidate line may be followed by what other branches already hold:
would dispatch feature/agent-view-phase-ui → …
in flight: bug/board-shows-staleness holds App.tsx, AgentList.tsx
Waves are a within-plan ordering. A correctly eligible branch can still name a file an agent has open on a different plan's branch, and no plan can declare that alone. These lines are that missing fact, read from local refs and worktrees — including unpushed commits and uncommitted changes, which no remote knows about.
They report; they do not judge. Nothing about the candidate's own files is
predicted, so the overlap is not computed and dispatch refuses nothing. Surface
the lines to the user and let them decide; a shared file is often fine, and
saying "these collide" would be claiming a certainty nothing here has. If a
candidate looks genuinely contested, the useful move is --max N or naming the
branch to hold back, not a refusal.
One thing here is refused, and it is a different question. A file two
branches share is a prediction. A worktree that already exists for the
candidate itself, holding work that has not landed, is a measurement — somebody
is at that desk. Dispatch refuses that branch, counts it skipped, and names
the worktree path; see The held-branch gate below.
No lines means nothing is held — the report stays silent rather than printing reassurance nobody would keep reading.
The report is capped at 8 branches and 6 files each, with the remainder counted
((+4 more), …and 5 more branches). When you see an overflow line, the fleet
is busy enough that /plot-fleet <slug> is the better view — say so rather than
re-deriving the omitted rows yourself.
The held-branch gate
A branch whose worktree already exists with work that has not landed is refused rather than dispatched. Unlanded means either commits that are not in the default branch or uncommitted changes in the working tree — an agent mid-edit has often committed nothing at all:
skipped feature/the-row-carries-its-verdict (held — worktree exists with unlanded work)
worktree: /path/to/plot-wt-feature-the-row-carries-its-verdict
nobody claimed it, so nothing here can tell a live agent from an
abandoned desk. Check it, then remove the worktree or let it finish.
Why this exists. /plot-fleet derives every state from origin/<branch>,
so a branch whose work was never pushed has no claim, and no claim reads
eligible. On 2026-08-20 a dry run reported claimed=0 across a fleet with
four live agents and offered two branches that were already implemented, tested
and green. Dispatch is the one component that can catch this, because it reads
this machine's worktrees for the collision report above.
What to do with it. Look at the worktree. It is either a live agent — leave
it alone — or an abandoned desk, in which case remove the worktree and re-run.
The gate deliberately does not claim the branch for you: a claim ref for a
worktree Plot did not create is a record nobody asked for, and /plot-reconcile
cannot tell a stale claim from a real one.
--dry-run refuses identically, and --allow-local does not override it —
that flag is about reading a plan's phase without a remote, and says nothing
about whether a human is mid-edit. A leftover worktree whose tip has merged
and which holds no uncommitted changes is still dispatched: those accumulate
normally, and refusing them would fire the gate on exactly the branches that are
safe.
The worktree is found by asking git which one holds the branch, not by
guessing a path from the branch name — hand-made worktrees are the population
this gate is for, and they rarely follow dispatch's plot-wt-<flattened>
convention.
3. Ask How This Project Runs an Agent Headless — Once
Only when Worker command is absent entirely, and only here:
../plot/scripts/plot-config.sh get "Worker command" ""
Empty output means nobody has been asked. Ask now, with the count from the dry run in hand:
3 branches eligible.
No `Worker command` configured — worktrees will be prepared
but no agent started.
How does this project run an agent headless?
(leave empty to keep starting them yourself)
Never offer an example command. Not in the prompt, not as an
AskUserQuestion option, not as a "for instance". An example becomes a
template, and then Plot has effectively hardcoded a tool it is not supposed to
know (Principle 5). The problem was never which command — it is that nobody
learns the option exists.
Write the answer to ## Plot Config either way, and that is the whole point
of asking here:
| Answer | Write | Meaning |
|---|---|---|
| a command | - **Worker command:** <what they said> | dispatch starts workers |
| empty | - **Worker command:** none | asked; this repo starts them by hand |
Unattended (
PLOT_UNATTENDED=1): stop, and write nothing to## Plot Config. Both answers above are durable configuration, so an unattended default would not merely act unasked — it would record a choice nobody made and stop the question ever being asked again.nonein particular means a person considered this and declined, which is a claim an agent cannot truthfully make. Prepare no worktrees.PLOT-UNASKED: How does this project run an agent headless? — stopped — Worker command absent; config left untouched
none is a deliberate absence, and recording it is what stops the question
returning. An empty answer is first-class — hand-starting works, and the config
removes a step rather than declaring the manual path wrong. A prompt that comes
back every dispatch is a nag, and nags get answered with whatever silences them.
plot-dispatch.sh never runs none as a command; it reports worker=declined.
Never ask this at /plot-init. Adoption runs long before anyone fans out
work — often before the repo has a second branch — so the question arrives about
a need the answerer does not have. It gets a shrug, the key is written empty,
and nobody revisits it: an answered-and-wrong config is harder to fix than a
missing one, because nothing later notices it was never really decided. Here
the consequence is concrete and immediate: these branches are about to be
prepared and nobody will start them.
Skip this step when the key already holds anything at all — a command or
none. Both mean the question has been answered.
4. Fan Out
../plot/scripts/plot-dispatch.sh [--max N] <slug>
Per branch the script: creates ../plot-wt-<suffix>, claims the branch by
pushing its ref, and starts a detached worker. A push that would overwrite an
existing branch is rejected — that rejection is the concurrency control, and
the skipped branch is reported, not retried.
--no-start prepares worktrees and claims without starting anything, for when
you want to drive the sessions yourself.
Once the fan-out is done the script records one Started: line per branch it
claimed, in /plot-implement's shape, on the default branch — through a
disposable plot/start-<slug> branch pushed with plot-push-main.sh. It is
written on the default branch because that is where the board reads plans from;
a record committed to whatever branch the dispatcher happened to be on would be
invisible, and the plan would keep reading as Ready while agents edit its
branches.
If that push fails — offline, refused, beaten to the ref — the fan-out stands: worktrees exist and claims are pushed, and those are the real state. The script says the record is missing and carries on. Record it by hand, or re-run the dispatch once the push works; a re-run adopts the existing worktrees and books nothing it did not newly claim.
5. Write a Brief per Dispatched Branch
A prepared worktree is not work handed over. What an implementer needs — which
alternatives the plan already rejected, and the measurements that killed them —
lives in a hand-off brief, and /plot-implement is the step that produces
one. Dispatching skipped that step, so every fan-out so far was completed by a
human writing the brief afterwards.
For each branch the script reports as dispatched, invoke /plot-implement
so it writes .plot/briefs/<branch-suffix>.md (step 4 there) and commits it on
the default branch. Skip branches reported as reused — a brief is already
there — and skip skipped ones entirely: another session holds them, and their
brief is not yours to write.
/plot-implement finds the branch already claimed and treats it as a resume, so
it re-creates nothing: no second claim push, no duplicate Started: record.
Dispatch has already booked both.
The caller here is this skill, not plot-dispatch.sh. No script in this repo
invokes a skill, and bash cannot reach one at all — skills exist inside an agent
session. That is the Manifesto's direction rather than an omission: skills
interpret and adapt; scripts collect and report. A brief is interpretation, and
a template string in the dispatcher would be a second definition of what an
implementer needs to know — it would drift from /plot-implement's the way every
duplicated rule in this repo has.
A direct plot-dispatch.sh call therefore cannot write a brief. It says so in
its summary (brief=missing) rather than refusing: --dry-run and --status
are the normal way to look before leaping, and a gate that blocks looking is a
gate in the wrong place. When you see that in the summary, the branches are
prepared and claimed and nobody has been handed anything — the brief step is
still owed.
--no-start does not change this. It suppresses workers, not briefs; the
inspect-first workflow still wants the brief written for whoever picks the
worktree up.
6. Report
Read the footer, never re-count:
summary: dispatched=3 reused=0 skipped=1 started=3 brief=missing worker=configured
Say what is now running, where the worktrees are, and how to watch:
/plot-fleet <slug> for state, ../plot-wt-*/.plot-worker.log for output.
brief=missing is the script reporting that it wrote none, which is always
true — it never can. If step 5 ran, say so; the summary is describing the
script's own reach, not the outcome of the dispatch.
worker= says why started= is what it is, and the prose line above the footer
says the same thing for a human:
worker= | Means | What to relay |
|---|---|---|
configured | a Worker command exists | what is running, and where the logs are |
unconfigured | nobody has been asked | step 3 was skipped — go back and ask |
declined | asked; this repo starts workers by hand | the worktrees are ready; name them so the user can cd in |
suppressed | --no-start | exactly what was requested — not a defect |
unconfigured on a real fan-out means step 3 did not happen. That is the state
this whole command spent an evening in: worktrees claimed, nobody working on
them, and nothing in the last line saying so.
Configuration
Starting workers requires the adopting project to say how (Principle 5 — Plot hardcodes no tooling):
## Plot Config
- **Worker command:** claude -p "Implement the branch named in $PLOT_BRANCH per the plan. Follow the DoD. Open a PR. Do not merge."
The command runs inside the worktree with PLOT_BRANCH and PLOT_WORKTREE
set, detached, with output to .plot-worker.log. The agent's pid — the
process the command names, not the shell that wraps it — is recorded in
.plot-worker.pid, so the panel describes the process doing the work; the
wrapper's own pid is kept in .plot-worker.wrapper.pid, where it records the
run's exit code. Without the key, worktrees are prepared and the user starts
them.
- **Worker command:** none records that the question was asked and the answer
was we start them by hand. It is never run as a command, and it stops step 3
asking again.
The example above is documentation, not a suggestion. Do not repeat it — or any other command — into the step 3 prompt. Someone reading this file has come looking for the format; someone being asked the question has not, and an example put in front of them becomes the answer.
Detached is the point: the fleet outlives this session. Close the laptop
and the workers keep going — which is also why a dead worker needs the reaper
(/plot-reconcile) rather than being noticed here.
Inspecting and stopping workers
Detached workers would otherwise be invisible:
../plot/scripts/plot-dispatch.sh --status # every worktree: pid, alive?, last log line
../plot/scripts/plot-dispatch.sh --stop feature/x # stop one worker
Both work regardless of the plan's phase — work already running must stay
inspectable even if the plan was since delivered. --stop requires an explicit
branch name (containing /); there is deliberately no "stop everything".
Stopping leaves the worktree and the claim in place: the branch stays taken
until you release it. Releasing is /plot-reconcile's job.
Guardrails
- The phase and
Impl:checks are gates in the script, not advice here.plot-dispatch.shrefuses a plan that is not Approved, or whoseImpl:answer is notown branches, and it fails closed if the phase cannot be read. You cannot talk it into fanning out unapproved work. - The phase is read from
origin/<main>, never the working tree. The question the gate means to ask is has this plan been approved where everyone can see it?, and only the shared ref answers it. So an approval committed locally and never pushed does not open the gate — push it first — and a checkout parked on another branch does not hide an approval that is already shared. Every refusal names the ref and sha it read. --allow-localis the escape for a repo with no remote, and nothing else. It gates on the working tree and says so on stderr. Reach for it only whenorigin/<main>genuinely cannot be resolved; using it to get past a refusal is how unapproved work gets dispatched. It does not unlock a held branch — see the next point, which it has no bearing on.- A held branch is refused by the script, not by your judgement. A worktree
on this machine carrying unlanded work means somebody is at that desk, and the
fleet scan cannot see it — the work is often unpushed, so there is no claim and
the branch reads eligible. Dispatch counts it
skippedand names the path. Do not route around it by deleting the worktree without looking, and do not claim the branch by hand to make it dispatchable. - Never dispatch a blocked wave. Eligibility lives in
plot-fleet-scan.sh; do not second-guess it or hand-pick a branch from a later wave. - Never delete another session's worktree or ref. A rejected claim means
someone else is working there. Cleanup belongs to
/plot-reconcile. - Never merge. Workers open PRs; merge authority stays with the human.
- Re-running is safe. Existing worktrees are adopted, claimed branches stay claimed. A dispatcher that dies halfway through can simply be run again.
Common Mistakes
| Mistake | Effect | Prevention |
|---|---|---|
| Fanning out without a dry run | Five agents start on work the user wanted scoped | --dry-run first, always, and ask |
| Dispatching every eligible branch by default | PR review becomes the bottleneck; DoD gaps pile up | Ask for a count; offer --max |
| Treating a rejected claim as an error | Duplicate work, or a deleted worktree someone was using | Rejection is normal — it means the lock worked |
Trusting claimed=0 as "nobody is working" | A second agent onto finished work — measured, four live agents, two green branches offered | claimed counts pushed refs; the held-branch gate reads this machine's worktrees |
| Creating a worktree by hand, then dispatching | No claim ref exists, so the fleet reads the branch as free | Dispatch through the script; the gate now refuses what the shortcut left behind |
| Creating worktrees inside the repo | They appear in the repo's own status and globs | Worktrees are siblings: ../plot-wt-<suffix> |
| Starting workers that merge their own PRs | Concurrent merges invalidate each other's bases | The worker command must say "open a PR, do not merge" |
| Stopping at the fan-out | A prepared, claimed worktree that nobody was handed — the gap a human closed by hand every time | Step 5: /plot-implement per dispatched branch |
Fanning out with worker=unconfigured and not saying so | Claimed branches nobody is working on, and a last line that reads like success | Step 3 asks once; step 6 relays worker= |
Suggesting an example Worker command | The example becomes a template, and Plot has hardcoded agent tooling (Principle 5) | Ask the question; offer no command, not even "for instance" |
| Asking again after an empty answer | A nag, answered with whatever silences it — including a wrong command | Record none; it means asked-and-declined |
Asking at /plot-init | A shrug at adoption writes an empty key nobody revisits | Ask at the first dispatch, where the consequence is concrete |
Writing the brief here instead of calling /plot-implement | A second definition of what an implementer needs, drifting from the first | One definition; dispatch invokes, never re-implements |