Skip to content
Back to skills

Orchestrate

BSecurity

Orchestrate one natural-language goal into parallel tmux Claude Code sessions. It decomposes the goal (with your approval), plans each task with wiki-plan, implements and reviews, runs an integration test, and merges after your confirmation. Use to build a goal across multiple sessions. For a single task, use loop-implement instead.

  • 5 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added September 2, 2026
ai-agentsrustgoshellbashnodeexpressrailsdockergitapi

Works with

  • claude code
  • terminal
  • cli
  • api

Security analysis

B85/100
  • highPerforms destructive filesystem operations

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

Scanned October 6, 2026

npx -y skills add choiyounggi/dev-loop --skill orchestrate --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Orchestrate?

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

Security grade badge for Orchestrate
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/choiyounggi-orchestrate/badge)](https://www.skillsdirectory.com/skills/choiyounggi-orchestrate)

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

Download with Pro
SKILL.md
---
name: orchestrate
effort: high
argument-hint: "[goal or parent issue]"
description: Orchestrate one natural-language goal into parallel tmux Claude Code sessions. It decomposes the goal (with your approval), plans each task with wiki-plan, implements and reviews, runs an integration test, and merges after your confirmation. Use to build a goal across multiple sessions. For a single task, use loop-implement instead.
---

# orchestrate — multi-session orchestrator

You are the orchestrator. **You do not implement — sessions do.** You clarify,
decompose, distribute, review, integrate, and merge. Autonomy lives inside the
implementation loop; two human gates bracket it (task-split, pre-merge).

Scripts referenced below live in `${CLAUDE_PLUGIN_ROOT}/skills/orchestrate/scripts/`.
Communication: session→orchestrator via each worker's OWN worktree-local
`.orchestration/status/<task>.json` (never the coordinator's checkout — a
worker writing there can hit a guardrails permission prompt no human is
watching, issue #167), collected into the canonical `.orchestration/status/`
by `scripts/collect-status.sh` (Phase 3 step 1, and automatically each poll
via `watch-status.sh` — see **Session knobs**); orchestrator→session via
`launch-session.sh` (the first prompt) then `send-prompt.sh` (every later
one), carrying templates/session-prompt.md §1–§4 on tmux, or the Task `--spec`
(same file, §O1–§O4) on Orca.

## Asking the user — every question is a chooser (REQUIRED)
Every question this skill puts to the **user** is delivered with the
**AskUserQuestion** tool, so they answer by selecting an option instead of typing
a reply. Prose in the turn is the *briefing* (task list, graph, slot count, cost
note, diff); the *decision* is always the tool call. This covers Phase 0 frontier
rounds, Gate 1 (task split **and** substrate), Gate 2, a Phase 3 deadlock, and any
worker escalation that needs a human verdict.

**Required shape**
- One call per round, up to 4 questions in it. A frontier wider than 4 → back-to-back
  calls of ≤4 questions, numbering continuous across them; never one question per turn.
- Each question carries 2–4 options; the **recommended answer is option 1**, its
  label suffixed `(Recommended)`. `header` ≤12 chars.
- Options are the concrete outcomes ("approve as proposed", "drop task 3", "Orca"),
  not a yes/no restatement of the prose.
- `multiSelect: true` when the items are independent (e.g. which optional tasks to include).
- Free-text is already available to the user as "Other" — never author an "Other" option.

**Predicate-keyed**

| Situation | Ask |
|---|---|
| Phase 0, frontier of open decisions | one question per frontier item, ≤4 per call, each with its recommended answer |
| Gate 1, `orca-detect.sh` non-zero (no Orca) | Q1 task split: approve as proposed / revise / abort. Say nothing about substrates |
| Gate 1, `orca-detect.sh` exit 0 (Orca present) | the SAME call carries Q1 task split **and** Q2 substrate: Orca / tmux |
| Gate 2, after showing the integration diff | Q1: merge / send back for rework / abort |
| Phase 3 deadlock, or an escalation needing a human | options = the concrete resolutions available, not "how should I proceed?" |
| You are a **worker** session (templates/session-prompt.md) | never AskUserQuestion — escalate via `ask-coordinator.sh`; only the coordinator asks the user |

**Pre-send check** — before ending any turn that asks the user something: does this
turn contain an AskUserQuestion call? Is the recommended option first and labelled?
If the turn instead ends with a question in prose, delete that prose question and
re-send it as the tool call. Enforcement is machine-side too: the
`orchestrate-ask-gate.sh` PreToolUse hook denies `launch-session.sh` /
`orca-worker-start.sh` / `orca-spawn.sh` until this session's transcript shows the
AskUserQuestion (and, when Orca is detected, one naming the substrate choice).

## Tool profile
Resolve the pluggable tool profile once up front:
`sh ${CLAUDE_PLUGIN_ROOT}/scripts/resolve-tools.sh --summary`. It maps capability
roles — `intake` (issue-tracker work-list source), `knowledge` (domain/policy),
`tacit` (incidents/danger zones), `verify` (test/build/lint/typecheck/QA
command), `explore` (code search; a fresh graphify graph when Preflight says
so), `design` (visual/UI spec, e.g. Figma) — to
whatever tools this installation has, or to generic defaults when unset (optional,
layered per-user then per-repo; see `references/tool-profile.md`). Use
`knowledge`/`tacit` yourself during Clarify/Decompose, and write each task's
resolved roles into its `<tools_guidance>` brief so worker sessions inherit them
even if they can't re-read the config. A role is a tool injected into one step,
never a loop: do not map a role to an implement/verify-loop tool or another
orchestrator (that nests loops); there is no `implement` role.

## Coordinator token budget
Accumulated context, not turn count, is what drives a run's cost — every call
resends the full history at the cached rate, so cost per turn grows as the run
goes on. Four things the coordinator itself controls:

(a) never `Read` a screenshot or other image directly into this session —
delegate the visual check to a subagent that returns a text verdict instead;
(b) bound every pane capture and status read (`tail -N` on tmux panes, one-line
`jq` filters on status JSON) instead of pulling full output into context;
(c) **say where a `/compact` is safe** — the coordinator cannot compact its own
session, so a human has to be told. The safe points differ in what they hand
back, so they are keyed by predicate:

| At this point | What to say | What gets re-read from disk afterwards |
|---|---|---|
| Gate 1, once the split is approved | a safe `/compact` point, if the run has already gone long | `.orchestration/graph.json` and the briefs |
| **After each merge-on-approval** — once `git merge-base --is-ancestor <branch> <integ>` has confirmed the merge landed (Phase 3 step 5) | a safe `/compact` point | `.orchestration/graph.json`, `.orchestration/status/*.json`, and the brief + plan of whatever `ready-set.sh` dispatches next |
| Gate 2, with the integration diff shown | a safe `/compact` point | the integration diff, re-read with `git diff` |

Gate 2's full-diff read is the LAST one of a run, not a recurring cost: Phase
5's integration review now runs on the `integration-reviewer` agent's own
fresh context, so this coordinator session itself never reads the full
integration diff until this final gate. Per-task diffs are read by the
`task-reviewer` agent at Phase 4 the same way, so this session reads no
worktree diff at all before Gate 2. Per-task plans are written and re-planned
on the `task-planner` agent at Phase 3 step 2a the same way, so this session
holds no plan body either (Phase A on the `task-analyst` agent, Phase B and
C on `task-planner`).

The middle row is the one that matters. Gate 1 and Gate 2 are the *first* and
*last* things a run does, so on their own they leave the long autonomous middle
— where essentially all of the context accumulates — with no sanctioned
checkpoint; one measured run peaked at **613,698** context, more than 2x
`token-report.sh`'s own warn threshold, and was told about it only after
teardown. That boundary is safe for exactly the reason **Re-entry** gives:
nothing unrecoverable is held there, because the graph, every task's phase and
`.attempt`, the briefs, the plans, the reviews and the merged commits are all
already on disk. It also recurs once per approved task rather than twice per
run.

(d) **measure it during the run, not only after it.** At that same
merge-on-approval boundary, before offering the `/compact`, read this
coordinator session's own peak context. Target the newest transcript in the
project's mapped directory, not the directory: the teardown form
(`--cwd <root> .`) reports every past session for this project, while the newest
transcript is this live session, since it is the only one being written during
the run.

```sh
d="$HOME/.claude/projects/$(printf '%s' "$PWD" | tr '/.' '-')"
newest=$(ls -t "$d"/*.jsonl | head -1)
sh {SKILL}/scripts/token-report.sh "$newest"
```

That prints a two-row table, plus one `warn: peak context …` line on stderr once
peak passes `LO_CTX_WARN` (default 300000). Pass the warn line to the user along
with the compact offer. The End-of-run contract runs the same script over the
whole run; this is that measurement taken while it can still change something.

**Known amplifier — the re-plan loop.** When a worker reports the plan is
contradictory or under-decided (Phase 3 step 2a), every round used to append
permanently to coordinator context: the worker's gap report, the coordinator's
verification of it against the code, the plan patch, and the re-send. One
measured run did eleven of them. The verification and the patch now run on the
same task-planner agent that wrote the plan — forward the gap report verbatim
with SendMessage; its transcript still holds the plan — so what this session
keeps per round is the one-line forward and the short re-send: the worker
re-reads `{ORCH_DIR}/plans/<task>.md` from disk anyway, so send `plan patched at
§N — re-read it from disk and re-run the adoption check` instead of restating
the fix in the prompt.

**Re-plan ladder — bound the loop.** Track each task's round count in your head
from the review/patch history already on disk (no new state file). Route each
gap report by what it faults. A gap in the analysis (`analysis.md`'s
requirements, ground truth, or constraints) goes to the same `task-analyst`
first — SendMessage it the gap report, wait for its fresh ANALYSIS REPORT, and
re-run gate-A yourself — and then to the same task-planner agent with
"analysis.md changed — re-check the plan against it". Any other gap goes to
the same task-planner agent only. Rounds 1–2 on a task are SCOPED PATCHES: the
planner patches only the reported gap, then the cheap re-send above. Round 3
is ONE full re-plan and goes through the two-stage handshake again, in this
order:
1. SendMessage the same `task-analyst` the gap report and "round 3 — re-run
   Phase A wholesale"; wait for its ANALYSIS REPORT and re-run gate-A yourself.
2. SendMessage the same task-planner agent "round 3 — analysis rewritten": it
   deletes its stale `review-verdict.md`, rewrites `plans/<task>.md` wholesale
   from the new `analysis.md`, and STOPs with a fresh STOP REPORT instead of a
   FINAL REPORT.
3. Run `plan-reviewer` again on the redesigned `design.md` before the planner
   emits gate-B, exactly as in the first handshake.

A full re-plan replaces patching a single section because a third scoped
patch is the signal the section-level fixes aren't converging. A task that still
reports a plan gap after its round-3 full re-plan is a deadlock-grade
escalation to the user: report it and get a human decision, same as a Phase 3
step 1 exit-3 DEADLOCK. There is never a round 4.

Basis:
`wiki/infrastructure/agent-orchestration/session-context-token-budget.md`
(directives 2–4), plus this skill's own **Re-entry** guarantee for the
merge-on-approval row.

## Preflight
Run `${CLAUDE_PLUGIN_ROOT}/hooks/preflight.sh` to resolve git/tmux/jq paths and
surface any missing CLI. **git, tmux, and jq are all required** — if any is
missing, stop and ask the user to install it (the SessionStart preflight hook is
advisory only; this skill must hard-require them). For a missing tmux: with the
user's consent, install it (macOS: `brew install tmux`; otherwise advise) before
launching sessions. Never auto-install without consent.

**Code-graph freshness (optional `explore` lead).** Run
`sh ${CLAUDE_PLUGIN_ROOT}/scripts/graph-freshness.sh <root>` once, bare (read
its exit code on the next line, never in a pipe). It never runs graphify
itself. Branch on the code: **0** (`fresh`) — set `explore` to graphify for
this run and write the row from Phase 3 step 2 into every brief; **2**
(`stale <N>`) — ask ONE chooser (§ Asking the user): "graphify graph is stale
(<N> files changed since it was built) — run `graphify update <root>` now
(AST-only, seconds, no LLM), or continue without the graph?" with *update* as
the recommended answer; run the update only on an explicit yes and re-run the
freshness check afterwards; **3**/**4** (`absent` / `cannot-evaluate <reason>`)
— print one line saying the graph is not in use and continue exactly as
before. The coordinator never runs a full `/graphify` build and never loads
the graphify skill document: the CLI is the whole interface
(`graphify --help`). Basis:
`wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md`.

**Coordinator permissions (tmux substrate).** `launch-session.sh` starts each
worker as `claude --permission-mode bypassPermissions` — the exact surface an
auto-mode permission classifier hard-flags as privilege escalation. It cannot
see the context that makes this safe (each worker worktree carries a guardrails
deny-net that still blocks dangerous commands in bypass mode and escalates
`ask` rules to you), so under an auto-mode coordinator the launch may be
DENIED. Handle this at onboarding, not at the first failure:

1. **Probe once, read-only**, during Preflight:
   `sh {SKILL}/scripts/install-permission-rules.sh --check` — exit 0 rules
   present (nothing to do), **4** absent, 3 the settings file is malformed
   (surface that to the user; fix before anything else).
2. **On 4, ask the user — never install silently.** One question: "orchestrate's
   tmux workers launch with permission prompts off (guardrails-sandboxed);
   pre-approve the four worker-management scripts in ~/.claude/settings.json?"
   Show what it adds (the snippet below) — this also covers the exit-5
   escalation recovery path (`resolve-escalation.sh`), executable by an
   auto-mode coordinator without any bare `rm`. A plugin that widens permissions
   without a fresh explicit yes is the supply-chain pattern guardrails exists
   to stop — and the fresh consent is also what lets the classifier pass the
   write at all.
3. **On an explicit yes**, run `sh {SKILL}/scripts/install-permission-rules.sh`
   (idempotent; backs up, refuses a malformed target, atomic write). On no —
   or if the installer itself is classifier-blocked — fall back to showing the
   snippet for the user to paste themselves, then continue; the launch will
   simply prompt (default mode) or deny (auto mode) until it lands.
4. If a launch is DENIED later anyway: do **not** work around the block — an
   agent widening its own permissions on its own initiative is itself
   classifier-blocked, by design. Re-offer step 2 and stop until the user
   decides.

What the installer adds (equivalently pasteable into
`.claude/settings.local.json` per-project, or `~/.claude/settings.json`
globally; `safe-cleanup.sh` is deliberately absent so destructive verbs keep
their normal review):

```json
{
  "permissions": { "allow": [
    "Bash(sh /Users/<you>/.claude/plugins/cache/*/dev-loop/*/skills/orchestrate/scripts/launch-session.sh *)",
    "Bash(sh /Users/<you>/.claude/plugins/cache/*/dev-loop/*/skills/orchestrate/scripts/send-prompt.sh *)",
    "Bash(sh /Users/<you>/.claude/plugins/cache/*/dev-loop/*/skills/orchestrate/scripts/watch-status.sh *)",
    "Bash(sh /Users/<you>/.claude/plugins/cache/*/dev-loop/*/skills/orchestrate/scripts/resolve-escalation.sh *)"
  ]},
  "autoMode": { "allow": [
    "$defaults",
    "Running the dev-loop orchestrate plugin's worker-management scripts (launch-session.sh, send-prompt.sh, watch-status.sh, resolve-escalation.sh) is allowed, including launch-session.sh starting a tmux worker with `claude --permission-mode bypassPermissions` and resolve-escalation.sh clearing a guardrails escalation record and delivering the coordinator's verdict to the waiting worker: the user sanctioned this orchestration workflow, and each worker worktree is sandboxed by groundwork guardrails, which still blocks dangerous commands in bypass mode and escalates `ask` rules to the coordinator. This does NOT extend to safe-cleanup.sh or other destructive commands, which keep their normal review."
  ]}
}
```

The path rules cover full-path invocations; the `autoMode.allow` rule teaches
the classifier the context so variable-form invocations (`sh $SKILL/scripts/…`)
pass too. The Orca substrate does not spawn through `launch-session.sh`, but
its worker terminals embed the same flag — if a classifier flags those, the
same `autoMode.allow` rule is the fix.

## Phase 0 — Intake + Clarify
Two ways the work-list arrives:
- **`intake` role configured** (e.g. an issue tracker) → if the user names a parent
  issue (key/URL), use the intake tool to read it and its children: the parent gives
  the overall goal/architecture, each child becomes a candidate task. Extract the
  issue key from a URL (last path segment). This is the "Jira-style" entry — only
  taken when `intake` is set *and* the user supplies an issue; otherwise:
- **`intake` unset, or a free-text goal** → the natural-language path (default):
  decompose the goal yourself in Phase 2.

**Already-done children (partial resume):** treat a child as *completed* if the user
says so (by key) or — when intake exposes status — its tracker status is Done. A
completed child is **not** decomposed into a task (no session/worktree), but it is
**not silently dropped** either: record what it produced as a **base output** — the
exact exposed signature (function/type/component/endpoint). Any task that depends on
it then still gets that contract injected (Phase 2/3). If the user names a completed
key without the signature, read it from the integration branch / its merged code
rather than re-creating it. This is how "one sub-issue already done, do the rest"
works safely.

Either way, map open decisions as a **design tree** — each decision branches into
the decisions that hang off it — then run **frontier rounds**:
- A decision belongs in the **frontier** when its prerequisites are already
  settled; a decision whose answer depends on another still-open decision waits
  for a later round.
- A fact the environment can answer (repo state, config, docs) is the
  orchestrator's job to look up — never asked of the user; only genuine
  decisions go to the user.
- Each round, ask the WHOLE frontier at once — as **AskUserQuestion** questions
  (§ Asking the user), one per frontier item, each a **question + a recommended
  answer** (never a bare open question, never prose the user must type back).
Repeat rounds until the frontier is empty — goal / scope / constraints / done
criteria clear enough to decompose. Don't start until they are.

## Phase 1 — Environment branch
- **git repo present** → create a feature (integration) branch + one worktree per
  task. Determine base via `gh repo view --json defaultBranchRef` (fallback: the
  current branch) — measure, don't assume.
- **no git repo** → run `scripts/safe-cleanup.sh init-check <workdir>`. Only if it
  returns ok, `git init` (add a `.gitignore` incl. `.orchestration/` first), then
  proceed as above. If it REFUSEs (nested repo / secrets), stop and report.
- Create `.orchestration/notes/` and `touch` an empty `notes/decisions.md` —
  the blackboard (append-only) lives there; see **Blackboard** below. Without
  this, the first worker to reach the implement phase's read checkpoint reads
  a file that does not exist yet.

## Phase 2 — Decompose
Get the task set: use the `intake` children as candidate tasks if Phase 0 read an
issue, otherwise split the goal into independent tasks yourself. **Drop the
completed children (Phase 0) from the task set** — but seed the dependency graph
with their base outputs (already-satisfied), so their dependents still resolve and
get the signature injected, while no session is spawned for them. Then, the same way
for both, for each *remaining* task extract **affected files**, **outputs** (what it newly
creates — component/schema/endpoint/type), and **consumes** (another task's output
it depends on). Build a conflict/dependency matrix from those and topologically sort
into Waves (`conflict-matrix.md`): a dependency edge `A → B` means B consumes A's
output, so A's Wave precedes B's. Detect duplicate outputs and assign a single
producer; others consume (add a dependency edge). **Mark shared surfaces** while
you do this: any output that lands in one task's `outputs` and at least one other
task's `consumes` is a shared surface — the conflict matrix already computes this
exact edge, so no extra pass is needed. Phase 3 step 0 commits a contract stub for
each one before its producer is dispatched (see Contract-first dispatch below).
Write BOTH artifacts: `conflict-matrix.md` for humans and
`.orchestration/graph.json` for the scheduler. A markdown table is not machine
readable.

**Graph leads (only when Preflight set `explore` to graphify).** For each
candidate task, run `graphify explain "<Symbol>" --graph
<root>/graphify-out/graph.json | head -40` on the symbols the task names, and
`graphify path "<A>" "<B>" --graph <root>/graphify-out/graph.json` for a
suspected edge between two tasks. The printed connections seed the task's
affected files and its shared surfaces; the disjointness test in the conflict
matrix runs on the confirmed sets, never on the graph's guesses. A graph hit is
a lead, not evidence: every file it suggests is confirmed by a search before it
enters `files`, and the record pairs both in one line —
`graphify explain <Symbol> -> <N> connections; grep -rn <Symbol> src -> <n> hits`.
Ask symbol-anchored questions only; a free-text `graphify query` is for
orientation (`--budget 800`) and never derives a file set. Any assumption taken
from the graph and not confirmed is recorded on the blackboard as
`graph-derived: <assumption>`.

```json
{ "tasks": [
    { "id": "t1", "deps": [],     "files": ["src/auth/**"], "outputs": ["AuthToken"], "risk": "R1", "risk_basis": ["none"] },
    { "id": "t3", "deps": ["t1"], "files": ["src/api/**"],  "consumes": ["AuthToken"], "risk": "R3", "risk_basis": ["path:auth", "shared-surface"] }
] }
```

**Risk tier (issue #192).** Every task node carries risk (exactly one of R0, R1,
R2, R3) and risk_basis (array of signal tokens from the table below, or
`["none"]`). The scheduler ignores both fields — ready-set.sh reads only id and
deps and graph-add.sh validates only id, deps, split_of, outputs, so no script
changes — and every node added later with graph-add.sh carries them too.

| Signal | Source | Token | Floor |
|---|---|---|---|
| files match an auth, authorization, secrets, or payment path | Phase 2 affected files | path:auth path:secrets path:payment | R3 |
| DB schema or migration files | affected files | path:schema | R3 |
| destructive or irreversible action (delete, drop, force, revoke) | plan deliverables | destructive | R3 |
| public API contract or deploy pipeline files (.github/workflows, Dockerfile) | affected files | path:public-api path:pipeline | R2 |
| shared-surface producer (another task consumes its output) | conflict matrix | shared-surface | R2 |
| new external dependency | plan | new-dependency | R2 |
| concurrency or multi-object write | lens 4 signal | concurrency | R2 |
| two or more dependents in graph.json | graph reverse lookup | fan-out:<n> | R2 |
| wiki-plan Size verdict large | step 2a | size:large | R2 |
| one or more [no-wiki] decisions | step 2a | no-wiki:<n> | R1 |
| no signal and Size verdict small | — | none | R0 |

The tier is the max-of-signals: the highest Floor any single signal sets is
the tier; signals are never summed, because a sum lets several low signals
dilute one payment-path hit into R1.

Phase 2 sets the initial tier from the file and graph signals; step 2a
re-reads the two plan-derived signals (size:large, no-wiki:<n>) and may only
RAISE the tier — record each raise on the blackboard as
`- [<task>] Ruling: risk <old>-><new> — <signal> — <cost if wrong>` and
rewrite graph.json; a tier is LOWERED only by the user at Gate 1 answering
revise.

**Tier to pipeline profile.**

| | R0 trivial | R1 normal | R2 high | R3 critical |
|---|---|---|---|---|
| planning | wiki-plan lite | full A/B/C | full A/B/C, plan-reviewer required | R2 plus a second plan-reviewer call with a different Agent model override |
| analysis agent — wiki-plan Phase A, claude-fable-5-1 | `task-analyst-r1` (high) | `task-analyst-r1` (high) | `task-analyst` (xhigh) | `task-analyst` (xhigh) |
| design agent — wiki-plan Phase B+C, claude-opus-5-5 | `task-planner-r1` (medium) | `task-planner-r1` (medium) | `task-planner` (high) | `task-planner` (high) |
| worker model (DEV_LOOP_WORKER_MODEL) | claude-sonnet-5-5 | claude-sonnet-5-5 | claude-sonnet-5-5 | claude-sonnet-5-5 |
| worker effort (DEV_LOOP_WORKER_EFFORT) | medium | medium | high | high |
| QA agents — task review and coordinator auditor cross-call, claude-fable-5-1 | `task-reviewer-r1` (medium) | `task-reviewer-r1`, `test-quality-auditor-r1` (medium) | `task-reviewer`, `test-quality-auditor` (high) | `task-reviewer`, `test-quality-auditor` (high) |
| final review agent — Phase 5, claude-fable-5-1, picked by the run's highest task tier | `integration-reviewer-r1` (xhigh) | `integration-reviewer-r1` (xhigh) | `integration-reviewer` (max) | `integration-reviewer` (max) |
| brief effort_level | simple | medium | complex | complex |
| review lenses | 1, 3 and 6 only | 1-4 and 6 | 1-6 | 1-6 plus the adversarial-change-review techniques recorded under lens 5 |
| coordinator auditor cross-call | none (floor only) | when tests look weak | mandatory | mandatory |
| rework budget | 1 | 3 | 3 | 3 |
| human gates | Gate 1 and 2 | Gate 1 and 2 | Gate 1 and 2 | Gate 1 and 2 (a per-task pre-merge gate is deferred, issue #192 section 9 question 1) |

R0 reuses wiki-plan lite mode as its definition — no new concept; every
number above lives only in this table and the prose cites the table. The
"coordinator auditor cross-call" row states only Phase 4's cross-call
obligation on the coordinator side — the worker's own step 6.5 auditor call
is unchanged at every tier.

Each agent row names the file to call; the parenthesis is the effort its
frontmatter pins, next to the model it pins. Effort lives only in an agent's
frontmatter — the Agent tool overrides `model` per call, never `effort` — so
each role has a base file (R3 and R2) and a generated `-r1` copy one effort
step lower (R1 and R0); edit only the base file and re-run
`scripts/gen-agent-tier-variants.sh`. The launch scripts hand the worker its
effort as `CLAUDE_CODE_EFFORT_LEVEL`, not `--effort`, because a skill's
`effort` frontmatter (loop-implement pins `high`) overrides `--effort` but not
that variable. The variable also outranks every subagent's frontmatter, so
the worker's own step 6.5 self-audit — the base `test-quality-auditor`, the
one agent the worker prompt names — runs at the worker's level, which is the
QA row's level at every tier; the `-r1` auditor is the coordinator's
cross-call.

**A 429 is not a verdict.** When any of these agent calls fails with an HTTP
429 naming a model limit (e.g. a Fable limit), re-run the same agent with the
Agent tool's `model` override — `opus`, then `sonnet`, skipping the family the
429 names — and escalate to the user after both fail. The override changes the
model only; the agent file's effort still applies.

For the same reason as the worker's variable, start the coordinator with
`CLAUDE_CODE_EFFORT_LEVEL` unset: set there, it flattens every agent's pinned
effort to one level.

Waves are **an illustration in the Gate 1 report, not an execution unit.**
Execution is decided by `ready-set.sh`: a task runs as soon as its dependencies
are `approved` and a slot is free. Still topologically sort — the result shows
the user the expected flow — but nothing waits on a Wave boundary.

**Propose the slot count.** Pick the number from the task count, their size, and
their risk, and **say what the number protects**: this cap guards **coordinator attention** and **API usage/budget**, not machine resources. Neither is
queryable, which is why it is a judgement rather than a computation. A slot is
held from dispatch until the task reaches a terminal state — `plan_ready` and
`impl_done` (review pending) count as held, because a pile of unreviewed tasks
next to a stream of new ones makes the cap meaningless. When `LO_MAX_SESSIONS`
is set it is an upper bound and overrides the proposal.

After writing it, run `scripts/ready-set.sh <graph> <status-dir> <cap>` once and
confirm it does **not** exit 4 — malformed JSON, a missing `.tasks` array, and a
dependency naming an undefined task are all caught here.

**Visual spec (`design` role).** While extracting the above, flag each task that is
UI-facing *and* whose source issue references a design (e.g. a Figma link). If the
`design` role is configured, pull that task's spec with it (don't guess from the
link) and carry it into the brief's `<design_spec>` (Phase 3 step 2). Backend-only
tasks, or any task with no design reference, skip this. With `design` unset, ignore
design links entirely — the original behavior.

## 🚦 Gate 1 — task-split approval (REQUIRED)
Report the task list, the dependency graph (showing the expected flow as Waves is
fine), the proposed **slot count with its rationale and what it protects**, and a
rough cost note. The briefing also carries the risk table, one row per task with
exactly the columns | task | risk | basis | profile (plan depth / worker model /
review lenses) | rework budget |, filled from the Tier to pipeline profile
table; this adds no AskUserQuestion question — a tier change is requested
through the existing revise answer. That report is the briefing; the approval itself is **one
AskUserQuestion call** (§ Asking the user) in the same turn — Q1 the task split
(approve as proposed / revise / abort). Bundle every remaining open decision
(task-set options, substrate choice) into that same call as further questions,
each with a recommended answer — never a separate turn per decision, never a
prose question. **Wait for the user's approval** before launching anything.

**Substrate — ask here, in this same turn.** Before writing that report, run
`scripts/orca-detect.sh`. Non-zero (no Orca): tmux, silently — say nothing about
substrates. Exit 0: report that Orca was detected and carry the choice as a
**question in the same AskUserQuestion call** as the split approval, options
— **Orca**: native trust-screen handling, event-driven waits, native
liveness; **tmux**: mid-flight steering via `send-prompt.sh`, no extra dependency. **Wait
for the user's answer**; their answer decides, and you carry it into Phase 3. A
detected Orca always asks — there is no default, no remembered choice, no
environment override. Launch nothing until both the split and the substrate are
answered.

## Phase 3 — Launch + plan (dispatch loop)
**Phases 3–4 are one dispatch loop, not a per-Wave repeat.** Each round:

1. First `scripts/collect-status.sh .orchestration/graph.json .orchestration/status
   .worktrees` (pulls in any worker-local record `watch-status.sh` has not yet
   polled since the last round — `ready-set.sh` reads the status dir directly, it
   does not poll). Then `scripts/ready-set.sh .orchestration/graph.json .orchestration/status <cap>`
   → **0** dispatch the printed ids, **2** nothing dispatchable but work is in
   flight (go wait for an event), **3** **DEADLOCK** — a failed dependency or a
   cycle: **do not wait**, report it and get a human decision (with no worker
   running, no event can ever arrive); after the human intervenes, return to
   step 1 to re-run the check, **4** the graph or status could not be read —
   refuse, do not guess; fix the error then re-run step 1, **5** every task is
   in a terminal state → go to Phase 5.
2. For each dispatched task (`<N>` = the number of tasks in this round):
   - tmux: **0** (Preceding-interface injection) + steps **1–3** below (setup,
     brief, launch, watch plan_ready). Orca: **O1–O5**.
   - **1** `scripts/setup-worktrees.sh <integ> <root> <base> <branch>...` then
     verify with `git worktree list`.
   - **2** Per task: write `briefs/<task>.md` (templates/brief.md) — fill
     `<tools_guidance>` and `<design_spec>` — then launch session and watch
     until `plan_ready` (step 3 below). **Write the brief at dispatch time.**
     It only needs the signatures this task consumes, and by then those are
     `approved`, so they're settled. When `explore` is graphify, the
     `<tools_guidance>` row is: `explore: graphify — graphify explain
     "<Symbol>" --graph <main-root>/graphify-out/graph.json | head -40 (lead,
     not evidence; the graph reflects the integration base, and a worktree
     carries no graphify-out)` with `<main-root>` the absolute main-checkout
     path.
   - **3** Collect `plans/<task>.md` when each session reaches `plan_ready`.
3. For each planned task, deliver §2 (implement) with `scripts/send-prompt.sh
   send lo-<n> "<prompt>"` (tmux, see Phase 4 for exit-code branch logic), or
   `orca orchestration task-create` the implement Task then
   `scripts/orca-worker-start --task <impl_task> --terminal <handle> --worktree
   id:<repoId>::<path>` (Orca; a bare `--terminal` with no `--worktree` is
   rejected — `terminal_worktree_mismatch`, see O3).
   On delivery failure, re-run step 3 after fixing the error.
4. Wait for event. tmux: `scripts/watch-status.sh --tasks <running ids>
   <status-dir> impl_done <N>` — without `--tasks` the tasks approved in
   earlier rounds satisfy `expected=<N>` immediately and the wait spins. Orca:
   `scripts/orca-wait.sh` with the implement Task ids, already event-driven.
5. On wake, handle that task: run the Phase 4 review — `test-floor.sh`, then
   the `task-reviewer` agent — and read the verdict line of
   `reviews/<task>-rN.md` (`head -1`). If tests weak, audit with
   `test-quality-auditor`. **On
   approval, merge before you loop (issue #90):** fast-forward first —
   `git fetch . <branch>:<integ>` (no checkout needed, works while the main
   worktree sits on another branch) — and only fall back to
   `scripts/safe-cleanup.sh merge <root> <integ> <branch>` when that is not
   fast-forwardable (that verb DOES check out `<integ>` in the main worktree).
   Either way, verify the merge landed with `git merge-base --is-ancestor
   <branch> <integ>` before dispatching any dependent — that exit code is the
   evidence, not the merge command's own chatter. Then append one line to the
   blackboard with the shell append primitive, never Write/Edit — see
   **Blackboard** below: `printf '%s\n' '- [<task>] merged: <outputs> now on
   <integ>' >> .orchestration/notes/decisions.md`. Then return to step 1:
   whatever dependency it released shows up in the next
   `ready-set.sh` round already merged, and the freed slot refills immediately.
   On rework needed, run
   the Phase 4 rework sequence — `status-update.sh <task> rework`, read the
   new `.attempt`, write `reviews/<task>-rN.md`, re-deliver with
   `send-prompt.sh send` (or a new Orca Task on the same terminal). The
   rework budget is `ready-set.sh`'s job (`LO_MAX_REWORK`); exhaustion
   surfaces as exit-3 DEADLOCK, handled at step 1.
   When `ready-set.sh` returns **5**, go to Phase 5.

**Session knobs (tmux substrate, set once per run):** `export LO_RUN_ID=<short-run-id>`
so every `launch-session.sh` gets a collision-proof name `lo-<n>-<run-id>` (reuse that
exact name for later `send-prompt.sh`); the script also exports the guardrails
escalation env into each worker. Also export `LO_GRAPH=.orchestration/graph.json
LO_WORKTREES_ROOT=<root>/.worktrees` once per run: with both set, every
`watch-status.sh` call pulls each worker's worktree-local `.orchestration/status`
(and `questions/`) records into the canonical dir automatically, once per poll
(issue #167 — workers never write into this checkout). It also collects
`blocked/` records (the `worker-blocked-signal.sh` hook) the same way, except a
canonical `blocked/<task>.json` is pruned once its worker-side copy is gone —
see exit 8 below. `ready-set.sh` reads the
status dir directly and does not poll, so before every `ready-set.sh` round also
run `scripts/collect-status.sh .orchestration/graph.json .orchestration/status
.worktrees` yourself (Phase 3 step 1). Trust-screen wording drifts between CLI releases —
if a launch hangs, set `LO_READY_EXTRA` / `LO_TRUST_EXTRA` (substrings) or
`LO_READY_TIMEOUT`. `LO_PASTED_TAIL_LINES` (default 40) is `send-prompt.sh`'s
fallback pasted-marker window, scanned only when it cannot locate the input box
in the pane capture — raise it if a CLI release changes the input-box chrome so
the box stops being locatable. `send-prompt.sh send` pastes any payload of
`LO_PASTE_THRESHOLD` bytes or more (default 800) as one bracketed tmux paste
and waits `LO_PASTE_SETTLE` seconds (default 2) before the submit key; after a
delivered verdict it confirms receipt against the worker's newest transcript
for up to `LO_RECEIPT_TIMEOUT` seconds (default 10; 0 disables) —
`LO_TRANSCRIPT_DIR` overrides the derived `~/.claude/projects/<mapped worktree
path>` location — and `state`/`send` recognise a mid-turn worker by
`LO_BUSY_REGEX` (default: the CLI's elapsed-time spinner suffix or the legacy
`esc to interrupt`) plus the optional fixed-string `LO_BUSY_PATTERN`.
`status-update.sh` resolves the status file's `session` field
from `tmux display-message -p '#S'` only when the caller is itself inside tmux
(`$TMUX` set) — a coordinator-side call (this shell, not a worker's tmux pane)
must pass `STATUS_SESSION=<lo-n-runid>` explicitly, or the record's `session`
field is left absent rather than guessed from whatever tmux session happens to
be active.
`watch-status.sh` now exits **5** on a pending guardrails escalation, one line per
record naming its `recorded <ts>` next to the task's current `phase @<updatedAt>` —
triage from that line alone: if the phase transition is LATER than the record, the
worker already moved on by bypassing the denial, so `rm` the record WITHOUT
delivering an answer (nothing is waiting for it). Otherwise the worker is still
waiting: approve/deny, then `sh {SKILL}/scripts/resolve-escalation.sh <escdir>
<task> lo-<n> "approved — re-run: <cmd>, then continue"` (or `"denied —
<alternative>"`) — it clears the record(s) and delivers in one pre-approved
call; the guardrails deny message told the worker the orchestrator would
re-run it, so a cleared escalation without a delivered answer leaves it
waiting forever. On exit 6, retry delivery directly with
`scripts/send-prompt.sh send lo-<n> "<same message>"` — then relaunch. Exits **3** on a failed
OR a *dead* worker (a non-terminal task whose tmux session vanished — recorded via
the status file's `session` field) — both abort fast instead of waiting the
timeout. It also exits **6** on a pending worker question, **7** on a live worker
whose pane is stalled, and **8** on a blocked worker (a
`.orchestration/blocked/<task>.json` record from the
`worker-blocked-signal.sh` hook, confirmed by a pane that stayed unchanged
across two polls) — playbooks in Phase 3 step 3. It presses Enter itself
when a worker's input box holds an unsubmitted paste on two consecutive
polls, unless `LO_AUTO_RECOVER` is `0`, `off`, or `false`;
`LO_AUTO_RECOVER_MAX` (default 3) bounds those presses per session per watch
process, and a malformed value is refused with **exit 4**. Give each
phase its own deadline with one exported
`LO_PHASE_TIMEOUTS="plan_ready=900,impl_done=3600,done=1800"`, keyed on the TARGET
phase of each wait: precedence is an explicit `[timeout-sec]` argument, then the
matching entry, then the 3600s default. A malformed entry (no `=`, non-numeric,
`<= 0`, or an unknown phase name) is refused with **exit 4**, never silently
defaulted; the effective budget and its source are printed before the wait.
`LO_MAX_REWORK` (default 3) is the rework budget `ready-set.sh` enforces —
same validation as `LO_MAX_SESSIONS` (empty/non-numeric/`<= 0` refused with
**exit 4**).

**The coordinator shell may be zsh:** zsh (the macOS default) does not
word-split unquoted parameter expansions the way bash does, so a bash-style
loop such as `for pair in "$name $task"; do launch-session.sh $pair; done`
silently passes the whole `"name task"` string as one argument instead of
splitting it into two. Launch each task with its own explicit
`launch-session.sh` call, arguments spelled out per task, never through a
splitting loop. `launch-session.sh` now fails fast against exactly this class
of bug: it exits **2** before any tmux call when the final session name
(after the `LO_RUN_ID` suffix) is empty or contains a character outside
`[A-Za-z0-9_-]`, when the worktree argument is not a git work-tree root, or
when exactly one of `LO_STATUS_DIR`/`LO_TASK_ID` is set (set-but-empty counts
as unset). A malformed caller now gets an immediate exit 2 instead of a
silently launched broken worker.

**Substrate (the user decided at Gate 1 — do not re-decide here):** the answer was
Orca or tmux. **Orca** → spawn **and supervise** workers through it, not raw tmux:
it resolves the trust/TUI screen, gives native liveness, and pushes worker events to
you instead of making you poll. Replace steps 1–3 below with O1–O5:

- **O1 — bind the Run once per orchestration run.**
  `orca orchestration run-create --objective "<goal>" --json` → keep `run_id` for the
  whole run (on re-entry, `orca orchestration run-use --id <run_id> --json` instead).
- **O2 — one Task per task-*phase*.**
  `orca orchestration task-create --spec "<the prompt>" --task-title "<short>" --json`
  → `task_id`. Use `templates/session-prompt.md` §O1–§O4 as the `--spec` body; the
  tmux §1–§4 one-liners are for `send-keys` and say "wait", which is wrong here.
  A `worker_done` settles a Task exactly once, so plan / implement / rework are
  separate Tasks. **You sequence them yourself** — dispatch phase N+1 only after
  phase N reads `completed`. Do *not* chain with `--deps`: it takes a JSON array
  (`--deps '["task_x"]'`; a bare id errors), and even then a task created that way
  came back `task_not_startable` with its dependency already `completed`
  (isolated: an identical task with no deps started fine on the same terminal).
- **O3 — start the worker.** First make sure the integration branch exists in the
  main repo (`git branch <integ> <base>` if it does not) — the tmux path gets this
  from `setup-worktrees.sh` step 1, this path does not. Then create the Orca
  worktree **from that branch**: `orca worktree create --repo id:<repoId>
  --name <task> --base-branch <integ> --no-parent --setup run --json` → copy the
  whole `worktree.id`. `--base-branch` is **not optional here**: omit it and Orca
  branches from the repo default (its documented fallback), so a Wave-2 task would
  not build on Wave 1's merged output and Phase 6's merge would drag in unrelated
  default-branch drift. Then scope guardrails inside it — **required, not a
  nicety** — with `scripts/worker-guardrails.sh <worktree-path>`, the same
  single-source script `setup-worktrees.sh` calls on the tmux path. Skip it and
  the worker inherits the *repo/global* rules instead of the sandbox ones, so
  routine in-worktree work (`rm -rf ./build`, `git checkout .`) turns into a
  `deny` + escalation and the worker stalls on the coordinator for every one of
  them. Then, with the escalation env exported:
  `GROUNDWORK_ESCALATION_DIR=<abs> GROUNDWORK_TASK_ID=<task>
  DEV_LOOP_WORKER_MODEL=<id from the profile table> DEV_LOOP_WORKER_EFFORT=<level from the profile table>
  scripts/orca-worker-start.sh --task <task_id> --worktree id:<repoId>::<path>
  --agent claude` → prints `dispatch=<id>` and `handle=<agent-handle>`. For this
  task's **next** phase pass `--terminal <handle>` **together with** `--worktree
  id:<repoId>::<path>` instead, so the session keeps its context — a bare
  `--terminal` with no `--worktree` is rejected with `terminal_worktree_mismatch`,
  because Orca resolves a bare terminal handle against the *caller's* own
  checkout, not the worker's (measured: run_08cb6f65cbfa, 2026-08-29, and one
  prior run). `worker-start --agent` alone cannot carry environment variables, so with
  the escalation env set the script creates the agent terminal itself
  (`terminal create --command`, the escalation contract + `--permission-mode`) and
  binds the Dispatch to it; that is why the worktree must exist first and why
  `new-child` / `new-top-level` are refused in this mode.
  Bare `worktree create` leaves one unused fallback shell beside the agent
  (measured: 3 workers → 4 terminals). After the worker is up, confirm with
  `orca terminal list --worktree id:<...> --json` that the extra handle is an idle
  shell — not a configured default tab — and close just that one with
  `orca terminal close --terminal <handle> --json`.
  A `worker-start` that fails **spends the Task**: it goes to `status=failed` and
  every later attempt on it returns `task_not_startable`. Do not retry the same
  Task — create a fresh one with the same spec. (Seen with `runtime_unavailable`,
  which is what you get when that terminal is still busy with another Dispatch.)
  The **not-spent exception**: `terminal_worktree_mismatch` (above) fires
  *before* dispatch — Orca rejects the bad `--terminal`/`--worktree` pair before
  ever handing the Task to a worker — so the Task stays `ready`, not `failed`,
  and MAY be retried on the same Task with corrected args. The spent rule
  applies to failures at or after dispatch (e.g. `runtime_unavailable`).
- **O4 — wait on pushed mail, not on a timer.** Workers on this substrate also
  write status worker-locally (issue #167) — Orca's own mailbox is not that
  channel, so after EVERY `orca-wait.sh` return, before acting on the events,
  run `scripts/collect-status.sh .orchestration/graph.json .orchestration/status
  .worktrees` to pull those records into the canonical dir.
  `GROUNDWORK_ESCALATION_DIR=<abs> scripts/orca-wait.sh [--until-all] <timeout-ms>
  [<task_id,task_id,...>]` → **0** completions arrived (acked — process them),
  **2** window elapsed *or* the ack did not land (checkpoint, just re-run), **3** a
  worker reported failure, **4** the runtime itself did not answer (`ok:false`, a
  nonzero status, or a connection lost mid-wait) — an **outage, not a checkpoint**:
  run `orca status --json` before waiting again, and restart nothing on this code
  alone, because a dead runtime does not stop a worker session (measured: workers
  kept committing and pushing while the runtime was down), **5** escalation
  pending (approve/deny, then **`sh {SKILL}/scripts/resolve-escalation.sh
  .orchestration/escalations <task>`** — no delivery args on the Orca path,
  since answers go through orca, not send-prompt.sh; use the 2-arg form — then
  re-run — like watch-status, code 5 recurs while a record is still on disk,
  by design), **6** question pending
  (`orca orchestration reply --id <msg_id> --body "<answer>" --json`, re-run).
  **Always pass this Wave's task ids — that is correctness, not a progress
  nicety.** Heartbeats reach this mailbox despite `--types`, and so do completions
  from an earlier Wave or an unrelated Task; without the ids none of that can be
  told apart from your own work. With them, exit 0 means a *successful
  `worker_done` for one of your ids* and the `completed=<c>/<n>` line is scoped to
  this Wave. Add `--until-all` to keep consuming batches until every listed id is
  completed, so one Wave costs one coordinator turn instead of one per batch; it
  still returns immediately on 3/4/5/6. Codes 3/5/6 leave the batch unread on
  purpose, so an unhandled event is never silently dropped — which also means
  delivery is **at-least-once**: a replayed batch must be processed idempotently
  (key off `taskId`, never off a local counter). `ORCA_WAIT_RECHECK_MS` (default
  15000) slices the wait so a guardrails record written *while* you are blocked
  surfaces within one interval instead of one full `<timeout-ms>`. When polling
  ANY inbox on this substrate, diff the SET of message ids you have already
  processed, never the count — a new message can arrive in the same interval
  another one leaves, leaving the count unchanged while the content changed.
- **O5 — liveness, in two questions.** `scripts/orca-worktree-alive.sh <wt>`
  (0 alive / 1 dead / 2 unknown — treat unknown as *not* dead) replaces watch's
  tmux check. It only asks whether a terminal is attached, which a wedged worker
  passes: measured, three workers sat on an interactive CLI prompt for 75 minutes,
  ALIVE the whole time, Tasks still `dispatched`, no escalation, diffs unchanged.
  So also run `scripts/orca-worker-stalled.sh <wt>` (0 progressing / 1 stalled /
  2 unknown — treat unknown as *not* stalled; `ORCA_STALL_MS`, default 600000).
  A stall is not a failure to act on blindly: read the worker's screen
  (`orca orchestration worker-read --dispatch <id> --limit 40 --json`) before you
  decide, because "wedged on a prompt" and "finished but never reported" look the
  same from the outside and need opposite responses. When the screen shows the
  worker parked on a permission dialog, `orca terminal send --terminal <handle>
  --text "2" --enter` clears it — decline (2) is the safe answer; granting the
  permission on the user's behalf is not something the coordinator does
  unattended.

**You cannot steer a running worker.** `orchestration send --to dispatch:<id>`
lands in the worker's mailbox, which a Claude worker never polls, and a new
Dispatch cannot be delivered while the current one runs (that is O2's
`runtime_unavailable`). So a mid-flight correction does not arrive: the supported
path is the Phase-4 review/rework round. (If you do send one, omit `--type` —
`note`/`guidance`/`message`/`info` are all rejected as `invalid_argument`.)

**Worker protocol on this substrate — put this in every `--spec` you dispatch.**
The worker still calls `status-update.sh` at each phase (the status files remain the
durable re-entry state); on top of that it must:
1. report the phase exactly once — `orca orchestration send --type worker_done
   --subject "<status>" --body "<what changed, what remains>" --task-id <task_id>
   --dispatch-id <dispatch_id> --outcome succeeded|failed --files-modified "a,b" --json`
   (a failure is `--outcome failed`, never failure encoded only in prose);
2. forward a guardrails escalation instead of stalling on it — when a command is
   denied with an escalation notice, `orca orchestration send --type escalation
   --subject "guardrails <rule>" --body "<command + why>" --task-id <task_id>
   --dispatch-id <dispatch_id> --json`;
3. use `orca orchestration ask --question "<q>" --timeout-ms <n> --json` for a
   blocking question, and then end its turn — and if that window expires, resume the
   same question with `ask --resume <message_id>` rather than deciding it or asking
   it again. A timeout leaves the question pending; it is not an answer. Measured on
   a 3-worker run: at 600s and 900s both workers instead "proceeded on a conservative
   assumption" and reported the guess after the fact.

Step 2 is the *fast* path for a guardrails block — it arrives with the worker's own
context. It is not the only one: `orca-wait.sh` pre-checks
`GROUNDWORK_ESCALATION_DIR` before it blocks, so a guardrails record still surfaces
when the worker never sends the message (it died, or it is not a Claude session).
Export that dir for both scripts and the file stays the safety net the tmux path
already relied on.

If the user chose tmux at Gate 1 (or `orca-detect.sh` was non-zero), use the tmux
`launch-session.sh` + `watch-status.sh` path below, unchanged. Always verify each
Orca `--json` result before relying on its fields (`worker-start` returns
`.result.dispatchId`, and the agent handle as the `role:"agent"` entry in
`.result.effects[]`). `orca-spawn.sh`
remains only for a worker needing custom agent argv (e.g. codex `--model` /
reasoning-effort flags) that `worker-start` cannot express.

0. **Preceding-interface injection (Wave 2+, and completed base outputs):** before
   launching this Wave, fill each task's brief `<dependencies>` with the **exact
   signatures** of (a) the approved preceding Wave and (b) any completed (partial-
   resume) child it depends on — the real signature, not a paraphrase. This is the
   contract the downstream session plans against; loose text invites drift. Wave 1
   with no completed dependencies skips this.

   **What a `deps` edge delivers, and what it does not (issue #90).**
   `graph.json` `deps` gate DISPATCH on the dependency's approval, and this step
   injects its SIGNATURE — never its merged CODE. Merged code reaches a task's
   worktree only through the merge-on-approval rule (step 5 below): a new
   worktree branches from the integration branch's tip at the moment
   `setup-worktrees.sh` runs (step 1), so it contains a dependency's code only
   if that dependency was already approved and merged by then. A task that must
   read merged results directly — realigning docs against another task's actual
   diff, asserting an invariant that spans two tasks' code — cannot rely on the
   signature alone: give it `deps` on every producer it reads, so it is not
   dispatched until each producer is approved and merged.

   **Contract-first dispatch (shared surfaces) — ordering exception.** A task
   that PRODUCES a shared surface (Phase 2) reorders its own steps so the stub
   it commits can quote a real signature and land before its own worktree
   exists: run step 2a's `wiki-plan` invocation for this task FIRST (on the
   `task-analyst` agent, then the `task-planner` agent) — it writes `plans/<task>.md`, but do not launch yet
   — then commit the stub below, THEN
   step 1 (`setup-worktrees.sh`, whose worktree now branches from a tip that
   already contains the stub), then step 2 (write the brief, referencing the
   already-written plan) and launch. A consumer task needs no reordering of its
   own: by round/dependency ordering its step 1 always runs after the
   producer's stub commit has landed, so its worktree inherits the contract
   normally.

   Commit the stub in a temp integ worktree: `git worktree add
   .worktrees/integ-stubs <integ>`, write the stub file(s) at the plan-named
   repo-relative paths — the exact signature `plans/<task>.md` step 2a just
   decided, plus a `// contract: <task> owns the implementation` marker comment
   (in the stub language's comment syntax), never implementation — commit
   `chore(orchestrate): contract stubs for <task>`, then `git worktree remove
   .worktrees/integ-stubs`. The producer's brief says it IMPLEMENTS the stub in
   place, not create it fresh. A producer that must change a committed stub's
   signature reports that as a plan gap: you re-decide, recommit the stub, and
   notify every consumer via the blackboard (append-only, shell `>>` primitive
   — see **Blackboard** below).
1. `scripts/setup-worktrees.sh <integ> <root> <base> <branch>...` — each worktree
   is created AT DISPATCH TIME, branched from the integration branch's CURRENT
   tip; never pre-create a worktree for a future wave, or it misses whatever
   merges land between now and that wave's dispatch. Then verify with `git
   worktree list` — the script's own `base=<hash>` line on each new worktree
   names what it actually branched from.
2. Per task: write `briefs/<task>.md` (templates/brief.md) — fill `<tools_guidance>`
   from the resolved tool profile so the session uses the right knowledge/tacit
   tools (the plan step is fixed to `wiki-plan`, not a configurable role), for
   a UI-facing task fill `<design_spec>` with the `design` role's pulled spec
   (Phase 2), and fill `<effort_level>` with the Tier to pipeline profile
   table's value for the task's tier — then

   The brief and plan are what the worker reads, not what it writes, so every
   reference to them inside a composed prompt uses the `{ORCH_DIR}` token
   (absolute path to this run's `.orchestration` dir, substituted like every
   other `{...}` token) — the worker's cwd is its own worktree, which does not
   contain `.orchestration/`. Status/questions are the opposite case: a worker
   writes those worktree-locally via the RELATIVE `STATUS_DIR=.orchestration/status`
   (issue #167), never through `{ORCH_DIR}` — the coordinator's own checkout is
   never a target a worker writes to. Repo files (source, tests, tracked docs) stay
   relative to that cwd instead: an absolute repo path would make the worker
   edit the main worktree rather than its own. This coordinator's own
   `briefs/<task>.md` / `plans/<task>.md` references above stay relative — the
   coordinator's cwd is the main repo root.

   **2a. Plan it yourself, before launching — on the `task-analyst` and
   `task-planner` agents, never in this session.** First invoke the bundled
   `task-analyst` agent (Agent tool, fresh context) for this task: it runs
   `wiki-plan` Phase A, writes `plans/<task>/analysis.md` and the gate-A
   ledger, and replies with its ANALYSIS REPORT. Re-run `gate-check.sh --run`
   on the gate-A ledger yourself; on exit 0 invoke the bundled `task-planner`
   agent, which runs Phase B and C from that analysis and writes
   `plans/<task>/` plus the flat `plans/<task>.md`. Planning still
   belongs to the coordinator side on purpose: a worker runs a cheaper tier
   (`DEV_LOOP_WORKER_MODEL`), and a plan is where an unmade
   decision becomes the implementer's guess — so the plan must come from the
   pinned planning models, not from whatever tier is executing. Both agents
   pin their model and effort in frontmatter (the Tier to pipeline profile
   table names the file per tier), so that
   guarantee holds without this session holding a single plan body: you read
   only the agents' fixed reports, the gate ledgers' exit codes you re-run
   yourself, and the Size verdict line — never `analysis.md`, `design.md`,
   `plan.md`, or the flat plan. Pass each agent the task id, brief path, plan dir,
   gates dir, risk tier, wiki root, integ ref, and any pointer files (issue
   text, user decisions, blackboard).

   **Apply the tier's profile here.** Read the task's risk from graph.json
   first, call the profile table's file for that tier (`task-analyst-r1` /
   `task-planner-r1` at R0 and R1), and pass the tier to both agents: when R0,
   they run wiki-plan in lite mode; when R1, full Phase A/B/C; when R2 or R3, full with the
   plan-reviewer call required (R3: a second call with a different Agent
   model override, both verdicts recorded). In every full-mode tier the
   plan-reviewer call is yours, not the agent's — see the two-stage handshake
   below. Then re-check the plan-derived signals (size:large, no-wiki:<n>) and raise the
   tier per the Phase 2 rule before launching. A raise that changes the
   planning profile — out of R0 (lite to full), or from R0/R1 to R2/R3 (`-r1`
   to base agents) — re-plans on the new tier's agents from Phase A, with the
   new tier's plan-reviewer calls, before launching. At launch, prefix the model and
   effort on the call itself, never export it:
   `DEV_LOOP_WORKER_MODEL=<id from the profile table> DEV_LOOP_WORKER_EFFORT=<level from the profile table> LO_STATUS_DIR=<abs status dir> LO_TASK_ID=<task> scripts/launch-session.sh lo-<n> <worktree> bypassPermissions "<plan prompt>"`
   — launch-session.sh reads both variables per call, so each dispatch carries
   its own tier.

   **Two-stage handshake (full mode).** The agent stops after writing
   `design.md` and replies with its stop report (plan dir, gate-A rc, decision
   and no-wiki counts, expected size, contradiction line). Before this and
   every later reviewer call, run
   `sh ${CLAUDE_PLUGIN_ROOT}/skills/wiki-plan/scripts/plan-gate.sh check requirements-covered <plan dir>`.
   On `fail`, forward its stderr with SendMessage: a "no leading R<n> id"
   failure to the same `task-analyst` first (wait for its ANALYSIS REPORT and
   re-run gate-A), any other failure to the same task-planner; then wait for a
   fresh stop report. These bounces are not reviewer calls, but a third
   `fail` on the same task is escalated to the user, like a third reviewer
   FAIL. You then run the
   `plan-reviewer` agent yourself (Agent tool) on `analysis.md` + `design.md`,
   record its full output under `design.md`'s `## Review`, and on
   `VERDICT: PASS` write just that line into `<plan dir>/review-verdict.md`;
   on `VERDICT: FAIL` forward the blocking findings with SendMessage — a
   finding against `analysis.md` to the same `task-analyst` first (wait for its
   fresh ANALYSIS REPORT and re-run gate-A), then always to the same
   `task-planner` with the remaining findings, plus "analysis.md changed —
   re-check design.md against it" when the analyst changed it — and wait for
   a fresh stop report (bounded at 3 reviewer calls,
   R3 always two). Then resume the same agent with SendMessage: "verdict
   recorded — emit gate-B, run Phase C, write the flat plan, reply with the
   final report". R0 lite mode has no stop: the agent runs straight through
   to the final report.

   Every design decision must be made and
   grounded in a `wiki/` page (record the decision->page map); leave nothing "as
   appropriate". The worker then ADOPTS this plan (session-prompt §1 / O1) instead
   of authoring one, and still signals `plan_ready` — so the phase sequence, the
   `plan_ready` watch, and the ready-set scheduler are all unchanged.

   **Confirm gate evidence before dispatching.** If this task's `wiki-plan`
   invocation ran Phase A/B, `.dev-loop/gates/plan-A-<task>.md` and
   `plan-B-<task>.md` must exist and `gate-check.sh --run` must exit 0 against
   each — a lite-mode `ABANDON` entry with a recorded reason still counts as
   passing that gate. A plan with no evidence — the ledger is missing, or any
   gate reads `UNMET` or `CLAIMED` — must not be dispatched: send the agent
   that owns the failing ledger (gate-A: `task-analyst`, gate-B:
   `task-planner`) back to `wiki-plan`'s corresponding Phase with SendMessage instead of
   launching a worker on it — and re-run `gate-check.sh --run` yourself on
   both ledgers after every report: the agent's report is a claim, the
   ledger's exit code is the evidence.

   **Read the Size verdict before launching.** `plans/<task>.md` step 5 carries a
   REQUIRED `## Size verdict`; read that one line now (`grep -n -A1 '^## Size verdict' plans/<task>.md`), before `launch-session.sh` — the task
   is still undispatched at this moment, so no status file exists for it yet.
   `small`/`medium` — continue below, unchanged. `large` — do NOT launch this
   task. Instead attempt `scripts/graph-drop.sh .orchestration/graph.json
   .orchestration/status <task-id>` on the oversized node and branch on its exit
   code: **exit 0** (no dependents) → add each piece the verdict recommends as an
   independent node with `scripts/graph-add.sh .orchestration/graph.json
   '<node-json>'` — `split_of` naming the dropped parent id, `deps` the parent's
   former deps, `outputs` partitioned among the pieces — real parallelism, since
   each piece now competes for its own slot; **exit 3** (dependents/consumers
   still name it) → do not force the drop — keep the original node and fall back
   to the existing overlap-split semantics (**Splitting a task mid-run** below)
   instead. Either branch, report the changed task list to the user immediately
   — same duty as a mid-run split — then resume this loop with the (possibly
   changed) task set.

   Because both agents pin their own model, **the planning models are the
   profile table's analysis and design rows, whatever model this coordinator
   session is running**. To change them, edit the `model:` and `effort:` lines
   in the base agent file and re-run `scripts/gen-agent-tier-variants.sh`; on a
   model-scoped 429 follow the 429 rule under the profile table. The worker
   model and effort are set per dispatch from
   the Tier to pipeline profile table, prefixed on the launch call below — never
   exported run-wide for the session.

   A worker that reports the plan is contradictory or under-decided is telling you
   the planning pass was wrong: forward the gap report verbatim with SendMessage
   (a gap in the analysis goes to the same `task-analyst` first — Re-plan
   ladder) to the same task-planner agent that wrote the plan (it patches `plans/<task>.md`
   from its intact context and replies with a fresh final report), re-run the gate
   and Size checks above, and re-send §1. Do not
   let the worker re-plan — that silently moves planning back onto the worker tier,
   which is the thing this step exists to prevent — and do not patch the plan in
   this session either (Coordinator token budget, Known amplifier). Then
   `DEV_LOOP_WORKER_MODEL=<id from the profile table> DEV_LOOP_WORKER_EFFORT=<level from the profile table> LO_STATUS_DIR=<abs status dir>
   LO_TASK_ID=<task> scripts/launch-session.sh lo-<n> <worktree> bypassPermissions
   "<plan prompt>"`
   (plan prompt = templates/session-prompt.md §1 — the tmux set — with the
   subagent + tmux worker protocol blocks). With BOTH vars set, a successful
   launch (the confirmed-submission path AND the session-reuse path) pre-seeds
   `<LO_STATUS_DIR>/<LO_TASK_ID>.json` phase=pending with the resolved session
   name via the sibling status-update.sh, so dead/stalled-worker detection covers
   the pre-plan_ready window; a seeding failure warns on stderr only and never
   changes the exit code; either var unset = the previous behavior exactly.
   Exit **0** = launched *and* the prompt confirmed
   submitted; **4** = the REPL never became ready (relaunch); **5** = the prompt was
   sent but submission could NOT be confirmed — the session is alive and may be
   holding an unsubmitted prompt, so read it with `scripts/send-prompt.sh state
   lo-<n>` and branch on its exit: **9** (`unsubmitted`) means the prompt is
   already in the box — `scripts/send-prompt.sh keys lo-<n> Enter`, never a
   re-send (that double-pastes); re-send only when `state` reports `ready`/0.
   Never launch a second session on top of it.
3. `scripts/watch-status.sh <status-dir> plan_ready <N>` in the background; when it
   exits, collect `plans/<task>.md`. *(Orca substrate: `scripts/orca-wait.sh
   <timeout-ms> <this Wave's task ids>` per O4 instead — same exit-code contract,
   event-driven.)*
   *(Plans proceed autonomously per the user's choice — no per-plan gate.)*
   `watch-status.sh` only answers "does the session still exist"; a worker can hold a
   live session and produce nothing for hours. So on a long wait also run
   `scripts/tmux-worker-stalled.sh lo-<n>` (**0** progressing / **1** stalled /
   **2** cannot tell — treat unknown as *not* stalled; silence threshold
   `LO_STALL_SEC`, default 600s), the tmux mirror of O5. Read the pane before acting
   on a stall: "wedged on a prompt" and "finished but never reported" look identical
   from outside and need opposite responses.

**Watch exit playbooks (tmux).** Mechanical responses for the non-terminal watch
exits — handle, then relaunch watch with the same target:
- **6 — question pending** (prints `[watch] question pending — <task>: <question>`;
  recurs while `questions/<task>.json` exists, like exit 5; exit 5 wins when both
  are pending): read the record (`{ts, taskId, question, options, worktree}`),
  answer with `scripts/send-prompt.sh send lo-<n> "<answer>"`, then delete BOTH
  the canonical `.orchestration/questions/<task>.json` AND the worker-side copy
  at `<record.worktree>/.orchestration/questions/<task>.json` — the collector
  only copies a question record when the canonical one is absent (copy-if-
  absent), so leaving the worker-side file behind re-collects the
  already-answered question on the very next poll. If the task's status was recorded `phase=failed` when it asked the
  question, reset it to the phase you actually observe (read the worker pane
  first) BEFORE relaunching watch — the reset IS a normal status write, not a
  new phase word: `STATUS_DIR=<dir> STATUS_SESSION=<lo-n-runid> sh
  scripts/status-update.sh <task> <observed-phase> note="reset after exit-6
  answer"` — otherwise `watch-status.sh` counts the stale `failed` phase and
  aborts with exit 3 again on the very next poll. Then relaunch watch.
- **8 — worker blocked** (prints `[watch] worker blocked — <task>:<session>
  (<reason>: <detail>)`; recurs while the canonical record exists and the
  pane stays static): act on `<reason>` — `rate_limit` / `overloaded` /
  `server_error` → read the pane, THEN delete BOTH
  `.orchestration/blocked/<task>.json` and
  `<record.worktree>/.orchestration/blocked/<task>.json` right away (the
  collector copies a worker-side record back otherwise; deleting only after
  a multi-hour wait means the same static pane re-wakes exit 8 on every
  poll — about every 30s — for the whole window); THEN, if the CLI shows
  automatic continue armed (`autoContinueAtUsageLimit`, on by default for
  interactive claude.ai-subscription sessions when the reset is under 24
  hours), just wait and relaunch watch once it resumes; otherwise follow the
  usage-limit branch of 7 below and relaunch watch only after that wait;
  `quota_auto_resume_stale` → the pane asks to press enter to continue:
  `scripts/send-prompt.sh keys <session> Enter`; `quota_auto_resume_disabled`
  → automatic continue will not resume the task (turned off, reset more than
  24 hours out, continuation dropped, or retry cap exhausted): report it to
  the user; `permission_prompt` / `elicitation_dialog` /
  `elicitation_url_dialog` / `agent_needs_input` / `worker_permission_prompt`
  → read the pane and answer with `scripts/send-prompt.sh keys` (decline is
  the safe answer); `idle_prompt` → finished-but-silent or a question asked
  in prose: send a prompt to emit the missing signal or to use
  `ask-coordinator.sh`; `authentication_failed` / `billing_error` / anything
  else → report to the user. For every reason OTHER than `rate_limit` /
  `overloaded` / `server_error` (handled above, before its wait), then
  delete BOTH `.orchestration/blocked/<task>.json` and
  `<record.worktree>/.orchestration/blocked/<task>.json` (the collector
  copies a worker-side record back otherwise) and relaunch watch.
- **7 — stalled live worker** (prints `[watch] worker stalled — <task>:<session>`;
  the weakest signal — failed(3) and all-reached(0) win over it; driven by
  `tmux-worker-stalled.sh`, silence threshold `LO_STALL_SEC` default 600s; a
  missing script or tmux disables the check): read the pane FIRST
  (`tmux capture-pane -t "=<session>:" -p | tail`), classify with
  `scripts/send-prompt.sh state lo-<n>`, then act — **9** (`unsubmitted`, the
  input box holds a parked paste) → `scripts/send-prompt.sh keys lo-<n> Enter`
  (watch now presses this Enter itself on two consecutive observations unless
  `LO_AUTO_RECOVER` disables it, so reaching 7 with 9 means auto-recover is off
  or capped),
  then relaunch watch (this is the exact path a field incident took ~10 minutes
  to find via the stall timeout instead); interactive chooser → answer with
  `scripts/send-prompt.sh keys <session>
  <key>...` (allowlist exactly `Up Down Left Right Enter Escape Tab Space 0-9 y n`;
  ALL keys validated before ANY is sent; **0** sent / **2** invalid session or
  key, nothing sent / **3** gone / **6** send failed on a live session);
  usage-limit stop ("You've hit your session limit · resets HH:MM") → if the
  pane shows automatic continue armed (`autoContinueAtUsageLimit`), the CLI
  resumes by itself — nothing further to do; otherwise wait for the reset
  time, then re-send a resume prompt that orders a state re-check
  (git status / tests) before continuing; finished-but-silent (forgot
  status-update) → send a prompt to emit the missing signal; auth/trust screen →
  keys per the screen. Then relaunch watch.
- **2 — timeout** (prints `[watch] TIMEOUT (<budget>s, source=<source>)`): a
  checkpoint, not a verdict — re-check each session with
  `scripts/tmux-worker-stalled.sh lo-<n>` and `scripts/send-prompt.sh state
  lo-<n>` — **9** (`unsubmitted`) is now one of the answers: the worker is
  parked on an unsubmitted prompt, recover with `scripts/send-prompt.sh keys
  lo-<n> Enter`, never a re-send — read panes, then relaunch watch with the
  same target.

## Phase 4 — Implement + review (max 3 rework)
Deliver §2 (implement) to each session with `scripts/send-prompt.sh send lo-<n>
"<prompt>"` — **0** delivered, **4** queued behind a busy turn, **7** unconfirmed
(may still have been delivered — do not resend; cross-check with `wait`/`state`),
**8** lost (confirmed: two quiet observations plus a failed automatic resend —
safe to re-dispatch), **10** truncated (the worker's transcript shows a user
message shorter than the prompt sent — the head was lost in the CLI's paste
handling: re-send it shorter, or write it to `{ORCH_DIR}/prompts/<task>-<round>.txt`
and send only a pointer; never trust the tail that arrived), **3** the session
is gone, **2** the session name or prompt was rejected. Branch on the exit code; stdout is exactly one token and stderr is
advisory context that must never be parsed. On **4**, `scripts/send-prompt.sh wait
lo-<n> [timeout]` blocks until the worker picks it up (**0** picked-up, **5**
deadline expired, **9** the worker is parked on an unsubmitted prompt: press
`keys lo-<n> Enter`, do not re-send). Then
`watch-status ... impl_done <N>`. *(Orca
substrate: `task-create` the implement Task, then `scripts/orca-worker-start.sh
--task <impl_task> --terminal <handle> --worktree id:<repoId>::<path>` to reuse
that task's existing session — a bare `--terminal` is rejected with
`terminal_worktree_mismatch` (see O3) — and wait with `scripts/orca-wait.sh`.
Rework rounds are further Tasks on the same `--terminal`+`--worktree` pair.)*

Before invoking the reviewer, run the floor: `scripts/test-floor.sh <wt> '<integ>'`.
At review time the worker has not committed (§2 forbids it; §4 commits
later), so the single-ref form measures the working tree — including
untracked new test files — instead of an empty `<integ>...HEAD` range; an
empty measurement now exits 2 (`unknown`, stderr `empty-range`), never
`pass`. **Exit 3** — do not invoke `task-reviewer` and skip the auditor
entirely; the coordinator writes `reviews/<task>-rN.md` itself with line 1
`VERDICT: rework` and the itemized stderr reasons (`no-tests` /
`case-count:<file>:<n>` / `no-assertion:<file>:<case>`) become the findings
of `reviews/<task>-rN.md` —
this consumes a rework round exactly like any other finding (run the rework
sequence below). **Exit 0 or 2** — continue to the lens pass unchanged,
and when the auditor is invoked, pass `floor=pass` or `floor=unknown`
alongside it.

Run the review on the `task-reviewer` agent (Agent tool, fresh context — not
this coordinator session; `task-reviewer-r1` at R0 and R1, per the Tier to
pipeline profile table). Pass it: the task id and round N, the worktree
path, the integration ref, the `{ORCH_DIR}` paths of the brief and the plan,
the task's risk tier, the floor result (`floor=pass` or `floor=unknown`), the
absolute review output path `{ORCH_DIR}/reviews/<task>-rN.md`, and the
template path `templates/review-report.md`. The agent runs `git -C <wt> diff
<integ>` plus `git -C <wt> ls-files --others --exclude-standard` for
untracked files itself (the worker has not committed yet) and writes
`reviews/<task>-rN.md` from `templates/review-report.md` with line 1 EXACTLY
`VERDICT: approve` or EXACTLY `VERDICT: rework` — a prefix or substring
match is not valid, and the unfilled template placeholder
`VERDICT: approve | rework` matches neither; the coordinator reads that line
with `head -1` and treats any other first line, including the placeholder,
as not-a-verdict (re-run `task-reviewer`, never approve). The coordinator
MUST NOT read the worktree diff into its own context (issue #192 stage 4).
Lens 4's cross-task ordering check still belongs to the integration-reviewer
at Phase 5; per task, the agent applies these fixed lenses:

1. **Plan conformance** — diff vs. the plan's decision→page map and the
   brief's `<scope_boundaries>` / `<out_of_scope>`; a decision silently made
   differently at implement time is a defect even when the code works.
2. **Wiki re-route from the diff** — run AGENTS.md routing protocol step 7 on
   the diff itself; report any page reached that the plan never named.
3. **Execution-environment reality** — any new flag/subcommand/API/dependency:
   confirm it exists in the version present where the code actually runs
   (`wiki/platforms/toolchains/flag-availability-at-the-execution-site.md`).
4. **Multi-object write ordering** — 2+ files/objects/rows written without a
   transaction; any ordering a concurrent reader could observe mid-flight
   (`wiki/backend/common/storage/multi-object-write-ordering.md`). You are the
   only reviewer who sees every worktree at once, so cross-task ordering
   hazards are your job alone.
5. **AC traceability** (R2 and above) — build the three-column table `| DoD item | gate id | test case |` with one row per `<definition_of_done>` item of the brief: gate id from `.dev-loop/gates/<task>.md`, test case as `<file>:<test name>`; any row with an empty gate or test cell is a Findings item whose failure scenario is the behavior that item guards going unverified (`wiki/qa/process/acceptance-criteria.md`).
6. **Excess** — for each element the diff adds (a file, function or method,
   class/interface/type, parameter, config key/flag/env var, dependency), name
   the brief or plan line it serves: the Objective, a `<definition_of_done>`
   item, a D-number, or a later task's Inputs — an element a later task
   consumes is that task's seam, so it has no caller yet by design. An element
   the plan's decision table names is never an Excess finding — a dispute with
   the plan belongs to lens 1. When no line serves it AND a search shows one
   of (a) zero call sites outside its own tests, (b) a re-implementation of an
   existing repo helper or a standard-library/language function — name that
   function and where it lives, (c) two or more call sites that all pass the
   same value for the new parameter, config key, or option, (d) a new
   interface, abstract type, or factory with exactly one implementation — it
   is a Findings item whose failure scenario is that search command, its hit
   count, and "no brief or plan line needs it". A complexity judgment with no
   such search evidence goes under Non-blocking.

**Lens set by tier.** The tier is passed to the agent and selects its lens
set: when the task is R0, it runs lenses 1, 3 and 6 only and writes
`not run — R0 profile` in the other rows; when R1, lenses 1-4 and 6; when R2
or R3, lenses 1-6. When the task is R0 and a review finds a second blocking round,
escalate to the user with AskUserQuestion instead of dispatching a second
rework — LO_MAX_REWORK stays the per-run bound; the R0 budget of 1 is
enforced by the coordinator not re-dispatching.

Alongside the pass, if a session's tests look weak, **cross-call
`test-quality-auditor` yourself** (self-call + orchestrator cross-call;
`test-quality-auditor-r1` at R1, per the Tier to pipeline profile table).
On shortfall, decide rework: `STATUS_DIR=.orchestration/status
scripts/status-update.sh <task> rework` atomically increments `.attempt`;
read the NEW value N from `status/<task>.json`, write `reviews/<task>-rN.md`
(N is that counter value, so the filename and the counter can never
disagree), inject §3 (rework), repeat. The rework budget is
**`ready-set.sh`'s job**, not tracked here: `LO_MAX_REWORK` (default 3) fails
a non-terminal task for scheduling once its `.attempt` reaches it, and with
nothing else in flight that surfaces as **exit 3 DEADLOCK** naming the
exhausted task — Phase 3 step 1 already routes that to a human decision.
When a task is approved, merge it into the integration
branch first (Phase 3 step 5's merge-on-approval rule) before you
return to step 1 of the dispatch loop — whatever dependency it released shows up in the next
`ready-set.sh` round already merged, and the freed slot is refilled immediately.
When `ready-set.sh` returns **5**, go to Phase 5.

**Insight emission.** After a rework round's fix is confirmed by re-review,
emit one ★ Insight candidate per finding that was fixed and confirmed —
**only** for findings that sat in `## Findings` (they carried a failure
scenario: a real defect). `## Non-blocking` items never emit — they lack a
failure scenario, so they are style/preference, not the near-miss lesson this
rule captures. Both conditions must hold: the finding was a `## Findings`
item AND the following re-review round confirmed the fix — a finding that is
caught but not yet fixed is not a near-miss lesson yet. Use the frozen block
format from `hooks/insight-instruction.sh` verbatim (`trigger`/`directive`
required, `why`/`evidence` expected); the 0–3-per-session cap still applies,
so if a round confirms more fixes than the remaining budget, prioritize the
highest-signal finding. Map the fields like this:

```
★ Insight ─────────────────────────────────────
trigger: <diff signal in lens vocabulary — e.g. "new CLI flag, no version check">
directive: <reviewer's action on that signal — e.g. "confirm the flag exists in the deployed toolchain version">
why: <why the miss happened — e.g. "lens 3 exists for exactly this, the first pass skipped it">
evidence: reviews/<task>-rN.md + the fixing commit
─────────────────────────────────────────────
```

## Splitting a task mid-run

A worker may report that its task is much larger than the brief assumed. It
proposes; **you decide**, and you reply either way — a rejection that is never
sent is indistinguishable from silence, and a worker that hears nothing decides
for itself.

The proposal must carry, per piece, the `files` it would touch and the `outputs`
it would newly produce. Without them there is nothing to judge; ask for them
rather than guessing, and say the worker should hold.

**The decision is one overlap test.** Compare the proposed pieces' `files`
against every task that is currently dispatched and every task still pending in
`graph.json`:

- **No overlap** — add it as an independent node. `scripts/graph-add.sh
  .orchestration/graph.json '<node-json>'` with `split_of` naming the parent and
  `deps` carrying whatever the piece genuinely consumes. It enters the ready set
  and the next free slot picks it up, so the split buys real parallelism.
- **Overlap** — add it with `deps: ["<parent>"]` and give it to the **same worker**
  in the **same worktree** when the parent settles (Orca:
  `worker-start --task <new> --terminal <handle> --worktree id:<repoId>::<path>`
  — a bare `--terminal` is rejected with `terminal_worktree_mismatch` (see O3);
  tmux: `send-prompt.sh send
  lo-<n>`). Do **not** create a second worktree: the parent's code is not on the
  integration branch until Phase 6, so a second checkout would be editing files
  it cannot see. This split buys a smaller review and rework unit, not
  parallelism — say so when you report it.

`graph-add.sh` returns **0** added, **3** REJECTED with the reason and the file
untouched, **4** the graph or the node could not be read. On **3**, reply to the
worker with the reason; do not retry the same node. A rejection for `depth 1`
means the proposal came from a piece that was itself a split — that is a signal
Phase 2's decomposition was wrong, so bring it to the user rather than working
around it.

On **4**, the graph file is unreadable (I/O error or corruption) — a failure
class different from validation. This blocks all dispatch. Reply to the worker:
"Split on hold — orchestrator cannot read its graph state. Escalating to user
immediately." Do not add the node. Report immediately to the user: "Graph I/O
error at `.orchestration/graph.json` — resolve and resubmit the proposal. Run is
blocked until `.orchestration/graph.json` is accessible."

You decide this without a user gate, but **report it immediately** — the task
list the user approved at Gate 1 just **grew** (split), and they need the
overlap verdict and the schedule change to intervene if they disagree.

**Dropping a task mid-run.** When a sibling's outcome makes an undispatched
node obsolete, drop it with `scripts/graph-drop.sh .orchestration/graph.json
.orchestration/status <task-id>`. It refuses a task that already has a status
file (any phase — dispatched, not yours to drop; that's the rework/failed
flow's job) and refuses a drop that would leave a dangling reference (another
task's `deps`, `split_of`, or `consumes` still names it) — drop dependents
before their parents. `graph-drop.sh` returns **0** dropped, **3** REJECTED
with the reason and the file untouched, **4** the graph or status dir could
not be read. You decide this without a user gate too, but **report it
immediately** — the Gate-1-approved task list just **shrank** (drop), and the
user needs the reason to intervene if they disagree.

## Blackboard — facts on disk, decisions on the hub

`.orchestration/notes/decisions.md` is an APPEND-ONLY file for cross-task facts:
an interface change to a declared output, or a load-bearing decision another
task could consume. Entry format, one line each: `- [<task-id>] <what changed
/ decided>`.

Workers read it at exactly two checkpoints — start of the implement phase, and
again before self-review — and append when they change a declared interface
or make a load-bearing decision; they never edit or delete an existing line,
even their own. The coordinator appends one line at each merge-on-approval
(Phase 3 step 5): `- [<task>] merged: <outputs> now on <integ>`.

Treat every line as a hint that something changed, not as the decision
itself: task assignment, rework routing, and merge approval stay on the
coordinator hub (status files, `ask-coordinator.sh`, Gate 1/Gate 2) — the
blackboard has no question path and settles nothing on its own
(`wiki/infrastructure/agent-orchestration/control-signals-vs-primary-artifacts.md`).
No helper script — a single shell primitive only: append with `printf '%s\n'
'- [<task-id>] <fact>' >> {ORCH_DIR}/notes/decisions.md`, `O_APPEND`-atomic
for a one-line write, and read the file with Read/`cat`. Never use Write/Edit
to append — both are read-modify-write, so a concurrent worker's stale read
silently drops another worker's just-appended line
(`wiki/backend/common/storage/multi-object-write-ordering.md`).

## Phase 5 — Integration test loop (max 3)
Merge-preview onto the integration branch and run the integration tests (use the
`verify` role's command if configured). On a test failure, route back to the
responsible session as rework.

Once tests are green, run the integration REVIEW via the `integration-reviewer`
agent (Agent tool, fresh context — not this coordinator session;
`integration-reviewer-r1` when every task in the run is R0 or R1, per the Tier
to pipeline profile table): pass it the
integration branch name, base ref, repo root, worktree paths, and the
`{ORCH_DIR}` paths of `graph.json`/briefs/plans/reviews; it runs `git diff
<base>...<integ>` itself. The coordinator consumes only its `VERDICT:
approve|rework` + `FINDINGS` + summary — MUST NOT read the full integration
diff into its own context at this phase (that full read is Gate 2's, the last
one — see **Coordinator token budget**). On `VERDICT: rework`, route each
finding back to its responsible task as rework, same as a failed integration
test. Repeat until the agent returns `VERDICT: approve`.

## 🚦 Gate 2 — pre-merge review (REQUIRED)
Show the full integration diff (`git diff`), then ask for the verdict with
**AskUserQuestion** (§ Asking the user) — merge / send back for rework / abort.
**Wait for the user's confirmation.**

## Phase 6 — Cleanup + merge (only after Gate 2)
1. `scripts/safe-cleanup.sh merge <root> <integ> <branch>...` — refuses dirty
   worktrees, merges sequentially, stops + reports on conflict (no --force). A
   branch already merged on approval (Phase 3 step 5, issue #90) re-merges here
   as a no-op — git reports "Already up to date" — so this sweep stays correct
   whether or not every branch was merged early.
2. `scripts/safe-cleanup.sh remove-worktrees <root> <branch>...` (after merge
   verified; skips any dirty worktree).
3. `scripts/safe-cleanup.sh kill-sessions lo-<n>...` (exact names only), or — instead
   of remembering every name — `scripts/safe-cleanup.sh sweep <root>`, the teardown
   for ONE run: kill every tmux session named `lo-<n>-$LO_RUN_ID`, `git worktree
   prune`, then report (never delete) any `.worktrees/` directory git does not know.
   `sweep` REFUSES with exit 1, touching nothing, when `LO_RUN_ID` is unset or is not
   `[A-Za-z0-9_-]+` — with no scope it would match every concurrent run's sessions.

**Orca substrate:** step 2 does not apply as written — an Orca-created worktree
is removed with `orca worktree rm --id <worktree.id> --json`, not
`remove-worktrees`, so Orca's own Run/Task state stays consistent with the
filesystem. `orca worktree rm` **also deletes the git branch**, so for every
branch `git merge-base --is-ancestor <branch> <integ>` MUST pass (exit 0)
**before** any `rm` — the same check an unforced `git branch -d` performs
internally — and there is no separate branch-deletion step afterward
(verified: 4 branches already gone after `rm`). Steps 1, 3, 4 are unchanged.
4. `scripts/safe-cleanup.sh list-orphans <root>` is read-only (kills, deletes and
   prunes nothing; needs no `LO_RUN_ID`) — the census across ALL run ids, and the way
   to read a dead run's id before sweeping it deliberately.
`--dry-run` may appear in any argument position on any destructive verb: it prints
exactly what the real run would touch and changes nothing, while refusals (dirty
worktree) still fire — a dry run never looks safer than the real one.
**Local merge into the feature branch only.** Remote push / PR is the user's job.
Steps 2–3 above (worktree removal, session sweep), plus archiving
`.orchestration/` artifacts, are exactly what `teardown` composes into one call
below — Phase 6's cleanup and the end-of-run contract cannot drift apart.

## End-of-run contract
Every orchestration run terminates in exactly one of three outcomes: **merged**
(Gate 2 passed, Phase 6 ran), **aborted** (the user or coordinator stopped the
run before Gate 2), or **escalated-and-abandoned** (a max-rework/max-retry
escalation was raised and the user chose not to continue). In all three, the
final step is the same: `LO_RUN_ID=<run-id> scripts/safe-cleanup.sh teardown
<root>` — it derives this run's worktrees from its own
`.orchestration/status/*.json`, removes them (dirty ones SKIPped, never
`--force`), sweeps this run's tmux sessions, and archives `.orchestration/`
artifacts to `archive-<date>-<runid>/`. A run that ends without teardown is
exactly the leak `list-orphans --stale` exists to find. On the Orca substrate
the same merge-verify-before-`worktree rm` rule applies to worktree removal
(see Phase 6's Orca substrate note); `safe-cleanup.sh teardown` still
archives `.orchestration/` and sweeps tmux-side leftovers regardless of
substrate.

After teardown, audit the run's token efficiency: `sh {SKILL}/scripts/token-report.sh
--cwd <root> .` for the coordinator's own session, plus one more `--cwd
<worktree>` per run worktree (each worker's transcripts live under its own
cwd). Put the table and any `warn:` lines in the final run report — a
peak-context warning is the signal to split tasks or delegate more next run.
Exit 0 is the report, exit 2 a usage error, exit 4 a malformed `LO_CTX_WARN`.
Basis: `wiki/infrastructure/agent-orchestration/session-context-token-budget.md`
(directive 1).

## Re-entry (resume)
On re-invocation with no context, measure real state first: `git worktree list`,
each `.orchestration/status/*.json` phase, and which `briefs/plans/reviews/`
artifacts exist. Resume from the earliest incomplete step (idempotently skip done
steps). There is no intermediate state such as a Wave index to restore. Reading
`.orchestration/graph.json` plus `status/*.json` and running `ready-set.sh` IS
the restored state — the same inputs always yield the same answer. The
rework round number is read from `status/<task>.json`'s `.attempt` field —
never reconstructed by globbing `reviews/<task>-r*.md`. Check `tmux ls`, and run `scripts/tmux-worker-stalled.sh <session>` on each
live one — a session that exists is not a worker that moves. Relaunch dead sessions and
re-deliver the right prompt with `scripts/send-prompt.sh send`. For leftovers of a
run that already died, `scripts/safe-cleanup.sh list-orphans --stale <root>`
enumerates them read-only (each with a `stale` verdict and its run id); tear a
dead one down with `LO_RUN_ID=<id> scripts/safe-cleanup.sh teardown <root>`.
On the Orca substrate, rebind the Run first (`orca orchestration run-use --id
<run_id> --json`), then measure with `orca orchestration task-list --json` +
`scripts/orca-worktree-alive.sh <wt>` **and** `scripts/orca-worker-stalled.sh <wt>`
— a Task reading `dispatched` proves only that it was handed out, never that the
worker is moving. Restart a proven-dead worker with a NEW Task (the old one is
spent, see O2) via `scripts/orca-worker-start.sh --task <new_task_id> --worktree
id:<...> --agent claude`; that call now probes first and rebinds to a live agent
terminal on that worktree instead of creating a second one, so re-running it is
safe (it exits 6 rather than guess when it cannot tell).
`setup-worktrees.sh` is idempotent (existing branches/worktrees are detected and
kept), so re-running it is safe. Note the difference from **partial resume** (Phase
0): that handles work done *outside* this orchestration — children with no
`.orchestration` record — whereas re-entry resumes this orchestration's own state.

## Guardrails
- You never implement — sessions do; you analyze, plan, review, manage.
- Gate 1 (task-split) and Gate 2 (pre-merge) are mandatory and are asked with
  AskUserQuestion, never as a prose question; everything else autonomous.
- No remote push, no PR, no force-push. Destructive cleanup only after Gate 2, via
  safe-cleanup (never --force).
- Sessions must not weaken tests (loop-implement guard); the auditor enforces it.
- No session or review agent may `git stash` — refs/stash is repository-global, so a
  parallel worker's stash pop can silently swap another worker's uncommitted work
  (issue #166); worker worktrees also carry a mechanical `Bash(git stash:*)` deny.
- Always verify real state after worktree/session ops (`git worktree list`, `tmux ls`,
  status files) — never trust echo logs (set -e is fail-open in eval subshells).
- Bundled agents only: `test-quality-auditor`, `integration-reviewer`, `task-reviewer`, `task-analyst`, `task-planner`, and each one's `-r1` copy. Don't
  depend on built-in agent names (general-purpose/Explore/Plan are
  version-dependent).
- A completed/excluded issue (partial resume) is injected as a **base output**, never
  silently dropped — otherwise its dependents lose their premise and re-create it.

Files in this skill

  • SKILL.md70.9 KB
  • scripts/ask-coordinator.sh2 KB
  • scripts/collect-status.sh4.6 KB
  • scripts/escalation-dir.sh1.2 KB
  • scripts/graph-add.sh4.7 KB
  • scripts/graph-drop.sh3.9 KB
  • scripts/install-permission-rules.sh5 KB
  • scripts/launch-session.sh15.9 KB
  • scripts/orca-detect.sh1.1 KB
  • scripts/orca-spawn.sh5.1 KB
  • scripts/orca-wait.sh13.8 KB
  • scripts/orca-worker-stalled.sh4.7 KB
  • scripts/orca-worker-start.sh15.8 KB
  • scripts/orca-worktree-alive.sh1.5 KB
  • scripts/ready-set.sh6.7 KB
  • scripts/safe-cleanup.sh21.9 KB
  • scripts/send-prompt.sh22.2 KB
  • scripts/setup-worktrees.sh3 KB
  • scripts/status-update.sh2.8 KB
  • scripts/test-floor.sh7.7 KB

Attribution

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

Comments

Loading comments…

Newsletter

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