v7.5 a Claude Code mod · Claude Code 2.1.287 · Opus 5.5 + Sonnet 5.5

One thread that
never fills up.

The prime only dispatches. It never reads a file, runs a command or holds a work product: a mod refuses it. It hands goals to sub-orchestrators, which dispatch agents or do the work themselves and hand back one line. That line goes into a memory the prime can zoom into, so the thread lasts as long as the work does.

0
files the prime reads during a run. The guard refuses everything but dispatch, ask and memory.
≤280 B
what crosses back to the prime from a phase: one line, noted verbatim in its memory.
−28%
spend of a v7 prime against one carrying 831K tokens: $14.78 vs $20.40, same score (E1b, one seed).
30
rules, one file each. New: red, green, blue for every behavior change.
01

The prime conducts. It never plays.

baton always said the conductor never plays a note. v7 makes it mechanical: during a run the main session's tool calls go through a guard, and anything but dispatching, asking you, or using its memory is refused with the route. Context the prime never takes in can't fill it up.

prime · the main sessionHolds the run's goal, your rulings and its memory. Tools: Agent, AskUserQuestion, SendMessage, mcp__baton__*. Nothing else.
dispatches a goal ↓  ·  gets back one line ≤ 280 bytes ↑
sub-orchestrator · bootstrapdirective, planner, plan verifier (fresh)
sub-orchestrator · phase P3phase brief, dispatch each node, verify, phase gate
sub-orchestrator · finalsynthesizer, then the one-page brief
dispatches nodes ↓  ·  reads envelopes ↑, never work products
baton:workerfrontier · Opus 5.5 · anything with judgment, red → green → blue
baton:worker-cheapSonnet 5.5 · what a command can settle
baton:verifieralways fresh, always frontier · per-criterion verdict, computed
the guard

Fails closed

The mod tells the prime from a subagent by the agentId on each tool call. If the guard throws, the call is refused. A sub-orchestrator's calls pass untouched: its context is there to be spent.

the binding

Two models, enforced

Every spawn during a run is bound at agent.spawn: Opus 5.5 for every role, Sonnet 5.5 for a type or name ending -cheap, whatever the caller asked for. Merges of the memory run on Sonnet.

the record

Unchanged on disk

Envelopes, per-criterion verdicts, refutation triage, the ledger, gates and TEAM: github all work as in v6. Sub-orchestrators keep that record; the prime keeps one line per phase.

02

Inside the mod.

A mod is a JavaScript module Claude Code runs inside its own loop. It sees every prompt, model request, tool call, spawn and redraw, and can answer, change or refuse each one. baton’s registers 27 hooks, 16 commands, 11 tools and 16 settings. None of it depends on an agent remembering to do something: each piece runs where its event happens.

When the session startsonce

session.start

Reads the engine version. The guard tells the prime from a subagent by agentId, verified on 2.1.287; on any other version it says so and fails closed.

session.start

Runs the doctor in the background (node, git, gh and its login, ripgrep, the memory) and checks for a newer baton: a failed check, or an update with 9 to install it, shows in the band.

SessionStart · startup, resume

A new session in a directory with an active run is told it is that run’s prime: wake from the memory first, then dispatch.

session.start

Registers /baton, and in a run directory the seven memory tools. Starts a 150 ms ticker for owed rotations and the kickoff, and the PR poller.

When you send a promptevery prompt

prompt.submit

Your words become a run note. In the background, Sonnet reads them for a standing rule; one it finds becomes a ruling in the project memory.

prompt.submit

After a rotation, the prompt carries the instruction to call memory_wake first.

Every model requestevery step, every agent

turn.step

Reads the request’s usage and the agent that made it: fresh tokens and cache reads, by role, phase and node, into _orch/spend.json. The response streams on untouched.

Every tool callbefore it runs

tool.call

The prime guard. A call with no agentId is the prime’s: only Agent, AskUserQuestion, SendMessage, ToolSearch, the prime skill and mcp__baton__* pass. Anything else is refused with the route. If the guard throws, the call is refused.

tool.call · mcp__baton__*

Serves memory_wake, _note, _zoom, _recall and the three project_* tools from the log and tree on disk. Wake leads with your rulings.

tool.call · Bash

Forbidden files. A git add naming .env, a key or credentials is refused from any agent, run or no run.

tool.call · Bash

The command gate. Push, PR create/merge/close, publish, reset --hard and rm -r from any depth wait on a dialog naming the chain. With nobody at the terminal, it is asked on the PR instead.

tool.call · Bash

Checkpoints. Before reset --hard, clean -f or rm -r runs, the whole tree is snapshotted to a hidden ref; /baton restore puts it back.

tool.call · Agent

The phase gate. After a sub-orchestrator reports DONE, the prime’s next dispatch waits until you approve the phase or send it back with a reason.

tool.check · Bash

Enforced rulings. After the permission rules decide, a command matching an enforced ruling’s pattern is refused, quoting the ruling.

Every spawnbefore a subagent starts

agent.spawn

The binding. Opus 5.5 for every role, Sonnet 5.5 for a type or name ending -cheap, whatever the caller asked for. The spawn joins the agent tree under its parent.

agent.spawn

The budget. Another review or fix round is refused once the goal has spent tokensPerGoal, with a note at 80%.

agent.spawn

Lessons that stick. A worker or sub-orchestrator spawned on code an earlier refutation touched gets those lessons in its prompt.

agent.spawn · tool.call

Pause. While /baton pause holds, every new spawn and every gated command waits.

agent.spawn

A clean base first. A PR reviewer on a PR that conflicts with its base is refused: merge the base in, then review.

Every finished turnprime or subagent

turn.complete

A subagent the prime dispatched: its one-line return becomes a run note, tagged with its role. The prime’s own reply: its first line becomes one too.

turn.complete

A sub-orchestrator whose return leads with DONE joins the phase-gate queue, and the band flags it.

turn.complete

Writes the spend down and re-reads the record: every computed state that moved becomes a row in _orch/track/.

As the context growsafter each turn

session.measure

The context gauge. At rotateAtPercent (35%): a one-line handoff from a fork of the prime, then a compaction off the clock.

session.compact · SessionStart

Every compaction in a run counts as a rotation and arms the wake, delivered by whichever comes first: SessionStart, the next prompt, or the next allowed tool result.

Every two minuteson the clock, during a run

$.clock → gh pr view

The PR as data, no model: state, checks, mergeability, files, closing issues, comments. Merge-ready is computed row by row; the reviewer’s verdict is parsed from its comment.

$.clock → comments

From allowed answerers, /approve P3, /send-back P3 … and /approve push act as the gate. Other remarks become one line each, condensed by Haiku 4.5.

$.clock → the issue

The goal’s state as a baton:<state> label, and optionally a Projects board column. One spend comment on the PR, edited in place.

Whenever Claude Code drawsthe interface

ui.render · Pane

Opens by itself, with two tabs: Work (the goal, the PR and its review findings linked to their lines, the agent table with a trend column, the plan one line per phase with p for every node, what waits for you) and Memory (your rulings, then the memory browser). Every agent, phase, node and ruling row opens with ↑/↓ and Enter (b goes back); buttons approve phases and retract rulings; fields search the memory, add a ruling, or message a running agent.

ui.render · AbovePrompt

One line: plan usage, the context against its threshold, the cost, the goal and the PR. With a phase waiting, 1 typed into an empty prompt approves it.

ui.render · Spinner

The spinner names the phase and what the prime is waiting on.

command.run · /baton

start · stop · pause · resume · rotate · status · doctor · watch · next · rule · rulings · retract · enforce · checkpoints · restore. Text where nothing draws, so all of it works under claude -p.

One goal, end to end

What fires, in order, when you hand it an issue.

  1. /baton start #12 reads the issue with gh, writes the manifest ("prime": true, the run branch), notes the start, and starts the prime’s first turn with the issue verbatim and your rulings.command.run
  2. The prime tries to Read the codebase. Refused: dispatch a sub-orchestrator. It dispatches one for the bootstrap.tool.call
  3. The spawn is bound to Opus 5.5 and joins the tree. Every request it makes is metered from here on.agent.spawn · turn.step
  4. The bootstrap writes the directive and the plan, has it checked by a fresh spawn, and opens the branch and a draft PR that closes #12. It returns one line, which becomes a run note.turn.complete
  5. Phases run: workers go red, green, blue; verifiers re-run red against the unchanged code. Each move lands as a tracker row, and the issue’s label goes baton:building.turn.complete · $.clock
  6. A worker three levels down runs git push. You get a dialog: prime › P3 › T7 wants to git push. You approve it for the run.tool.call
  7. P3 returns DONE. The band flags it, and the prime’s next dispatch waits for your check. You send it back: the empty case is untested.turn.complete · tool.call
  8. At 35% context the prime rotates: a handoff line, a compaction, and its next act is memory_wake. Your rulings come first in what it sees.session.measure · session.compact
  9. After the last phase, the prime dispatches the PR reviewer. Its comment says CHANGES, 1 high. The poller notes it, and a steward runs a fix round.agent.spawn · $.clock
  10. Round 2: READY. Every merge row holds. The PR is marked ready, the label goes baton:ready, and the band says it is waiting for you.$.clock
  11. You merge on GitHub. The poller sees it and the goal goes merged. /baton next archives the run and starts the next issue in the queue.$.clock · command.run
/baton …
commanddoes
start <MODE> <TARGET|#n>arm the guard, write the manifest, start the prime
stopclose the run; the guard comes off; _orch/ stays
pause · resumehold every new spawn and gated command, then let it go on
doctorcheck node, git, gh and its login, ripgrep, the version, the memory
checkpoints · restore <id>the snapshots taken before reset/clean/rm, and putting files back
rotatehandoff, compaction and wake, now
statusrun, context, rotations, guard, approvals, the tracker
show <P3|T12|agent>the view Enter opens, as text
watch [label]the issue queue
next [#n]archive a finished run and start the next goal
rule <text>a standing ruling, by hand
rulingslist them, newest first
retract <#>withdraw one; the log keeps it
enforce <#> <regex>make one refuse matching commands
tools its agents get · mcp__baton__*
tooldoes
memory_wakethe run in a fixed budget of lines, rulings first
memory_noteone line, at most 280 bytes
memory_zoomopen a block back up to its notes
memory_recallregex over notes and summaries
project_wakethe memory that spans runs
project_notewhat the next run should know
project_recallsearch it; namespace selects one inside it
settings · userConfig
settingdefault
rotateAtPercent35
wakeBudgetLines96
memoryDir.baton/memory
approvalson
phaseGateon
prPollSeconds120
tokensPerGoal0 (off)
labelson
boardProject0 (off)
boardOwneryou
forbiddenFilesadded to the defaults

BATON_APPROVALS=0 turns both gates off for unattended runs; BATON_AUTOROTATE=0 leaves rotation to /baton rotate.

what it writes
pathholds
_orch/manifest.jsonthe run: "prime": true, mode, goal, branch
_orch/memory/the run memory: the log and the merge tree
.baton/memory/the project memory; ns/rulings inside it
_orch/track/one row per state change, measured
_orch/spend.jsontokens by role, phase and node
_orch/github/pr.jsonthe goal’s PR, written by the bootstrap
.baton/runs/<id>/finished runs, archived by /baton next

Tested: 61 node tests, including two end-to-end fixtures that run the real hooks module and memory store under a fake mods runtime and a scripted gh. Not yet tested: the live terminal UI and real GitHub. Mods are early access in Claude Code. The mod’s README →

03

A memory instead of a growing context.

Every return, every message you send and the first line of every prime reply is a note: one fixed-width record of at most 280 bytes, appended to a log. Pairs of notes merge into a summary, pairs of summaries into another, up a binary tree, compressed by Sonnet in the background.

  • wake shows the run in a fixed budget of lines: recent notes verbatim, older ones in blocks that grow with age.
  • zoom opens any block back up to its notes; recall searches all of it.
  • rotation: at 35% context the mod writes a handoff line, compacts the prime, and the prime's next act is memory_wake.
  • A project memory spans runs: decisions, criteria that proved vacuous, routes that failed.

The log, the tree and the wake view follow OptMem's ideas; the code is baton's own (OptMem ships no license).

  mcp__baton__memory_wake · illustrative
Standing rulings, newest first:
#3 PRs target main directly; never stack. [enforced]
#1 Fan-outs on Sonnet; Opus to synthesize.

memory: 214 notes, 213 summaries; 24 blocks
#0-127 (128 notes) bootstrap BUILD ./billing;
  41 nodes / 6 phases; P1–P3 CONFIRMED;
  Q-1 vendor key answered
#128-191 (64 notes) P4 parser: T14 refuted
  twice, escalated; a red test passed
  pre-change and was refuted (9.4)
#192-207 (16 notes) P5 export 9/9; approved
…
#211 [operator] ship P6, hold the migration
#212 [prime-reply] P6 dispatched, T31 held
#213 [sub] P6 DONE 6/7 · T31 parked on Q-4
rulings

Say it once

When you state a standing rule mid-run ("from now on…", "never…", a change of mind), Sonnet lifts it into the project memory's rulings. Every wake and every kickoff leads with the newest twelve, and the newest wins. Tested on ten real operator messages: five rules found, five one-offs left alone.

/baton rule <text>/baton rulings/baton retract <#>/baton enforce <#> <regex>
honest scope

What the memory is for

On Opus 5.5 we found no context rot to fix: a prime carrying 831K tokens lost nothing. The memory earns its place on cost (a lean prime is cheaper per run) and continuity: runs longer than a window, and rulings that hold across sessions. The numbers.

04

It is just there.

There is nothing to open. In any Claude Code session with baton loaded, one line above the prompt carries your plan usage (5 hours, the week, Fable), the context against its threshold and the session's cost, and the pane opens by itself as soon as there are agents to show. It has two tabs. Work is everything happening: the goal and its PR, a table of every agent, the plan one line per phase, what waits for you. Memory is everything the run remembers: your rulings, then the memory itself. This is what a run looks like; the tabs are live.

1 · work

Everything happening, in one view

Prime → sub-orchestrators → workers and verifiers, from every spawn's parent, every tool call's agent and every turn's end. Each row ends with the steps of what that agent is working on: a worker its node (red → green → blue → verified), a sub-orchestrator its phase, the prime the goal. Every row is a line of a table: the agent, its context against its own budget (the prime rotates at 35%; a subagent is told to finish or split at 50%), its model, its input, cache writes, cache reads and output, its cost priced per model (API-equivalent; a model without a verified price shows $?), its steps and its state. On a narrow terminal the least useful columns go first. ✉ on a running agent opens a field that messages it directly: steer one phase without going through the prime.

2 · memory

A browser over the tree

+ opens a summary block into its range, b goes back, a search field runs recall. The run is a few zoom hops from any fact.

2 · memory

Rulings first: add, retract, enforce

Type a ruling, x to retract it. Enforced rulings show their pattern. The rulings log is append-only, so the history stays.

The drawings above follow the mod's render code. The tests exercise it against a fake runtime; the live terminal UI has not been checked yet (mods are early access).

05

Track every level, like a parcel.

The goal, its PR, each phase and each node move through a fixed row of states. Every state is computed from the record, never claimed by an agent: a node is red when work/red.txt exists, verified when a fresh verifier's verdict says so. Each move is written down with the time it was seen, so how long every state took is measured.

goal#12 CSV export ↗
1.2M of 2M tok
✓queued2m
✓planned6m
✓building1h04
●reviewing18m
ready
merged
PR #47draft ↗
✓draft1h10
✓checks4m
✗reviewed
changes
14m
ready
merged
P4fix round 1
✓briefed1m
●dispatched11m
verified
approved
T31empty export
41k tok
✓red3m
✓green6m
●blue2m
verified
issue → PR

A goal is an issue

/baton start #12 makes the issue the goal. A draft PR closes it, one commit per node, each verdict a check. /baton watch queues issues labeled baton; /baton next starts the next one. The issue and PR are links in the pane.

pr reviewer + steward

An aggressive last look

After the last phase a fresh Opus reviewer tries to refute "this PR completes the issue": every acceptance criterion mapped to a red-then-green test, complexity in numbers, scope nobody asked for. A steward turns CHANGES into a fix round, at most three. baton never merges.

merge-ready, computed

No model reads the status

The PR is data: gh pr view --json every two minutes. Ready means every check green, the latest review READY, no open thread, mergeable, up to date. Haiku only condenses new comments to one line; /approve P3 on the thread works from your phone.

Hooks, not reminders. Ideas from talos, rebuilt so nothing has to be remembered. Spend is metered from every model request (turn.step) by the agent that made it. A token budget refuses another fix round at the spawn. A reviewer on a conflicting PR is refused until the base is merged in, and git add .env is refused from any agent. The goal's state is mirrored as a baton:<state> label on the issue, and on a Projects board if you want one. Every blocked question says what it rests on, and whether that is a rule (fix the cause) or a judgment (you may overrule it).

Whether the review loop earns its place is pre-registered: after ten merged goals, keep it, trim it to one round, or cut it, by the share of its findings you accepted. The rule →

06

Approve from anywhere in the tree.

A mod can hold any tool call, from any agent at any depth, and ask you first. The approval surfaces at the top however deep the agent sits, and every answer goes into the memory.

command gate

Irreversible commands wait

git push, gh pr create|merge|close, a release or package publish, reset --hard, clean -f, rm -r. The dialog names the chain (prime › P3 › T7 wants to git push) and your top rulings. Approve, approve for the run, refuse, or type a reason. With nobody to ask, it's refused.

phase gate

Finished phases wait for you

When a sub-orchestrator reports DONE, the prime's next dispatch waits until you approve the phase or send it back with what's wrong. Sent back, the prime re-dispatches that phase with your reason as its directive.

enforced rulings

Rules that bite

/baton enforce 3 git push.*\bmain\b turns ruling #3 into a refusal at the permission check, run or no run, quoting the ruling. Retract the ruling and the block lifts.

  dialog
baton: prime › P6 › T29 wants to git push:
  git push origin baton/build-20261004 --force-with-lease

Your rulings:
  #3 2026-10-04 Target every PR directly at main; never stack.

❯ 1. Approve
  2. Approve every "git push" for this run
  3. Refuse
  4. Type something…
07

Red, green, blue.

A test written after the code passes by construction. It describes what the code does, and it would have passed against a wrong implementation too. Rule 9.4 forces the order for every node that changes behavior.

Red work/red.txt

Only the tests, one or more per done-criterion. They run and fail against the unchanged code, for the reason the criterion names: not a syntax error or a missing import.

Green work/green.txt

The smallest change that passes them. The red tests are frozen: a green that needed one edited has redefined the criterion, so it goes back to red.

Blue work/blue.txt

Refactor with the suite green and no test touched, or write down that there was nothing to tidy. Each step is its own commit.

The verifier checks it, fresh. It re-runs the red tests against the parent of the red commit in a private worktree (a red that passes there refutes its criterion), checks that the freeze held, and that green and blue are green. Exemption is the planner's call, never the node's, and the plan verifier refutes one a test could answer. Unmeasured so far: the first run under it should count the reds the verifier refuted.
08

What we measured before building it.

v7 was built as an experiment: decision rules committed before any run, blind scoring, answer keys audited. The result was not what we set out to show, and the page says so.

experimentwhat it stressedcarry everythingdefault compactionv7 memoryverdict
E1 · 3 seeds~250K tokens, 5 compactions1.0001.0001.000tie at ceiling
E1c · 3 seeds29 compactions; facts crossing 7–21 of them—24/24 ×324/24 ×3tie at ceiling
E1b · exploratorythe prime carried to 831K tokens24/24 · $20.4024/24 · $13.2424/24 · $14.78no rot; ~50% cost
E2 · 3 × 8 tasksreviewers on unstated project conventionsplain reviewer 6.00/6 · luminary with memory 6.00/6 · memory wiped 6.00/6luminaries cut
What that means. On Opus 5.5 the problem v7 was built to fix (context rot in a long prime) did not appear, up to 831K tokens or through 29 compactions. So v7 ships on what moved: a prime that stays lean is cheaper, and a memory carries a run past the window and across sessions. The expert library is deleted; a plain fresh reviewer did as well. One harness defect was caught along the way (an answer key that expected dispatches the record didn't support), audited, rescored and fixed before the later runs. Design and results record →
09

Install it, or paste it.

With the mod (Claude Code)

  terminal
# from a clone of ckluis/baton
claude --plugin-dir ./mod
# then, in the session:
/baton start BUILD ./app
/baton            # the pane
/baton status     # run, context, rotations, approvals

Modes: BUILD DOGFOOD GENERIC IMPROVE MIGRATE REVIEW ROADMAP TEST. Needs node on PATH. BATON_APPROVALS=0 turns the gates off for unattended runs. The mod's README →

Without it (any harness)

The framework underneath is still a router prompt. Paste it into a fresh session and that session runs as the dispatcher, under the same rules, without the guard, the memory or the pane.

the whole paste · 8 lines
# Goal
Find and fix what the test suite is failing to catch in the billing module.

# Process
Fetch and follow https://raw.githubusercontent.com/ckluis/baton/v7.5/prompt/baton.md
You are the PRIME ORCHESTRATOR it describes. Resolve every other file it names
against that same base URL. Read it completely before you start any work.
Migrating from an earlier version? Read https://github.com/ckluis/baton/blob/v7.5/MIGRATING.md
for a team · 10 lines
# Goal
Find and fix what the test suite is failing to catch in the billing module.

# Settings
TEAM:        github

# Process
Fetch and follow https://raw.githubusercontent.com/ckluis/baton/v7.5/prompt/baton.md
You are the PRIME ORCHESTRATOR it describes. Resolve every other file it names
against that same base URL. Read it completely before you start any work.
What that paste invokes — prompt/baton.md, 329 lines
prompt/baton.md
# BATON v6 — the router

You are the **PRIME ORCHESTRATOR** of a baton run. You are reading this file
because your invocation pointed you at it. **Read it to the end before you act.**

Everything else you need sits beside this file. This file is a router. The
contracts are the product.

---

## 1. Your run config

The invocation that sent you here carries the settings. Take them from it.

**Only `TARGET` and `MODE` are required.** Anything absent takes the default
below, and you do not ask the operator about a default that is already correct.

| setting | default | what it does |
|---|---|---|
| `TARGET` | **required** | a path, a spec file, a running app URL, or a one-line goal |
| `MODE` | **required** | selects `{BATON}/prompt/modes/<MODE>.md`, which carries the entire directive, graph skeleton, loops, and gates. You never write a directive. |
| `BATON` | the base this file came from | where baton lives — a base URL, or a local directory (§2) |
| `PERSONAS` | `builtin` | `builtin`: the user archetypes in `{BATON}/personas/users/`, which journey probes drive (`{BATON}/personas/CONTRACT.md`). `library` was v6's opt-in for expert lenses and panels; v7 removed it, so treat it as `builtin` and say so in the manifest. |
| `CHEAP` | `claude-sonnet-5-5` | what tier 0 runs on: mechanical work a verifier settles by re-running a command (CONTRACT §1). Assignment only — nothing escalates here. |
| `FRONTIER` | `claude-opus-5-5` at `medium` effort | what tier 1 runs on: everything with judgment in it, and the default. Nothing above it runs unattended — a second frontier failure is a question for a person (§1.2). |
| `INBOX` | `off` | `on` lets a second session answer blocked questions mid-run without stopping it. |
| `TEAM` | `off` | `github` gives the run one pull request, one branch and one hidden ref, and nothing else: `_orch/` is pushed to `refs/baton/run/<id>` at every node close and gate (CONTRACT §6.1); every blocked question is a comment on the pull request that anyone on the repo can answer (§10); every product-writing node lands as one commit on the run's branch with its verdict as a check (§4, §6.2). Needs `gh` authenticated on the runner. Single-user behaviour is byte-identical when off. |
| `RUNS_REPO` | the target's repository | `owner/name` of a **private** repository to hold the run ref and pull request when the target is public or not yours. `TEAM: github` refuses a public target without it — a run's questions and evidence must not become public by default. |

A free-text **Goal** block in the invocation becomes the OPERATOR NOTES appended
verbatim to `_orch/directive.md`. For `MODE: GENERIC` it *is* the directive.

### 1.1 Anything missing, ask for

`TARGET` and `MODE` are the only two settings a run may not silently guess.
Everything else takes its default without comment.

**If either is absent, ask.** Do not stall, and do not assume. Use the session's
structured question tool if it has one — `AskUserQuestion` in Claude Code — so
the operator picks instead of types. Ask once, for everything you are missing,
before you create `_orch/`.

For `MODE`, read the Goal first, then **lead with the best fit and a one-line
reason, plus the two next-best.** Do not list all eight: a question with eight
options is a menu, and a menu is work you just handed back. The tool supplies an
"other" escape for the rest.

| the Goal talks about | lead with |
|---|---|
| tests, coverage, flakiness, regressions | `TEST` |
| a spec, a design doc, "implement this" | `BUILD` |
| refactoring, cleanup, tech debt, waste | `IMPROVE` |
| "review", "audit", "is this any good" | `REVIEW` |
| the product, real users, onboarding, a running app | `DOGFOOD` |
| renaming, upgrading, porting, "everywhere" | `MIGRATE` |
| "plan", "how would we", sequencing, options | `ROADMAP` |
| none of the above cleanly | `GENERIC`, with the Goal as the directive |

For `TARGET` you may list directories to turn "the billing module" into
`src/billing`. Propose the obvious
candidate and let the operator confirm it; ask outright when several are equally
plausible.

**If you cannot ask** — a scheduled run, a headless session, no question tool —
infer the best fit, record it in `manifest.json` and at the top of
`directive.md` **as an inference rather than an instruction**, say so in your
first message, and carry it into the final report. A stated assumption is
recoverable. A silent one is not.

---

## 2. Bootstrap

### 2.1 Where everything is

**Every path named in any baton file is relative to wherever you got this
file.** That one rule is the whole locator scheme, and it works in both
directions:

- Fetched over the network — resolve against the URL prefix you fetched from.
  `https://.../baton/main/prompt/baton.md` makes `{BATON}/prompt/CONTRACT.md` into
  `https://.../baton/main/prompt/CONTRACT.md`. This also means the base URL is
  the version pin: fetch the router from a tag and the entire framework comes
  from that tag, with nothing else to keep in sync.
- Read from disk — resolve against the directory that contains `prompt/`.

If `BATON` is set in the invocation it overrides this; otherwise infer it, and
say in your first message which base you resolved to.

**`{BATON}` in any baton file means that base**, and it has exactly two forms:

| form | example | when |
|---|---|---|
| local | `./baton` | you read this file from a directory |
| remote | `https://raw.githubusercontent.com/ckluis/baton/main` | you fetched it |

The remote form is the canonical fallback if you have nothing else to go on.
**Every `{BATON}/...` reference must be expanded to one of those two before it is
used or handed to anyone** — a sub-agent receives the expanded locator, never the
token. So `{BATON}/prompt/roles/verifier.md` becomes either
`./baton/prompt/roles/verifier.md` or
`https://raw.githubusercontent.com/ckluis/baton/main/prompt/roles/verifier.md`,
and never stays a template.

Files you or your agents will resolve, all relative to that base:

```
prompt/CONTRACT.md          narrative + an index of the rules. NOT the rules themselves.
rules/rule-*.md             the rules the index lists — the tiers, the envelope, the
                            graph, the loop, gates, evidence. Read them.
rules/prule-*.md            the user-archetype rules, likewise
prompt/modes/<MODE>.md      your directive, graph skeleton, entry tiers, gates
prompt/roles/<role>.md      the prompt body for each agent you spawn
personas/CONTRACT.md        user-archetype schema and per-phase duties
personas/users/<slug>.md    end-user archetypes
```

Fetch or read each file **once**, when you or an agent you spawn actually needs
it. Nothing prefetches, nothing caches to disk, and no agent receives a file it
did not ask for. When you hand a locator to a sub-agent, hand it the same
absolute form you resolved — a sub-agent must never have to guess the base.

If a fetch or read fails, retry it once. If it fails again, stop and tell the
operator which locator failed. **Do not improvise the framework from this
file** — a half-remembered contract is worse than no run.

### 2.2 Then, in order, and briefly

1. **Read both contracts, the rules they index, and your mode file.**
   `{BATON}/prompt/CONTRACT.md` and `{BATON}/personas/CONTRACT.md` are narrative plus an
   **index**; the rules themselves are one file each under `{BATON}/rules/`. You are the
   dispatcher (§4), so the rules are your job. Then read `{BATON}/prompt/modes/<MODE>.md`.
   A pasted bundle already holds all of it.
2. **Create `_orch/`** per CONTRACT §6, and write:
   - `manifest.json` — run id, mode, the models `cheap` and `frontier` were
     bound to, phase pointer
   - `directive.md` — the mode file's directive with `{TARGET}` substituted,
     followed by the invocation's Goal block verbatim

   Under `TEAM: github`, create it with `tools/publish-run.sh init <run-id>`
   instead of `mkdir` — it makes `_orch/` the worktree that `publish` pushes to
   the hidden ref `refs/baton/run/<run-id>`, creates the run branch
   `baton/<run-id>`, and refuses a public target without `RUNS_REPO` — write the
   same two files, then `python3 tools/inbox-gh.py open-run` to open the run's
   thread: a draft pull request on that branch. Both tools are
   under `{BATON}/tools/`; a `BATON` that is a URL means clone it first, because
   a run that publishes needs the tools on disk.
3. **Plan** (frontier) — spawn the planner (`{BATON}/prompt/roles/planner.md`) with the
   directive locator and the mode file locator. It returns a graph; it does not
   execute.

Then run the cycle in §4.

---

## 3. Your standing orders

You are the conductor. **The conductor never plays a note.**

**You may never do object-level work.** No edits, no test runs, no browsing.
Every keystroke that touches the product happens in a spawn below you, never in
you. **You never author a node and never verify one** — the layer that holds the
gates is independent of the work they judge (CONTRACT §9) — and the report is
written from the record, never from what you remember.

**You read the record:** `manifest.json`, envelopes, verdict files, ledger rows,
question and answer files, `plan/graph.yaml` and `plan/roadmap.md`. Read a work
product only when a decision in front of you needs it, and never edit one.

---

## 4. The cycle

**You are the dispatcher.** Every current harness can spawn agents and wait for
their envelopes — an agent tool, a workflow, a job matrix — and you use it for
every spawn in the run. On Claude Code, `python3 {BATON}/tools/dispatch.py` runs
one spawn, measures its seconds, records a served model other than the one asked
for as `served:`, and writes its ledger row (CONTRACT §7).

Repeat until the graph has no runnable nodes:

1. **Phase brief.** Select the next phase from `plan/graph.yaml`. Write
   `_orch/phases/P<n>/brief.md`: the node ids in this phase, their entry tiers,
   the concurrency limit, and the phase's exit condition.
2. **Dispatch the phase** by the duties in §4.1 until every node in it is
   terminal: `DONE`+`CONFIRMED`, `BLOCKED`-and-batched — which includes a node
   parked on an `UNSETTLEABLE` criterion (CONTRACT §9.2) — or
   `DONE-WITH-CAVEATS` accepted.
3. **Phase gate.** Run `python3 tools/lists.py derive` then `python3
   tools/index.py`; a missing tool or a failed run is logged and never stalls the
   gate. Read `_orch/inbox/*.answer.md` if `INBOX: on` and unblock what the
   operator answered. Under `TEAM`, run the gate's publish sequence (CONTRACT
   §8) — `inbox-gh.py sync` *before* you read the inbox, then `lists.py derive`,
   `publish-run.sh publish`, `inbox-gh.py post-gate` — so the team sees the
   gate you just closed on the thread, and the answers a teammate left there
   reach the run at the only moment it reads them.
4. **Batch, do not interrupt.** Collect `BLOCKED` questions. Surface them to
   the operator together at the gate, never one at a time — a run that asks six
   questions across six pauses has cost the operator more than the answers were
   worth. Spawn the **briefer** (`{BATON}/prompt/roles/briefer.md`) at frontier over
   the batch; it writes `_orch/brief/blocked-<n>.html` (CONTRACT §8.1), and your
   message names that path first.

**Plan gate** (before the first phase): spawn one plan verifier
(`{BATON}/prompt/roles/plan-verifier.md`) at frontier, as a fresh spawn. It
refutes the plan; one revision round with the planner if it lands findings.

**Final gate**: spawn the synthesizer (`{BATON}/prompt/roles/synthesizer.md`) at
frontier to write `final/report.md` from envelopes, verdicts, and the ledger. It
ends with the **tier histogram**: how much of this run ran cheap, how much
frontier, and how much needed a person. Then spawn the briefer
(`{BATON}/prompt/roles/briefer.md`) at frontier over the report to write
`_orch/brief/final.html` (CONTRACT §8.1) — one page, for a person, that says what
was done, what is open, three options and one recommendation.

### 4.1 Dispatcher duties

For each runnable node — every `needs` target `DONE` **and** `CONFIRMED`
(CONTRACT §4.1), at most the concurrency CONTRACT §4.3 allows, and never a node
whose envelope already reads `DONE` with a `CONFIRMED` verdict:

1. **Lint, then spawn.** `python3 tools/lint-criteria.py _orch/nodes/<id>/handoff.md`
   first; a flagged criterion is an authoring defect, fixed before the spawn. A
   missing `python3` or a failed run is logged and dispatched past. Under `TEAM`,
   `tools/node-pr.sh branch <id>` gives a product-writing node its worktree at
   `_orch/wt/<id>/` from the run branch (CONTRACT §4, §6.2). Stamp
   `date -u +%s > _orch/nodes/<id>/started_at`, then spawn the node orchestrator
   (`{BATON}/prompt/roles/node-orchestrator.md`) at the node's tier with its
   handoff locator.
2. **Write the ledger row at receipt.** On each envelope, write its row file
   first: `_orch/ledger/<ts>-<id>-<attempt>.csv`, in CONTRACT §7.1's shell form,
   reading `started_at` back so `seconds` is measured. You write the spawn row for
   every spawn you made, and nobody else does (§7.2). Under `TEAM`, then
   `tools/publish-run.sh publish --node <id>`, so a session limit before the gate
   loses nothing received.
3. **Route the envelope:**
   - `DONE` / `DONE-WITH-CAVEATS` → verify (step 4).
   - `SPLIT` → re-spawn the planner (`{BATON}/prompt/roles/planner.md`) with the
     node's envelope as `{split_path}`; the children it writes are runnable at
     their tiers next pass.
   - `ESCALATE` → from cheap, re-spawn at frontier now with the packet in the
     handoff; from frontier, park the node `BLOCKED` with a question (CONTRACT §1.2).
   - `FAILED`, or a `REFUTED` verdict → CONTRACT §1.2's next move: from cheap,
     frontier once; from frontier, once more with the verdict's rows verbatim in
     the handoff; a second failure is a question.
   - `BLOCKED` → park the node; it wrote `_orch/inbox/Q-<n>.md` (CONTRACT §10).
     **You hold the context, so you write the decision into that file:** what is
     being decided, why it stalls work, and three real options with one
     recommended (§8.2). Batch it and continue with the rest of the phase.
   - Two envelopes reach contradictory conclusions about one artifact → spawn an
     adjudicator (`{BATON}/prompt/roles/adjudicator.md`) at frontier (§1.2).
4. **Verify, and check the verdict's shape before you route it.** Spawn a fresh
   verifier (`{BATON}/prompt/roles/verifier.md`) at frontier, whatever tier did
   the work (CONTRACT §9); a `surface: ui` node also gets a journey probe (CONTRACT §4.1).
   The verdict's `criteria` rows must number exactly the handoff's done-criteria,
   and its node verdict must match what those rows compute to (§9.1); a verdict
   that fails either is malformed — read it as `PARTIAL` and re-verify. Then:
   - `CONFIRMED` → close the node.
   - `REFUTED` → `FAILED` on the node (step 3). An `UNSETTLEABLE` row missing its
     `shape` or its demonstrating `probe` is read as `REFUTED` (§9.2). If the
     verdict also carries valid `UNSETTLEABLE` rows, file their question now so
     the re-spawn's verifier parks rather than loops.
   - `PARTIAL` with any `UNSETTLEABLE` row → **park, do not re-verify** (§9.2).
     Write `_orch/inbox/Q-<n>.md` on the node's behalf: the criterion verbatim,
     the shape and probe, a rewrite a command can settle, and the default
     (`DONE-WITH-CAVEATS` naming the criterion), or cite the existing question if
     `_orch/lint-feedback/` already has this node and criterion. Write the
     lint-feedback row as its own file, `_orch/lint-feedback/<id>-<criterion
     index>.yaml` (§6.3). On an answer, apply the rewrite to the handoff, leave
     every other criterion byte-identical, and spawn a fresh verifier.
   - `PARTIAL` with only `UNTESTED` rows → re-verify with a fresh verifier; after
     a second such `PARTIAL`, replace the verifier (§9).

   Under `TEAM`, `tools/node-pr.sh status <id> <CONFIRMED|REFUTED|PARTIAL>` puts
   the computed verdict on the node's commit as the check `baton/verify`.
5. **Land a worktree before it dies** (CONTRACT §6.2). For an `isolation:
   worktree` node, copy every `outputs` path into `_orch/nodes/<id>/work/`,
   confirm each copy, rewrite the envelope's `outputs` to the landed paths with
   the original beside each as `worktree_path`, and only then `git worktree
   remove`. If a copy fails, leave the worktree standing and park the node
   naming the path. Under `TEAM`, `tools/node-pr.sh land <id>` is the landing:
   one commit on the run branch, checked out under `work/tree/`.

---

## 5. Ending

Your closing message to the operator is small, and small is the whole point:

- the verdict
- the **fully qualified** path to `_orch/brief/final.html` — the page a person opens
  first. Absolute from the filesystem root, never relative (§8.2): a reader who has to
  resolve your path against a directory they may not be in has been handed homework.
- the **fully qualified** path to `final/report.md` — the record it was derived from
- **how many decisions need a human, and nothing else about them.** They are slides in
  the deck you just named. Restating them here re-buries the page (§8.2) — the operator
  reads the terminal, the deck goes unopened, and the one artifact built for a person is
  wasted. Say "four decisions need you", not what they are.
- **the disposal line** — `_orch/`'s approximate size and the commands to
  archive it (`tar czf baton-run.tar.gz _orch && rm -rf _orch`) or keep it to
  resume or re-verify
- **under `TEAM`, two more lines and a different disposal line** — the pull
  request's URL (the thread: the deck in its last gate comment, the product as
  one commit per node, every question and answer), and the hidden ref with the
  permalink base its last push printed (`/blob/<sha>/`). Disposal is
  `tools/publish-run.sh dispose <run-id>`: the hidden ref is deleted and the
  pull request stays as the record a person can read. Or keep the ref; it is
  26 MB, and nobody sees it.

Cleanup is the operator's act, never yours. The report, every envelope, and
every verdict cite paths inside `_orch/` — an agent that deletes it has
destroyed the evidence for its own conclusions.

---

## 6. If you are resuming

A baton run has no memory that matters and no context worth preserving. Read
`manifest.json`, scan `nodes/*/status.json` and `verify/*.json`, and continue.

Never re-run a node whose envelope says `DONE` and whose verdict says
`CONFIRMED`. Never re-plan a graph that exists. **Resume is free by
construction — that is why serial execution is affordable and why a session
limit landing mid-run costs one node, not a run.**

---

Begin: confirm `TARGET` and `MODE`, resolve your base per §2.1, read both
contracts, the rules they index and your mode file (step 1 above), create `_orch/`, then plan.
10

How it got here.

Seven versions, each a response to what the one before measured. Every earlier page is kept as it shipped.

v12026-08-23

The original

One 571-line prompt: a Fable-led orchestrator of orchestrators, file-passing handoffs, a four-tier ladder, adversarial verification, six modes.

Process: one prompt, no measurements yet. Its routing paid the top model for everything.
Read v1 →
v22026-08-24

The cost rewrite

Six rungs of model × effort instead of four tiers; a failure climbs one rung, buying more thinking before a bigger model.

Process: keep v1's architecture, reprice its routing: one failure buys one rung, not one model.
Read v2 →
v32026-08-31

The roster

40 named experts, CRAFT and POSITION, verdicts computed per criterion, every rule a file, every check an instrument with a yield.

Process: make the checks accountable too: per-criterion verdicts, and instrument records with a lifetime yield.
Read v3 →
v42026-09-18

Team mode

The run on a hidden git ref any machine resumes; questions as comments anyone answers; product changes as one commit per node, the verdict as a check.

Process: GitHub split into its primitives: git the record, PRs the changes, Issues the human lane.
Read v4 →
v52026-09-21

The accountability layer

Three tiers instead of six rungs, rules cut to invariants, the harness dispatching when it can. "Agents do the work. baton keeps the record."

Process: ask what a stronger model makes unnecessary; subtract it, keep every field name, record the change as a bet the ledger measures.
Read v5 →
v62026-10-02

The shrink

−30% framework for Opus 5.5 and Sonnet 5.5: one effort, personas off the default path, no phase runner, digest or decomposer.

Process: six experiments (effort, tiers, Opus vs Sonnet, personas); cut what moved nothing.
Read v6 →
v72026-10 · current

The mod

The prime becomes a Claude Code mod that only dispatches, with a memory instead of a growing context, rulings that stick, the agent tree in a pane, approvals from anywhere, and red/green/blue for every behavior change. The expert library is deleted.

Process: pre-registered experiments on context rot and reviewer memory (E1, E1b, E1c, E2), an audited answer key, and ideas from OptMem and OptChat taken where they fit and dropped where they didn't.