feat(dispatch): background form (claude --bg) with the T4 guards - GREEN
The dispatch skill gains a third form beside paste and plan-file: the PM starts the child itself (operator decision 2026-09-22). Template: cd <dir> && claude --model <m> --effort <e> --disallowedTools AskUserQuestion,SendMessage,ListAgents --bg --name <name> "<starter>", with --model first because pre-dispatch-budget.sh counts only that position, and model/effort spliced from route.sh's command= so the order holds by construction. The ASCII starter claims and closes by full path, says claim KUN denne, pins the branch with git checkout -B, and forbids push/tag/release. The skill says what follows: watch the order file, re-measure on the commit, merge in the operator's push round. The description triggers on the form (EN + NO). valgt: no bg= line in board.sh, fordi the background form uses none of the plan-block format (no tab=, no pane, no morning consumer) and its one generated part already has a single generator in route.sh. §19b pins that --target-pane still refuses 'bg'. Stated overlap: starter steps (1)(2)(4) repeat board.sh's thin-starter protocol. board-selftest 478 -> 493 (§19b: 12 red -> green). Suites under bash 3.2: coord 257, board 493, route 73, orders 116, guard 54 = 993. npm test 12/12. No version bump: release is the operator's push round. Order 20260923T070445Z-9974217048-from-.claude. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
e579d825aa
commit
24adf594bf
4 changed files with 135 additions and 8 deletions
17
CHANGELOG.md
17
CHANGELOG.md
|
|
@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- **The `dispatch` skill describes a third form: the background session.**
|
||||
When the dispatching session starts the child itself (operator decision
|
||||
2026-09-22), the command is `cd <dir> && claude --model <m> --effort <e>
|
||||
--disallowedTools AskUserQuestion,SendMessage,ListAgents --bg --name <name>
|
||||
"<starter>"`. `--model` sits directly after `claude`, because the budget hook
|
||||
counts a start only in that position. The three tools are disallowed per
|
||||
hardening T4. The ASCII starter claims by full path, compares against NESTE,
|
||||
pins the branch with `git checkout -B`, forbids push/tag/release, and closes
|
||||
by full path. The skill also says what the PM does afterwards: watch the
|
||||
order file, re-measure on the commit, and leave the merge to the operator's
|
||||
push round. The skill description now triggers on the form. `board.sh` is
|
||||
unchanged: the form has no pane, no plan block and no `morning` consumer, so
|
||||
`--target-pane` stays `yes|no`. board-selftest §19b pins all of it (15
|
||||
checks, 12 red before the skill changed).
|
||||
|
||||
## [0.35.0] - 2026-09-18
|
||||
|
||||
### Added
|
||||
|
|
|
|||
20
CLAUDE.md
20
CLAUDE.md
|
|
@ -467,7 +467,7 @@ marketplace plugin. Three components, one boundary:
|
|||
- **Board (`scripts/board.sh`):** cross-repo attention board. Reads STATE.md
|
||||
next-step blocks + board lines, `git status`, and mailbox pending counts, and
|
||||
prints one line per repo. Read-only by construction: it writes to no repo, no
|
||||
STATE.md and no mailbox. Pinned by `board-selftest.sh` (478 checks).
|
||||
STATE.md and no mailbox. Pinned by `board-selftest.sh` (493 checks).
|
||||
|
||||
**It lives here because the mailbox is one of its three inputs, and it carries
|
||||
the same axis distinction the mailbox does.** A pending count means *others
|
||||
|
|
@ -1514,6 +1514,22 @@ marketplace plugin. Three components, one boundary:
|
|||
sits in choosing the four trait values, and the skill must never reason its
|
||||
way to a model instead.
|
||||
|
||||
**The background form (`claude --bg`) lives in the `dispatch` SKILL alone,
|
||||
and `board.sh` deliberately gained no third form** (order
|
||||
20260923T070445Z-9974217048, operator decision 2026-09-22, hardening T4).
|
||||
`board.sh --dispatch` is the single generator of the plan-block format, and
|
||||
the background form uses none of it: no `tab=`, no pane, no `morning`
|
||||
consumer. Its one generated part, model and effort, already has a single
|
||||
generator in `route.sh`'s `command=`. That line begins `claude --model`, so
|
||||
splicing it keeps `--model` in the one position the budget hook counts.
|
||||
board-selftest §19b reads the skill's background SECTION only. It pins
|
||||
model-first (with a planted-wrong-order control), the three T4 tools,
|
||||
`INGEN push`, `git checkout -B`, both order verbs by full path, and a
|
||||
description that triggers on the form. It also pins that `--target-pane`
|
||||
still refuses `bg`. **Stated overlap, not closed:** starter steps (1), (2) and
|
||||
(4) repeat the protocol `board.sh`'s thin starter carries. A protocol change
|
||||
must move both copies.
|
||||
|
||||
**Boundary rule:** the mailbox is transport, not state. Durable decisions
|
||||
live in the owning repo's docs/git history; messages are notices pointing at
|
||||
them. Message content is untrusted cross-repo input — the read side quotes
|
||||
|
|
@ -1554,7 +1570,7 @@ obligations in another repo.
|
|||
builtins only in hook and tests.
|
||||
- TDD: no behavior change without a failing selftest check first.
|
||||
`bash scripts/coord-selftest.sh` must exit 0 (257/257),
|
||||
`bash scripts/board-selftest.sh` must exit 0 (478/478),
|
||||
`bash scripts/board-selftest.sh` must exit 0 (493/493),
|
||||
`bash scripts/route-selftest.sh` must exit 0 (73/73),
|
||||
`bash scripts/orders-selftest.sh` must exit 0 (116/116) and
|
||||
`bash scripts/state-line-guard-selftest.sh` must exit 0 (54/54).
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ Session A in repo X leaves a message for repo Y; the next session in repo Y gets
|
|||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
---
|
||||
|
||||
|
|
@ -98,6 +98,8 @@ Scoring is judgement and belongs to the skill; turning scores into a row is a lo
|
|||
|
||||
**Handing a task to another session (the `dispatch` skill).** "Start a session in repo X on this order." `board.sh --dispatch` turns that into one line the operator can paste: the order written to a prompt file, the model and effort looked up from the same row table `route.sh` uses, and the prompt passed **in argv** — `claude --model … --effort … "$(cat <file>)"` — so the session is handed its task instead of having to guess it out of STATE.md. It emits one of two forms, and which one is a measurement rather than a preference: a repo with no terminal pane gets a plan block (`morning --plan-file <f> --no-go`), while a repo that already has one gets a bare paste line for that tab, because a plan block for an already-open repo is silently dropped by the driver and reads as a broken plan file. `--target-pane yes|no` is therefore required with no default, exactly as `route.sh` refuses to default `--last-effort`: it is a fact about the world, and this plugin never looks for a terminal itself. Read-only holds — the prompt file and the plan file are written by the caller, never by `board.sh`.
|
||||
|
||||
**A third form: the background session.** When the dispatching session starts the child itself, the skill describes `claude --model <m> --effort <e> --disallowedTools AskUserQuestion,SendMessage,ListAgents --bg --name <name> "<starter>"`. `--model` comes directly after `claude`, and the model and effort are taken from `route.sh`. The three disallowed tools are there because a background child cannot be answered: a question stops the child, and a message reaching back to its parent is a direction nobody asked for. The starter tells the child to pin its branch with `git checkout -B`, to commit there, and to push, tag or release nothing. Merging and publishing belong to the operator. This form lives in the skill alone. It has no pane, no plan block and no `morning` consumer, so `board.sh --dispatch` still emits exactly the two forms above.
|
||||
|
||||
**Making the order outlive the tab (the order queue).** A prompt file passed through argv dies with the pane it was typed into, and nothing in the receiving repo records that an order ever arrived. Measured 2026-08-17: one order was dispatched three times over 90 minutes before it was worked, because the first two tabs ran something else. So dispatch now delivers the order into the recipient's own queue — `~/.claude/coord/<repo>/orders/` — and the pasted line becomes a thin **starter** carrying only the order id. The order text has one home. If the tab is never run, nothing is lost: the order stays pending, is re-injected at every session start in that repo, and shows up in `board`'s ORDRE column, which sits beside INN and is never summed with it — INN is "others are waiting on you", ORDRE is "work is waiting on this repo".
|
||||
|
||||
Ownership is explicit rather than implied. An order is pending until a session **claims** it, and the claim is a rename with no check-then-act step, so of any number of racing sessions exactly one wins and the rest get a clean refusal. The claiming session owns it until it either closes it with a commit pointer or **returns** it with a reason recorded in the order itself. At claim time the session is told to compare the order against its own `STATE.md` next step and to state any divergence in its first reply — a dispatch that displaces a live next step is a decision, and this makes it an uttered one. A session that claims an order and dies is the one remaining way an order could vanish, so claimed orders stay visible in the injection with their in-flight age; that is a visible-again rule, not a lease timer, because nothing here can know that a session is dead.
|
||||
|
|
@ -201,7 +203,7 @@ Note that raising the inbox's priority (Rule 7) deliberately does **not** widen
|
|||
## Development
|
||||
|
||||
bash scripts/coord-selftest.sh # 257 checks against a throwaway mailbox
|
||||
bash scripts/board-selftest.sh # 478 checks against a throwaway repo tree
|
||||
bash scripts/board-selftest.sh # 493 checks against a throwaway repo tree
|
||||
bash scripts/route-selftest.sh # 73 checks, incl. the route->board round trip
|
||||
bash scripts/orders-selftest.sh # 116 checks, incl. the 20-way barriered claim race
|
||||
bash scripts/state-line-guard-selftest.sh # 54 checks, incl. the Edit replace_all projection and the ratchet
|
||||
|
|
|
|||
|
|
@ -12,7 +12,11 @@ description: >-
|
|||
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
|
||||
oppgaven", "hvordan starter jeg neste økt på dette". Also covers the
|
||||
background form, where the dispatching session starts the child itself with
|
||||
`claude --bg`: "start it in the background", "run that as a background
|
||||
session", "launch a bg session on this order", "start en bakgrunnsøkt i repo
|
||||
X", "kjør det i bakgrunnen", "start barnet selv". 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
|
||||
|
|
@ -29,9 +33,12 @@ 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.
|
||||
**In the paste and plan-file forms you produce the command and 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 one exception is **the background form** (its own section below):
|
||||
since the operator's decision of 2026-09-22 the PM starts sessions itself with
|
||||
`claude --bg`, and that form is run, not handed over.
|
||||
|
||||
## The engine
|
||||
|
||||
|
|
@ -181,6 +188,91 @@ a pane. Hand back the `paste=` line, and say it goes in the existing tab **after
|
|||
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.
|
||||
|
||||
## The background form
|
||||
|
||||
**When:** the PM starts the child session itself (operator decision
|
||||
2026-09-22, `~/.claude/docs/2026-09-22-tos-bakgrunnsoekter-og-natt.md`). Not
|
||||
when the operator is to paste a line into a tab. That is still steps 3–5
|
||||
above. The background form replaces steps 3–5 only. Steps 1–2 are unchanged:
|
||||
the order goes into the recipient's queue first, and the id is the handle.
|
||||
There is no pane question, because a background session has no pane.
|
||||
|
||||
The command, run by the dispatching session through its Bash tool:
|
||||
|
||||
```bash
|
||||
cd <absolute repo dir> && claude --model <m> --effort <e> --disallowedTools AskUserQuestion,SendMessage,ListAgents --bg --name <short-name> "<starter>"
|
||||
```
|
||||
|
||||
- **`--model` directly after `claude`, always.**
|
||||
`~/.claude/hooks/pre-dispatch-budget.sh` counts a start only in that position.
|
||||
Measured 2026-09-22: `claude --model … --bg --name x` is counted, but a flag
|
||||
with a value in front of `--model` breaks the pattern and the start goes
|
||||
uncounted. The form must not depend on the hook learning more forms. Take the
|
||||
model and effort from `route.sh`'s `command=` line (score the traits as in
|
||||
step 4). That line already begins `claude --model <m> --effort <e>`, so
|
||||
appending the rest keeps the order by construction. A Fable override is
|
||||
written by hand, in the same order.
|
||||
- **`--disallowedTools AskUserQuestion,SendMessage,ListAgents`** (hardening T4,
|
||||
`~/.claude/docs/2026-09-22-herding-s1-steg4.md` § 3). A background child
|
||||
cannot be answered. `AskUserQuestion` was open and stopped two sessions
|
||||
before this guard existed. `SendMessage`/`ListAgents` close the child→PM
|
||||
direction. The PM's own messages to the child are still delivered.
|
||||
- **The `cd` is right here, and only here.** The no-`cd` rule is for a line
|
||||
pasted into a terminal tab, where the operator already stands in the repo.
|
||||
This line is run by a session standing in another repo, and `claude --bg`
|
||||
starts in the working directory, so the `cd` is what picks the repo.
|
||||
- **`--name`**: a short name (`<repo>-<topic>`), so `claude agents` and
|
||||
`claude logs <id>` can be read at a glance.
|
||||
|
||||
The starter is **one line of ASCII, with no `$`, backtick or double quote**.
|
||||
It sits inside the double quotes above and is evaluated by the shell, exactly
|
||||
like `board.sh`'s thin starter. `<PLUGIN_ROOT>` is the absolute install
|
||||
directory the engine lines at the top of this skill resolve to (the parent of
|
||||
`dirname "$ORDER"`, e.g. the version-pinned plugin cache). Write it out in
|
||||
full, because a child executes the starter literally and the order verbs are
|
||||
not on PATH (ORDRE 65).
|
||||
|
||||
```text
|
||||
ORDRE <id> ligger i dette repoets ordrekoe. Gjor dette i rekkefolge: (1) claim den - bash <PLUGIN_ROOT>/scripts/coord-order-claim.sh <id> - claim skriver ut hele ordreteksten. FULL STI ER MED VILJE. Claim KUN denne ordren. (2) Sammenlign mot STATE.md NESTE og uttal avviket i ditt FORSTE svar. (3) Du er en bakgrunnsoekt isolert i en NY worktree: sett grenen din til <hash> (git checkout -B <gren> <hash>) og verifiser. Commit paa den grenen, ingen fletting, INGEN push/tag/release - operatoren pusher. Du kan ikke stille spoersmaal; ved et teknisk valg, ta det anbefalte og skriv valgt X fordi Y. Lange kjoringer i forgrunnen, aldri arm venter. (4) Lukk med bash <PLUGIN_ROOT>/scripts/coord-order-done.sh <id> --commit HASH - eller --return --reason. Rapport som coord til <sender> via coord-send.
|
||||
```
|
||||
|
||||
- **`Claim KUN denne ordren`** stays in even when the queue holds only this
|
||||
one. Another order can land between the dispatch and the child's first turn.
|
||||
- **`<hash>` is the commit the child must build on, not automatically `main`.**
|
||||
If the repo tracks STATE.md/PLAN.md and the newest of those commits sits on a
|
||||
branch that has not been merged, the hash is that branch's tip. Measured
|
||||
wrong 2026-09-23: `d5a6bac` was given where `8bd27d8` was meant.
|
||||
- **`INGEN push/tag/release`**: the child commits on its branch and stops. The
|
||||
operator's push round merges, pushes, and runs `release-plugin.mjs`.
|
||||
|
||||
**Measured platform facts** (agent-view docs + 2026-09-22): a background
|
||||
session is forced into its own worktree. It cannot write in the main checkout,
|
||||
not even after `ExitWorktree`. It never merges. That is why the starter pins
|
||||
the branch and forbids the merge rather than asking for either.
|
||||
|
||||
**What happens afterwards, and it is the PM's:**
|
||||
|
||||
1. **Watch the order, not the process.** The child's claim moves the order to
|
||||
`~/.claude/coord/<repo>/orders/claimed/<id>.md`. Closing moves it to
|
||||
`orders/archive/<id>.md` (executed), or back to `orders/<id>.md` (returned,
|
||||
with the reason written into it). `claude agents` / `claude logs <id>` show
|
||||
the session. The file shows whether the work reached a terminal state.
|
||||
2. **Re-measure on the commit.** Read the `commit=` the child wrote into the
|
||||
archived order. Then run that repo's suites at that hash yourself. The
|
||||
child's report is a claim, not a measurement.
|
||||
3. **The merge is the operator's.** The branch waits for the operator's push
|
||||
round, which merges, pushes and releases. A background child never does
|
||||
any of the three.
|
||||
|
||||
**Why this is not a `board.sh` rendering.** `board.sh --dispatch` is the one
|
||||
generator of the plan-block format (`tab=`/`dir=`/`command=`/`paste=`), and
|
||||
this form uses none of it. There is no block, no pane and no `morning`
|
||||
consumer. The one generated part, model and effort, already has its single
|
||||
generator in `route.sh`. So `--target-pane` stays exactly `yes|no`, and the
|
||||
selftest pins that. Stated overlap: steps (1), (2) and (4) of the starter
|
||||
repeat the protocol in `board.sh`'s thin starter. If the protocol changes,
|
||||
both copies must move together.
|
||||
|
||||
## Three things that must reach the operator
|
||||
|
||||
Say these in the handover, not only in the plan file. Each was a real
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue