"Start a session in repo X, on order Y, at cost Z" was produced by hand, and it misfired four times on 2026-08-16 across two repos. Three distinct holes, all measured, all closed here: 1. A bare `claude --model X --effort Y` forces the operator to type Go, and the session then guesses its task out of STATE.md. The emitted command carries the prompt in argv: `... "$(cat <file>)"`. Verified directly that this passes the file's bytes as ONE argv element with no re-evaluation, so $(...), backticks, quotes and UTF-8 in the prompt BODY are inert - only the PATH is expanded, so it must be absolute and shell-clean. 2. --no-go stops only the follow-up Go message, never the work (morning:806). The plan-file form says so in its own output, not just in a comment. 3. A session dispatching its own next session gets an empty plan: morning's plan_drop_open (morning:1788) drops a block whose repo already has a pane, and --dry-run says "0 of 1", which reads as a broken plan file. --dispatch therefore emits two forms, chosen by --target-pane: a plan block, or a bare paste line for the tab that already exists (and no tab= key at all, so it can never be fed to morning as a plan). Generator ownership, the question left open for two sessions: it goes in board.sh, which already owns the block format including paste=. A second emitter of tab=/repo=/dir=/command=/paste= would be two copies of one file format. Read-only survives - the prompt file and the plan file are written by the caller, the brief-nightly.sh split unchanged. --target-pane yes|no is REQUIRED with no default, the same rule --last-effort carries: it is a measurement (morning --probe-panes, which works without a tty), and the dry-run cannot substitute for it - run from a Claude session morning reports "window: unknown ... assuming an empty window" and plan_drop_open never fires, so a dry-run gate would pass the self-dispatch case every time. Cost comes from route.sh's row table; --dispatch deliberately takes no --model/--effort, because --advisor opus is a property of the ROW and a dispatch taking the model directly has no honest source for that flag. New skills/dispatch/SKILL.md is the front door. board-selftest 183 -> 217. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ett8uHMDLir6trFaMzrYRu
174 lines
8.4 KiB
Markdown
174 lines
8.4 KiB
Markdown
---
|
|
name: dispatch
|
|
description: >-
|
|
Turn "start a session in repo X, on order Y, at cost Z" into a complete,
|
|
verified startup command — the prompt written to a file, the model and effort
|
|
derived from the rubric, and the output form chosen by whether the target repo
|
|
already has a terminal pane. Use whenever work is being handed to a session in
|
|
another repo, or to this repo's own next session: "dispatch a session in repo
|
|
X", "start a session there with this order", "give me the startup command for
|
|
repo Y", "hand this work to <repo>", "write the plan file for that session",
|
|
"open a tab for X with this task", "how do I launch the next session on this".
|
|
Also triggers on Norwegian phrasings: "dispatch en økt i repo X", "start en økt
|
|
der med denne ordren", "gi meg oppstartskommandoen for Y", "send arbeidet til
|
|
<repo>", "skriv planfila for den økten", "åpne en tab for X med denne
|
|
oppgaven", "hvordan starter jeg neste økt på dette". Trigger even when no tool
|
|
is named — producing a runnable startup command for another session IS this
|
|
skill. Not for choosing WHICH repo deserves the next session (that is `board`),
|
|
not for scoring model and effort alone (that is `route`), and not for sending a
|
|
message to another repo (that is `coord-send`).
|
|
version: "0.24.0"
|
|
---
|
|
|
|
# dispatch — hand a session a task it can actually start on
|
|
|
|
A dispatch is one line the operator pastes and one file that line reads. Both
|
|
halves are easy to get wrong in ways that look right: a command with no prompt
|
|
in it, a plan file the driver silently discards, a `--no-go` that does not stop
|
|
what everyone assumed it stopped. All three were measured on 2026-08-16 — four
|
|
separate misfires in one day, by two different repos — and this skill exists so
|
|
they are not re-derived a fifth time.
|
|
|
|
**You produce the command. You never run it.** Starting a session in another
|
|
repo spends the operator's quota and takes an action inside a repo this session
|
|
does not own. Hand back the finished command and stop.
|
|
|
|
## The engine
|
|
|
|
BOARD="${CLAUDE_PLUGIN_ROOT}/scripts/board.sh"
|
|
|
|
"$BOARD" --dispatch --repo <name> \
|
|
--prompt-file <absolute path> \
|
|
--target-pane <yes|no> \
|
|
--path <known|partial|undetermined> \
|
|
--verification <strong|weak|none> \
|
|
--reversibility <cheap|costly|one-way> \
|
|
--scope <local|multi-file|cross-cutting> \
|
|
--rationale "why these four scores"
|
|
|
|
It writes nothing and prints one block. Exit 2 means it refused — read stderr
|
|
and fix the call; every refusal is a case where a command would have been wrong
|
|
rather than merely imperfect.
|
|
|
|
It is `board.sh` and not a script of its own because the block format
|
|
(`tab=`/`repo=`/`dir=`/`command=`/`paste=`) has exactly one generator. Two
|
|
emitters of one file format is the drift defect this repo's CLAUDE.md warns
|
|
about.
|
|
|
|
## The four steps, in order
|
|
|
|
### 1. Write the prompt file
|
|
|
|
The order goes in a file, in plain prose, and the file is what the session
|
|
actually receives:
|
|
|
|
```bash
|
|
mkdir -p "$HOME/.claude/dispatch"
|
|
PF="$HOME/.claude/dispatch/<repo>-$(date -u +%Y%m%dT%H%M%SZ).prompt"
|
|
```
|
|
|
|
Runtime scratch, not repo content — but it must survive until the operator
|
|
pastes the line, so never write it to a directory that gets cleaned between
|
|
sessions. The path must be absolute and shell-clean (letters, digits, `/`, `.`,
|
|
`_`, `-`); `--dispatch` refuses anything else, because the path is expanded
|
|
inside the command while the *contents* are not.
|
|
|
|
Write the whole order: the concrete task, which discipline applies (Iron Law,
|
|
TDD, which file the test goes in), and what must **not** be triggered. A prompt
|
|
that says only "continue" forces the receiving session to guess its task out of
|
|
STATE.md, which is the thing handing over a prompt is supposed to prevent.
|
|
|
|
Norwegian prose, `æøå`, quotes, `$` and backticks are all fine in the file. That
|
|
is measured, not assumed: `"$(cat file)"` hands the bytes to the session as one
|
|
argv element with no re-evaluation. The ASCII rule applies to shell and hook
|
|
*code*, not to a prompt travelling through argv.
|
|
|
|
### 2. Measure whether the target already has a pane
|
|
|
|
```bash
|
|
morning --probe-panes | grep "<absolute dir of the target repo>"
|
|
```
|
|
|
|
A hit means `--target-pane yes`. **Measure it; never assume it.** This is the
|
|
one input `--dispatch` refuses to default, for the same reason `route.sh`
|
|
refuses to default `--last-effort`: it is a fact about the world, and guessing
|
|
it produces a dispatch that verifies green and opens nothing.
|
|
|
|
Two facts about this measurement, both verified 2026-08-16 against the installed
|
|
`morning`:
|
|
|
|
- `--probe-panes` **works from a Claude session**, without a tty. It cannot
|
|
identify the anchor pane, but the `DIR` column — the part you need — is there.
|
|
- A session dispatching **its own next session** is always `--target-pane yes`.
|
|
That is not a special case for one repo; it is what self-dispatch is, and it
|
|
is where all four of the day's misfires landed.
|
|
|
|
### 3. Score the four traits and call `--dispatch`
|
|
|
|
Scoring is judgement and it is yours; the model, effort and advisor flag are a
|
|
lookup and are `route.sh`'s. Score the task **the dispatched session** will do,
|
|
using the `route` skill's trait table.
|
|
|
|
`--dispatch` deliberately takes no `--model`/`--effort`. `--advisor opus` is a
|
|
property of the rubric *row* — two rows share a model/effort pair while
|
|
differing on it, and the CLI accepts a wrong advisor silently — so a dispatch
|
|
that took the model directly would have no honest source for that flag. If the
|
|
right call is a Fable row, the rubric cannot produce it: write that command by
|
|
hand, and say in the handover that it is a recorded override, running without an
|
|
advisor.
|
|
|
|
### 4. Verify, then hand it over
|
|
|
|
**`--target-pane no` (plan-file form).** Write the whole output to a file and
|
|
dry-run it:
|
|
|
|
```bash
|
|
"$BOARD" --dispatch ... > "$HOME/.claude/dispatch/<repo>-<ts>.plan"
|
|
morning --plan-file "$HOME/.claude/dispatch/<repo>-<ts>.plan" --dry-run
|
|
```
|
|
|
|
Expect `opening: 1 of 1`. Then hand back:
|
|
|
|
morning --plan-file <path to the plan file> --no-go
|
|
|
|
**Know what that dry-run does not prove.** It proves the block parses and yields
|
|
a command. It does *not* answer the pane question: run from a Claude session
|
|
there is no tty, so `morning` reports `window: unknown ... assuming an empty
|
|
window` and `plan_drop_open` never fires. A gate built on the dry-run would pass
|
|
the self-dispatch case every single time — the one case it would exist to catch.
|
|
Step 2 is the measurement; this is a parse check.
|
|
|
|
**`--target-pane yes` (paste-only form).** There is no plan file, deliberately:
|
|
`morning`'s `plan_drop_open` (morning:1788) drops a block whose repo already has
|
|
a pane. Hand back the `paste=` line, and say it goes in the existing tab **after
|
|
`/exit`**. Never prefix it with `cd` — one repo per terminal tab, and the
|
|
operator is already standing in that one. `morning --relaunch` bypasses the
|
|
filter but opens a *second* tab beside the existing one, which is rarely wanted.
|
|
|
|
## Three things that must reach the operator
|
|
|
|
Say these in the handover, not only in the plan file. Each was a real
|
|
correction, not a hypothetical:
|
|
|
|
1. **The command carries the prompt in argv.** `claude --model X --effort Y` on
|
|
its own is not a dispatch; it is a session waiting for someone to tell it
|
|
what to do. Delivered bare twice on 2026-08-16, corrected by the operator
|
|
with "gi meg alltid komplette oppstartskommandoer for nye sesjoner".
|
|
2. **`--no-go` does not make the session wait.** It suppresses only the
|
|
follow-up Go message — `morning:806` is exact: "--no-go says nothing is typed
|
|
once the startup command is in". The startup command, prompt and all, is
|
|
typed regardless, so the dispatched session starts working on its own. An
|
|
operator decision was once taken on the opposite premise and had to be
|
|
corrected before the run.
|
|
3. **Which form you produced, and why.** "Plan file, because `<repo>` has no
|
|
pane" or "paste line, because `<repo>` already has one and a plan block for
|
|
it would be dropped". The form is a consequence of a measurement, and the
|
|
operator should be able to see the measurement.
|
|
|
|
## Where a dispatch is not enough
|
|
|
|
The prompt file is scratch: it carries the order to one session and nothing
|
|
records it afterwards. When the receiving repo needs a durable record of the
|
|
order — something its next session will still see after this one is gone — send
|
|
it a `coord-send` message as well. The dispatch starts the session; the mailbox
|
|
is what makes the order survive it.
|