Skip to content
Back to skills

Wish

BSecurity

Interactive orchestrator for working a single Djinn bead end-to-end. Reads the iter formula's step graph via `djinn formula describe <path> --json` and walks it in `needs` topological order, dispatching each step shape to its handler: go-mode bookends (`claim`, `commit-push`) run in-process; single-agent steps (`implement`, `fix-*`) dispatch to context-isolated subagents; the loop shapes run the iterate-until-pass loop driver (run the body, read its verdict, pass or spawn the paired fix step ...

  • 4 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added October 5, 2026
ai-agentsgoshellbashgitdatabasebackend

Works with

  • terminal
  • cli

Security analysis

B75/100
  • criticalAccesses sensitive system or user directories

Pro shows the line behind each finding and how to fix it

Scanned October 6, 2026

npx -y skills add kurofune/claudit --skill wish --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Wish?

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

Security grade badge for Wish
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/kurofune-wish-claudit/badge)](https://www.skillsdirectory.com/skills/kurofune-wish-claudit)

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
---
# Generated by djinn-gen-adapters from theme=djinn. DO NOT EDIT — regenerate via djinn-gen-adapters.
# djinn-compat: 1
name: wish
description: >-
  Interactive orchestrator for working a single Djinn bead end-to-end. Reads the iter formula's step graph via `djinn formula describe <path> --json` and walks it in `needs` topological order, dispatching each step shape to its handler: go-mode bookends (`claim`, `commit-push`) run in-process; single-agent steps (`implement`, `fix-*`) dispatch to context-isolated subagents; the loop shapes run the iterate-until-pass loop driver (run the body, read its verdict, pass or spawn the paired fix step until budget). This is the interactive mirror of the autonomous `core/iter.formula.toml` run — each step gets the same per-step context isolation the autonomous harness gets for free by spawning a fresh subprocess per step. Triggers on: /wish, work a bead, orchestrate a bead, run the iter formula interactively, drive a bead through the contract.
argument-hint: "<bead-id> [formula-path|variant] [--interactive]"
---

<!-- djinn-compat-check: begin -->
**Before anything else:** run `djinn skills check --here` in the repo you are working in. If it exits non-zero, stop: relay its output to the user verbatim — it names the fix — and run no other step of this skill. On exit 0, continue.
<!-- djinn-compat-check: end -->

# /wish — interactive bead orchestrator

`/wish` works a single bead end-to-end by driving the
**same iter formula** the autonomous Djinn harness runs
(`core/iter.formula.toml`, the canonical on-disk home of the embedded default),
but gives each step the per-step **context
isolation** that autonomous mode gets for free — autonomous mode spawns a fresh
subprocess per step; this orchestrator dispatches a fresh subagent per
single-agent step so no step's working context crowds the next.

The orchestrator is a **walker over a step graph plus a dispatch table**. It does
not hard-code the contract steps — it reads them from the formula at runtime, so
the orchestrator and the autonomous harness can never drift in which steps run or
in what order.

**Scope of this skill.** This body implements the spine (read the graph, order it,
dispatch the non-loop shapes — go-mode bookends in-process, single-agent steps to
subagents), the **iterate-until-pass loop control flow** for the loop shapes (§4c:
run the loop body, read its on-disk verdict, and either pass, spawn the next attempt
dispatching the paired `fix-*` step, or escalate when the attempt budget is
exhausted), the **`on_exhausted` escalation matrix** (`soft_fail` /
`leave_and_reopen` / `hard_reset`; §4c exhaustion matrix), the **`--interactive`
checkpoint flag** (Step 1b) — **autonomous remains the default** when the flag is
absent — and the **structural-fault halt** (`ActionHalt`, exit code 3; §4c
structural-fault halt), a hard mid-budget stop **distinct** from budget exhaustion.
The `implement` single-agent step dispatches `djinn-implementer`
unconditionally as the orchestrator's first authoring step (§4b).

---

## Step 1 — Read the bead id argument

The first argument is the **bead id** (e.g. `phoenix-abcd`). The optional second
argument is the **formula path or bare variant name** (see Step 1a for how it is
resolved when omitted). If no bead id was passed, stop and ask for one — the
orchestrator works exactly one bead per invocation.

Throughout this body, `<bead-id>` denotes that argument and `<formula-path>` the
resolved formula path.

**The bead worktree.** A `/wish` run never edits the
shared checkout. Resolve these once, from wherever you were invoked:

- `<repo-root>` — the shared checkout: the path on the first `worktree` line of
  `git worktree list --porcelain` (the main worktree, from any linked one too).
- `<repo-name>` — the basename of `<repo-root>`.
- `<worktree>` — `<repo-root>/../<repo-name>-wish-<bead-id>`, checked out on
  branch `wish/<bead-id>`. §4a `claim` creates or reuses it.

From `claim` on, `<worktree>` is the working directory for every command this
body runs and every subagent it dispatches, and the ONLY place anything is
edited. Every relative path in this body (`.djinn/state/<bead-id>/`, the codex
`$OUT` dirs) resolves inside `<worktree>`. `<repo-root>` appears only in the §4a
bookends: the worktree add/remove and the fast-forward of its `main`.

## Step 1a — Resolve the formula path (explicit arg > label > default)

`<formula-path>` is resolved in this strict precedence order, mirroring the
autonomous harness's per-bead dispatch (`resolvePerBeadFormula`,
`internal/djinn/session/run_formula.go`) so an interactive `/wish` run and an
unattended drain pick the **same** formula for the **same** bead:

1. **Explicit second argument wins.** If the operator passed a second positional
   argument, it overrides the label scan entirely. Two accepted forms,
   disambiguated by shape:

   - **A formula path** — the argument contains a path separator (`/` or `\`),
     starts with `.`, or ends in `.toml`. Use it verbatim as `<formula-path>` —
     e.g. `/wish <bead-id> core/iter-codex.formula.toml`
     forces the all-codex variant regardless of labels.
   - **A bare variant name** — anything else:
     `/wish <bead-id> codex` is shorthand for the
     iter-codex variant. The name is the SHORT variant name (`codex`, the
     `<name>` in `iter-<name>.formula.toml` and in the `formula:<name>` label)
     — NOT the full file stem (`iter-codex`), which would probe a doubled
     `iter-iter-codex.formula.toml` and error.

     **Resolve it by the `formula:<name>` label rule below, in full — all THREE
     tiers, including the embedded one.** `/wish
     <bead-id> codex` and a `formula:codex` label name the same formula, so they
     must resolve identically: `.djinn/iter-<name>.formula.toml` →
     `core/iter-<name>.formula.toml` → `djinn formula describe --embedded
     iter-<name> --json`, stopping and reporting the error only when the
     EMBEDDED tier also reports no such formula. Do not stop at the two on-disk
     probes: a consumer repo has no `core/` checkout and, since djinn-myttl, no
     scaffolded `.djinn/` copy either, so a two-tier explicit argument would
     fail in exactly the repos where the equivalent label succeeds — the drift
     this rule exists to prevent. Which variant names resolve is therefore a
     property of the binary (its diagnostic lists them), not a list restated
     here. The label rule's `<name>` guard applies unchanged: reject a path
     separator or a leading `.` (already excluded by the shape test above,
     restated so the two rules cannot drift).

2. **Otherwise, resolve from the bead's `formula:*` label.** Read the bead's
   labels and scan for the `formula:` namespace:

   ```bash
   bd show <bead-id> --json | jq -r '.[0].labels // [] | map(select(startswith("formula:"))) | .[]'
   ```

   - **Zero `formula:*` labels** → the **default production formula**, resolved
     with the same **override-else-default intent** the harness uses for the
     embedded default (`ResolvePayload`, `core/payload.go`, which falls through
     from a local `.djinn/` override to the *embedded* default bytes — not an
     on-disk `core/` probe): if `.djinn/iter.formula.toml` exists (the optional
     local override) use it; otherwise fall through to **`core/iter.formula.toml`**
     — the canonical on-disk home whose bytes ARE that embedded default, which
     djinn itself dogfoods (the local `.djinn/` default was deleted in
     djinn-ccyz.45). Because this skill shells out to `formula describe`, the
     first two tiers resolve an on-disk PATH, so the concrete
     `.djinn/`-then-`core/` probe matches `resolvePerBeadFormula`
     (`internal/djinn/session/run_formula.go`) rather than `ResolvePayload`'s
     embed fallthrough. This is the common case, and in this repo it resolves to
     `core/iter.formula.toml`. Probing `.djinn/` first then `core/` is mandatory —
     naming the local override path unconditionally would `formula describe` a file
     that no longer exists and stop the walk (Step 2's failure posture) for every
     normally-labeled bead.

     **Third tier — the binary, only when NEITHER file exists.** A consumer repo
     has no `core/` checkout, and since djinn-myttl `djinn init` no longer
     scaffolds `.djinn/iter.formula.toml` (a scaffolded copy shadowed the
     binary's embedded default forever, with no resync path back), so in a
     consumer repo BOTH on-disk probes above miss. Do not stop there — fall
     through to the default the binary itself embeds:

     ```bash
     djinn formula describe --embedded iter --json
     ```

     This tier is LAST, never first: a `.djinn/iter.formula.toml` override wins
     over it, and an on-disk `core/iter.formula.toml` wins over it, exactly as
     they do for the autonomous harness. Note the shape difference — `--embedded`
     takes a formula NAME (`iter`), not a path, and composes the graph out of the
     binary's payload FS, so Step 2 consumes this command's JSON directly instead
     of being handed a `<formula-path>`. Everywhere below that says "the resolved
     `<formula-path>`", read it as "the resolved describe invocation".
   - **Exactly one `formula:<name>` label** → the **`formula:<name>` variant**,
     resolved by the same override → canonical probe order as the harness's
     `resolvePerBeadFormula` (`internal/djinn/session/run_formula.go`): probe
     `.djinn/iter-<name>.formula.toml` first (the optional local override), and if
     it does not exist fall through to **`core/iter-<name>.formula.toml`** — the
     canonical home where the variant formulas ship next to the embedded default.
     For example `formula:codex` resolves to `core/iter-codex.formula.toml` in
     this repo. If **neither** path exists, take the SAME third tier the default
     does — `djinn formula describe --embedded iter-<name> --json`, the binary's
     own copy — mirroring `LoadFormulaVariant`'s three tiers exactly
     (`.djinn/` → `core/` → `core.PayloadFS()`); a consumer repo carries neither
     on-disk file, so stopping at tier two would strand every labeled bead there.
     Only when the embedded tier ALSO reports no such formula do you **stop and
     report the error** — its diagnostic lists the variants this binary ships, so
     an operator typo names itself. Reject a `<name>` containing a path separator
     or a leading `.` — the label is operator-set and `formula:../etc/passwd` must
     not resolve outside the formula dirs (this mirrors the harness's
     `ValidateFormulaName` guard).
   - **Two or more `formula:*` labels** → **stop and ask the operator** which
     formula to use. Ambiguous intent is never resolved by guessing; the
     autonomous harness errors here (`ErrAmbiguousFormulaLabel`) and the
     interactive path must not be more permissive.

The resolved `<formula-path>` — or, at the embedded tier, the resolved
`describe --embedded <name>` invocation — then feeds Step 2 unchanged.
Precedence summary: **explicit arg (path or bare variant name) >
`formula:<name>` label > default**, and within each of the last two the same
three tiers in order: **`.djinn/` override → on-disk `core/` canonical →
`djinn formula describe --embedded <name>` (the binary).**


**Record what you took, and — when the explicit arm won — what the bead alone
would have taken.** Carry two facts out of this step for Step 2c to announce:
the **precedence arm** that chose the formula (`explicit arg`,
`formula:<name> label`, or `default`) and the **probe tier** that found it
(`.djinn/ override`, `on-disk core/`, or `embedded`). Additionally, when arm 1
(the explicit second argument) won, run arms 2 and 3 anyway — they are pure
reads (`bd show` plus file-existence probes) and cost nothing — and keep the
formula the bead's own label-or-default WOULD have resolved to as the **shadow
resolution**. Step 2c compares the two. This resolution never changes the
formula the walk uses; arm 1 still wins.

## Step 1b — Interactive mode (`--interactive` checkpoint flag)

`/wish` is **autonomous by default**: with no flag it
walks the step graph (Step 3), runs the loop control flow (§4c), and applies the
`on_exhausted` escalation matrix end-to-end with **NO operator interaction** — the
same hands-off path the autonomous `core/iter.formula.toml` run takes and the
summoner supervisor (W7) depends on. This is the locked default; `--interactive`
only opts *into* pauses, it never opts out of autonomy elsewhere.

**Detect the flag.** Scan the invocation arguments (`$ARGUMENTS`) for the literal
token `--interactive`. Strip it before reading the positional `<bead-id>` /
`<formula-path>` (Step 1) so the flag may appear in any position. If `--interactive`
is **absent**, run **fully autonomous** — do **NOT** pause at any checkpoint below;
the walk runs straight through to `commit-push`. This no-pause default is the
**regression guard**: an interactive checkpoint must NEVER fire without the flag.

**When `--interactive` IS present**, pause at each **checkpoint** enumerated below.
At a pause: surface the relevant state (the just-produced verdict, the diff, or the
escalation), then **await an operator decision** — `continue` (proceed to the next
step) or `abort` (stop the walk and leave the bead as-is). The implementer retains
full autonomy *between* checkpoints (locked decision: checkpoints are operator
inspect/continue/abort points, not the orchestrator second-guessing the implementer
mid-step).

**Checkpoint points** (interactive mode pauses at each; autonomous mode runs past
all of them):

1. **After `implement` returns** — surface the implementer's summary, or its
   `already-done` verdict and evidence before the bead is closed (§4b). (Fires at
   §4b `implement`.)
2. **After each loop reaches its pass verdict** — the review loop
   (`review-loop`) and the `regression-loop`. Surface the pass verdict
   (`SHIP`, or a green tree) and the body artifact path so the operator can read
   the findings before the spine advances. (Fires in the §4c loop driver's
   **pass** branch.)
3. **Before `commit-push`** — the full working-tree diff is inspectable. Surface
   `git diff <pre>..HEAD --stat` (and offer the full diff on request) so the operator
   can review exactly what is about to be committed and pushed before the terminal
   go-mode bookend runs. (Fires at §4a `commit-push`, before the commit.)
4. **At any `on_exhausted` escalation** — before the §4c exhaustion matrix HALTs,
   pause so the operator can inspect the failing loop's last verdict and choose
   `continue` (accept the escalation and apply its disposition) or `abort`. This
   ties into the W4.1 failure-handling branch — the operator gets a say before the
   `soft_fail` / `leave_and_reopen` / `hard_reset` disposition is applied.

A `continue` resumes the walk at the next step; an `abort` stops immediately and
reports the bead's current state. Aborting does NOT reopen or reset beyond what the
current step already did — it is a clean operator-requested stop, not a rollback.
Concretely, after an `abort` the bead stays `in_progress`, any commits landed by
earlier checkpoints are retained on disk, and uncommitted working-tree changes are
left as-is in `<worktree>` — the operator resumes or cleans up by hand.

## Step 2 — Obtain the step graph

Shell out to the `djinn` binary to describe the formula as JSON:

```bash
djinn formula describe <formula-path> --json
```

This emits a `steps[]` array. Each step object carries:

- **`id`** — the step id (e.g. `claim`, `implement`, `review-loop`).
- **`type`** — one of the shapes in `core/SHAPE-REGISTRY.md` (the dispatch key —
  see Step 4).
- **`title`** — a human label (may contain runtime placeholders; ignore for
  dispatch).
- **`needs[]`** — the ids this step depends on. **This is the edge set the
  walker orders by.**
- **`loop`** — `null` for non-loop steps; otherwise `{max_attempts,
  check_function, on_exhausted}`. Present only on the loop shapes.
- **`children[]`** — the loop body steps (the review and fix children),
  present only on loop shapes. The spine does not descend into `children` at the
  top level — the loop driver (§4c) descends into them per loop step.

If the `djinn` binary is not on `PATH`, build it once
(`go build -o /tmp/djinn ./cmd/djinn`) and invoke `/tmp/djinn` instead. If
`formula describe` exits non-zero or emits JSON that does not parse, **stop and
report the failure to the operator** rather than proceeding on a partial or
guessed graph (the same stop-and-report posture as the cycle check in Step 3). Do
NOT hand-author the step list — the JSON from `formula describe` is the single
source of truth so the orchestrator tracks formula edits automatically.

## Step 2b — Resume preamble (re-invocation resumes at the first incomplete step)

`/wish` is **resume-safe by re-invocation**: when a prior
run was interrupted partway through a bead (session ended, context lost, crash) and
the operator re-invokes `/wish <bead-id>`, this preamble
runs **BEFORE the Step 3 step walk** and resumes at the **first incomplete step**
rather than redoing completed work. There is **no resume flag** — re-invocation
auto-detects existing in-progress on-disk state. (Locked decision: resume is a
re-invocation behavior, not a new flag.)

**Autonomous parity.** The autonomous harness already resumes —
`internal/djinn/session/resume.go` applies crash-recovery resume anchors so the
SECOND binary invocation resumes at the **anchored iter, not iter 1** (closing the
phoenix-ccyz.18 "crash kill mid-iter-3 followed by Djinn restart
resumes at iter 3, not iter 1" gap). Interactive `/wish`
mirrors that behavior, but by a **different mechanism**: the Go harness reads an
in-process `ResumeAnchors` map (`s.Cfg.ResumeAnchors`, threaded from `Sweep`); this
skill is **prose, not the Go binary**, so it has **no `ResumeAnchors` map**. The
interactive mechanism is **on-disk-state inspection** — it scans the same forensic
artifacts the loop execution (§4c/§4d) already wrote, **NOT** the Go `ResumeAnchors`
map. Reading the on-disk state is the interactive realization of the autonomous
anchor lookup, exactly as §4c reads the on-disk verdict artifact instead of
re-running the Go check.

**Idempotency guard (already-closed = finish, then stop).** First, run
`bd show <bead-id>` and read its status. If the bead is **already `closed`**, do
NOT re-run the walk and do NOT re-dispatch any step. When `wish/<bead-id>` or
`<worktree>` still exists, an earlier `commit-push` was interrupted: run
`cd "<repo-root>" && djinn wish land <bead-id>`, which finishes the remaining
stages (§4a `commit-push`). Then report that the bead is complete and **stop**.

**Fresh-run guard (empty/absent state = clean start).** Next, check
`<worktree>/.djinn/state/<bead>/` — run state lives inside the bead worktree, so
an absent `<worktree>` means absent state. (A `wish/<bead-id>` worktree at any path
other than `<worktree>` halts here, as §4a `claim` does.) When that directory is **empty or absent**, there is no prior
run to resume from: start the walk at the **first step** in topo order — identical to
a clean run (clean-run parity; this is the regression guard that an absent state dir
must never skip a step). Only a non-empty `.djinn/state/<bead>/` triggers the
resume scan below.

**Unlanded work = resumed run.** Classify the run **fresh** only when the state
dir is empty or absent AND `wish/<bead-id>` holds no commit missing from `main`:

```bash
git -C "<repo-root>" show-ref --verify --quiet refs/heads/wish/<bead-id> &&
  git -C "<repo-root>" rev-list --count main..wish/<bead-id>   # > 0 → resumed
```

An implementer can commit and crash before any artifact is written, so a
missing state dir alone never proves a fresh run. Any other case is a **resumed
run**: the §4b `implement` dispatch omits the already-done exit, and the walk
continues through review and `commit-push`, which lands those commits on `main`.

**The resume scan.** When `.djinn/state/<bead>/` exists and is non-empty, scan it for
the per-step artifacts each step writes, and for **each step in the Step 3 topo
order** decide whether it is **DONE**. Resume at the **first not-DONE step** in topo
order; every step before it is treated as already complete and is **not** re-run.

**Per-shape DONE detection** (mirrors the §4 dispatch shapes):

- **go-mode `claim`** → DONE when `bd show <bead-id>` reports the bead
  **`in_progress`** AND `<worktree>` exists on branch `wish/<bead-id>` (the
  claim's effects are the bd state change and the worktree, so they ARE the
  evidence). Otherwise resume at `claim`; it is idempotent (§4a).
- **`loop-with-review` step** → DONE when a **`<step>-iter-<N>.json` with a pass verdict is present**. On disk
  this is the §4d dir-per-iter layout: scan the `.djinn/state/<bead>/<loop-id>.iter-<N>/`
  iter directories, read the body step's verdict artifact inside (the file named by
  the body step `id`, **no `.json` suffix** — §4d), and treat the loop as DONE when
  the **highest recorded `iter-<N>`** holds the pass token (`SHIP`, per the §4c
  body→verdict map). (The bead's
  `<step>-iter-<N>.json` framing names the per-iter verdict record; §4d is its
  canonical on-disk realization — see §4d, do not duplicate the layout here.)
- **`loop-with-regression` step** → never DONE on resume: it writes no verdict
  artifact, so re-run it (a green tree costs one test + lint run and no agent).
- **`keep-loop` step** → DONE when any step after it in topo order is DONE: it
  writes no verdict artifact, and its kept rounds are already on the branch.
  Otherwise re-run it from round 1 with the current HEAD as the last kept commit.
- **single-agent step** (`implement` / `fix-*`) → DONE when its **per-iter state
  artifact is present** under `.djinn/state/<bead>/`. The `implement` step's
  artifact present means authoring already ran; resume past it. (The `fix-*`
  children write no structured artifact — §4d — so their done-ness is subsumed by
  the enclosing loop step's verdict scan above.)

**Loop resume granularity (highest recorded iter, re-run only the unfinished one).**
A loop step that is **not** DONE may still be **partway through its budget** — earlier
iterations passed their body→fix→re-check cycle but the loop has not yet reached a
pass verdict. Do NOT restart that loop at iter 1. Instead resume at the **HIGHEST
recorded `iter-<N>`** for that loop — comparing `N` **numerically, not
lexicographically**, so `iter-10` ranks above `iter-9` (a string compare would
wrongly pick `iter-9`, re-running a completed iteration on any 10+-iteration rework
bead) — and **re-run only that unfinished iteration**,
continuing the §4c cycle from there with `N` set to that highest recorded index. This
mirrors the autonomous anchored-iter resume (`resume.go` sets `s.iter = anchor.IterIndex`
rather than restarting at iter 1) — the interactive loop driver resumes at the highest
on-disk `iter-<N>` rather than re-running iterations that already completed.

Once the resume target is identified, hand control to the Step 3 walk **starting at
that step** (for a partway loop, at that loop with `N` pre-positioned to the highest
recorded iter). Steps before the resume target are skipped as already-DONE; the walk
proceeds normally from the resume point through `commit-push`.

## Step 2c — Announce the resolved formula, its knobs, and any disagreement

**Before Step 3 dispatches ANY step**, print a resolution announce. Front-door,
never a closing summary: a run summary naming the formula that was walked
arrives after the spend has already happened, which is exactly how four beads on
2026-09-07 were walked on the default `iter` with fable-pinned steps
hand-substituted, while the variant encoding that contingency already shipped.
The announce is cheap, and on the ordinary path it is **non-blocking**.

**What the announce must name.**

1. **The resolved formula** — the `<formula-path>` Step 1a produced, or, at the
   embedded tier, the `describe --embedded <name>` invocation.
2. **The precedence arm that chose it** — exactly one of Step 1a's three arms,
   named with that arm's literal name: `explicit arg`, `formula:<name> label`,
   or `default`.
3. **Where the formula came from** — exactly one provenance value. For a
   resolution that went through Step 1a's probe sequence — the `default` arm,
   the `formula:<name> label` arm, and the bare-variant-name form of the
   explicit arm — name **the probe tier that found it**: exactly one of Step
   1a's three tiers, named with that tier's literal name: `.djinn/ override`,
   `on-disk core/`, or `embedded`. A formula **path** the operator supplied
   directly (Step 1a's explicit-arg path form) is used verbatim and is probed by
   no tier at all — `/wish <bead-id>
   /tmp/custom.formula.toml` is a supported resolution that came from none of
   the three — so it has no tier to name: print the fourth literal provenance
   value `supplied path` instead. **Never force a directly supplied path into
   one of the three probe tiers** — an announce naming a tier the resolution
   never visited is a false announce, and the tier names are what an operator
   reads to tell a local override from the binary's own copy.
4. **A per-step knob table** — one row per step the walk will dispatch, with the
   four columns **step id**, **`adapter`**, **`model`** and **`effort`**: the
   knobs this run will ACTUALLY spend on. Include loop-body children and
   `fix-*` children, not just top-level steps — they are where the model pins
   live. Print `—` for a field the step does not declare.

**Read the table off the graph you already have.** The four knob fields come
from the Step 2 `djinn formula describe <formula-path> --json` output —
top-level steps at `.steps[]`, loop-body children at `.steps[].children[]`, the
same records the §4 dispatch knob rule reads. **do NOT invoke `formula describe`
a second time**: a second invocation can resolve differently from the graph the
walk dispatches from, and an announce naming knobs that are not the knobs used
is worse than no announce at all.

Announce shape (illustrative — the values come from the JSON, never typed):

```
formula: core/iter-codex.formula.toml
  arm:  formula:<name> label      tier: on-disk core/
  step id            adapter  model         effort
  claim              —        —             —
  implement          codex    gpt-6-sol     high
  review-loop  —        —             —
```

**Stop ONLY on disagreement.** After printing the announce, compare the walked
formula against Step 1a's **shadow resolution**. When
an explicit formula argument was passed AND the bead's own label-or-default
would have resolved to a DIFFERENT formula, **name BOTH formulas and STOP for
operator confirmation before dispatching the first step** — an explicit argument
must never outrank the bead silently. That case is operator-typed by
construction, so a human is present to answer.

**Everything else proceeds.** Both non-disagreement cases **proceed with no
pause** — print the announce and walk on:

- **No explicit argument.** When
  no explicit formula argument was passed, there is nothing to disagree with:
  the bead's label-or-default IS the resolution. **Proceed with no pause.**
- **An explicit argument that agrees.** When
  the explicit argument resolves to the SAME formula the bead's label-or-default would have,
  the operator restated what the bead already said. **Proceed with no pause.**

This narrowness is load-bearing. Step 1b locks
`/wish` as **autonomous by default** with **NO
operator interaction**, and unattended use (the summoner supervisor, W7) rests
on that default. An unconditional confirmation prompt here would break it. The
disagreement stop is the only pause Step 2c may introduce, and `--interactive`
remains the only way to opt into the Step 1b checkpoints.

**One stop, not two.** Step 1a already stops on two or more `formula:*` labels.
When that stop fired, the operator answered by naming the formula, and this
step's confirmation does **not** fire on top of it: treat the answered ambiguity
as the bead's own resolution and proceed with no pause — **one stop, not two
consecutive prompts**. The same holds in reverse: this step's disagreement
confirmation never re-opens the label ambiguity Step 1a already settled.

## Step 3 — Order the steps by `needs` (topological walk)

Walk the **top-level** `steps[]` in dependency order: a step runs only after
every id in its `needs[]` has run. Concretely, repeatedly select the next step
whose `needs` are all already-completed; ties (two independent steps both ready)
keep their original `steps[]` declaration order — not the order of the shrinking
ready-frontier. The production `core/iter.formula.toml` is a linear chain
(`claim → implement → review-loop → regression-loop → commit-push`), so the topological order is the declared order — but
order by `needs`, not by array position, so a future non-linear formula still
walks correctly. Never hardcode this chain: a consumer formula may insert,
drop or reorder steps, and the shipped variants patch knobs the describe output
carries — always walk the graph `djinn formula describe` returns.

If the `needs` graph has a cycle (no step is selectable but steps remain), stop
and report the malformed formula rather than guessing an order.

## Step 4 — Per-shape dispatch table

For each step in topological order, dispatch on its `type`. The authoritative
shape→handler mapping is `core/SHAPE-REGISTRY.md`; this table is its
orchestrator-side realization.

| `type` (shape) | Handler | Runs |
|---|---|---|
| **`go-mode-step`** | in-process bd/git bookend (see §4a) | in this context |
| **`single-agent-step`** | subagent dispatch — `implement` → `djinn-implementer`; `fix-*` → `djinn-fixer` (see §4b) | isolated subagent |
| **`loop-with-review`** | loop driver — body via the single-lens reviewer (see §4c) | driver + isolated body |
| **`loop-with-regression`** | loop driver with NO procedure body — you run the tree yourself, then dispatch `fix-regression` to `djinn-fixer` only when it is broken (see §4c) | driver + isolated fix |
| **`keep-loop`** | keep-loop driver — dispatch the body children each round, run the decide, then KEEP the round's commits or RESET the branch to the last kept commit, until a stop verdict or `max_attempts` (see §4c `keep-loop`) | driver + isolated body |

A step whose `type` matches none of the rows is a contract violation (the parity
test `TestFormulaStepsAllHaveKnownShapes` would have caught it upstream) —
stop and report the unknown shape rather than skipping it silently.

<!-- djinn-dispatch: claude-code -->
**Starting a dispatch.** The agents are the `.claude/agents/<name>.md` files
`djinn init` writes. Start each dispatch with the Agent tool, naming the agent
by bare name as `subagent_type` (a reviewer takes no agent file: dispatch it as
a general-purpose subagent).
<!-- djinn-dispatch: end -->

### §4a — `go-mode-step` (in-process bookends)

`go-mode-step` bodies bypass the LLM and run **in this orchestrator's context**.
The two top-level go-mode steps in the iter formula, `claim` and `commit-push`, are
the bookends of the run. `go-mode-step` writes no per-iter LLM artifact — its output is the side effect
(the `bd` state change, the commit). The `file_nits` go-mode steps
(`file-nits-impl`, `file-nits-tests`) are loop **children**, not top-level steps,
so the spine never reaches them directly — they run inside the W3.2 loop driver.
`mutation-check` is a go-mode `review-loop` child too, and `/wish` skips
it: an interactive run wires no mutation runner, so the review prompt carries no
`## Mutation check` report and the reviewer sweeps rubric 7 from the diff alone.

#### `claim`

Open (or reuse) the bead worktree, then claim the bead in-process:

```bash
want="$(cd "<repo-root>/.." && pwd -P)/<repo-name>-wish-<bead-id>"
# The path of the porcelain record that holds wish/<bead-id>, if any.
have=$(git -C "<repo-root>" worktree list --porcelain | awk -v b="branch refs/heads/wish/<bead-id>" \
  '/^worktree /{p=substr($0,10)} $0==b{print p}')
if [ -n "$have" ] && [ "$have" != "$want" ]; then
  echo "HALT: wish/<bead-id> is checked out at $have, not $want" >&2; exit 1
elif [ -n "$have" ]; then
  echo "reusing existing worktree $have (wish/<bead-id>) from an earlier run"
elif git -C "<repo-root>" show-ref --verify --quiet refs/heads/wish/<bead-id>; then
  git worktree add "$want" wish/<bead-id>    # branch kept, worktree gone
else
  git worktree add "$want" -b wish/<bead-id> main
fi
bd update <bead-id> --claim --status=in_progress
```

- **Reuse, and say so.** Reuse is keyed on the path AND the branch of one
  porcelain record. When `wish/<bead-id>` is already checked out at `<worktree>`
  (a previous run crashed), reuse it, tell the operator, and keep its commits and
  uncommitted changes. When it is checked out at any other path, halt and name
  that path. When the branch exists with no worktree, attach it without `-b`.
- **Never force-remove a dirty worktree.** Never run `git worktree remove --force`
  on a path whose `git -C <path> status --porcelain` is non-empty — not to
  recreate it, not to clean up. A worktree in the way that you cannot reuse is an
  escalation: name its path and stop.
- The name `<repo-name>-wish-<bead-id>` on `wish/*` stays clear of the summoner's
  `summoner-<bead-id>` worktrees on `summoner/work/*`, which its startup reap
  removes.
- The bead MUST be `in_progress` before any implementation step runs (the
  cardinal beads rule). This mirrors the autonomous `djinn.bead_claim` StepFunc.

Every step after `claim` runs with `<worktree>` as its working directory. Every
HALT or escalation below leaves `<worktree>` and `wish/<bead-id>` in place and
names the worktree path.

#### `commit-push`

Run the whole finish as one command, from `<repo-root>` (never from
`<worktree>`, which it removes):

```bash
cd "<repo-root>" && djinn wish land <bead-id>
```

Never perform any of its stages by hand, and never close any bead but
`<bead-id>` — not its parent epic, even when it has no other open child.
`djinn wish land` (`cmd/djinn/wish_land.go`) runs, in order:

1. A refusal check: the latest `review` sidecar under
   `<worktree>/.djinn/state/<bead-id>/` must approve the bead (`SHIP`, or
   `SHIP-WITH-FIXES` whose every actionable IMPORTANT finding is fixed or filed
   as a `review-follow-up` bead discovered from `<bead-id>`), `<repo-root>`
   must be on `main`, and `main` must still fast-forward to `wish/<bead-id>`.
2. Commit whatever is left in `<worktree>`.
3. The repo's test command inside `<worktree>`: `djinn.test_argv` from
   `.djinn/config.json`, else `go test -short ./...`.
4. `git -C "<repo-root>" merge --ff-only wish/<bead-id>`.
5. The §6 evidence comment and the four `evidence_*` labels, transcribed from
   that sidecar with the mapping the autonomous `djinn.commit_push` StepFunc
   uses (`ReviewEvidence`, `internal/djinn/session/formula_evidence.go`).
6. `git push` of `main`, `bd close <bead-id>`, then `bd dolt commit` and
   `bd dolt push` — a code-only push leaves the bead's closed status and labels
   invisible to collaborators (`core/bead-work-contract.md` §8).
7. `git worktree remove "<worktree>"` (never `--force`), then
   `git branch -d wish/<bead-id>`.

Each stage first reads whether it already happened, so a rerun after an
interruption anywhere finishes the rest without a second evidence comment or a
second close. On a non-zero exit, relay its message to the operator and stop:

- **Red tree, or a review that did not approve:** nothing landed; the bead
  stays `in_progress` and the worktree is kept. Escalate.
- **Fast-forward refused** (`main` moved on): the bead, worktree and branch are
  untouched. The message names the worktree and the rebase —
  `git -C <worktree> rebase main`, then re-run
  `/wish <bead-id>` (it resumes at
  `regression-loop`, which re-tests the rebased tree, then `commit-push`).
- **Anything later** (a push, bd, or a dirty worktree at teardown): fix the
  cause it names, then re-run `djinn wish land <bead-id>`.

**Interactive checkpoint (Step 1b #3):** in `--interactive` mode, pause BEFORE
`commit-push` and surface `git diff <pre>..HEAD --stat` so the operator can review
the working-tree diff before it is committed and pushed. Autonomous mode commits
and pushes without pausing.

### §4b — `single-agent-step` (subagent dispatch)

`single-agent-step` bodies dispatch to a **context-isolated subagent** so the
step's work does not crowd the orchestrator's context. Route by step id:

- **`implement`** → the `djinn-implementer` subagent. This is the
  orchestrator's **first authoring step**: it runs right after the `claim`
  go-mode bookend (§4a) — the step order is **claim → implement** — so the bead
  is claimed before any code is written. Dispatch the `implement` step
  **unconditionally** to `djinn-implementer` as a subagent,
  passing the claimed `<bead-id>` under the worktree dispatch preamble (below).
  The implementer reads that bead's
  `acceptance_criteria` / `design` / notes and builds the complete feature to
  every criterion per `core/bead-work-contract.md` §3 (committing incrementally,
  honoring backend TDD), then returns a compact summary of what it built. The
  orchestrator does not implement in its own context — the §3 authoring work runs
  in the implementer's isolated context window.

  **The already-done exit (first claim only).** On a fresh run (Step 2b found no
  prior state AND no `wish/<bead-id>` commit missing from `main`), add to the dispatch: "If every acceptance criterion already holds
  at HEAD, commit nothing and return `already-done` with the `file:line` or
  command output proving each criterion." On a resumed run, omit it — the bead's
  own earlier commits would read as done. When the implementer returns
  `already-done`, take the **close-and-skip** disposition, the mirror of the
  autonomous `ErrFreshnessStale` route (the 2026-09-26 lean-formula ruling):
  `bd close <bead-id>` with the implementer's evidence as the reason, skip every
  later step, run `bd dolt commit -m "<bead-id>: already done" && bd dolt push`,
  tear the worktree down with §4a `commit-push`'s stage 7 commands, and end the walk. It is a successful finish — never an `on_exhausted`
  disposition, never a reopen.

  **Interactive checkpoint (Step 1b #1):** in `--interactive` mode, pause after
  `implement` returns and surface its summary — or its `already-done` evidence,
  before the close. Autonomous mode does not pause.

  **Implementer-absent degraded note (not the primary path).** The unconditional
  dispatch above is the primary instruction. Only if no agent named
  `djinn-implementer` is resolvable in the current adapter — a
  misconfigured install, not the normal case — fall back to an explicit
  manual-implementation checkpoint: tell the operator to implement the bead by
  hand per `core/bead-work-contract.md` §3, then resume the walk at the next step
  once implementation is committed. This degraded note exists only to keep a
  broken install from hanging; it is **not** the W3.1 graceful-degradation
  default, which W5.2 replaced.

- **`fix-*`** (`fix-review`, `fix-regression`) → dispatch to `djinn-fixer` as a subagent under the
  worktree dispatch preamble, passing the `<bead-id>`, the findings from the paired review (or the failing tree
  output for `fix-regression`), and a `fix_threshold`. These steps are loop **children**, so they are
  dispatched by the W3.2 loop driver — the spine lists them here for completeness
  of the dispatch table, but does not invoke them at the top level.

<!-- djinn-dispatch: claude-code -->
The implementer and fixer subagents are pinned to Opus per the Djinn
model decision; a model knob on the dispatched step overrides the fixer's pin
per the §4c knob pass-through rule.
<!-- djinn-dispatch: end -->

#### Worktree dispatch preamble

Open EVERY dispatch — implementer, fixer, the `review` reviewer —
with this block, `<worktree>` and `<bead-id>` filled in, and start
the dispatched agent in `<worktree>`:

```text
WORKING DIRECTORY: <worktree> — a git worktree on branch wish/<bead-id>.
It is the ONLY place you may edit files. cd into it for every command.
Do not edit any file outside it. Commit on wish/<bead-id>; never push, pull,
or create or switch branches.
```

No dispatch names the shared checkout or its path as a place to work. A codex
step gets the same scope through `-C "<worktree>"` (§4c).

### §4c — loop shapes (the iterate-until-pass loop driver)

The two walked loop shapes (`loop-with-review`, `loop-with-regression`) each drive
an **iterate-until-pass** cycle. The third loop shape, `keep-loop`, has no pass
token and no exhaustion; it has its own driver in the `keep-loop` subsection
below, and nothing else in §4c applies to it. `loop-with-regression` is the one whose body is not
a procedure — you run the check yourself; see its subsection below. They reuse the
spine's walker and dispatch table — the loop driver is what descends into a loop
step's `children[]`. This is the interactive mirror of the autonomous harness's
loop dispatch (`internal/djinn/session/run_formula.go` plus `checks.ReviewApproved`
/ `checks.RegressionClean`): **the harness runs an in-process Go check that parses
the body's sidecar and returns pass/spawn/halt; this skill has no in-process check
(it is prose, not the Go binary), so it reads the SAME on-disk verdict artifact the
body skill already wrote.** Reading the artifact — not reimplementing the Go parser
— is the interactive equivalent.

#### The loop algorithm (identical across the loop shapes)

For a loop step, first read its control fields from the `formula describe --json`
output:

- **`loop.max_attempts`** — the attempt budget (e.g. `review-loop` = 3,
  `regression-loop` = 2). The cycle runs the body→verdict→branch sequence at most
  this many times.
- **`loop.check_function`** — `djinn.check.review_approved` /
  `djinn.check.regression_clean`. This is the
  cross-check that confirms which shape you are in; it is **not** something you
  run — it names the autonomous check whose verdict logic you mirror by reading
  the artifact.

Then identify the body children from the step's `children[]` array (the describe
JSON does **not** emit the check's `review_step` / `fix_threshold`
args, so derive them from the children themselves):

- **body child** — the `single-agent-step` child whose `id` does NOT start with
  `fix-`: `review`. Its `id` selects the handler (the body→handler map
  below). `loop-with-regression` has none.
- **`fix-*` child** — the `single-agent-step` child whose `id` starts with
  `fix-`: `fix-review`, or `fix-regression`.
- **Run the two in the order the `children[]` array declares them, honouring
  their `needs[]`** — do NOT assume body-then-fix. On a `loop-with-review` the
  order is INVERTED and the inversion is the point (djinn-np4m4.3): the fix
  child runs FIRST, acting on the PRIOR attempt's review sidecar (so it does
  nothing on attempt 1), and the review child runs LAST, so the attempt ends
  with the artifact you read the verdict from. Reading a review that a fix has
  since edited out from under is what deferred djinn-iang4.3.
- **`mutation-check` child** (`loop-with-review` only) — a `go-mode-step`
  child; skip it (§4a).
- **`file-nits-*` child** (`loop-with-review` only, `file-nits`) — a
  `go-mode-step` child; the per-iter NIT-filing dispatch. The reviewer is a bare
  single-lens subagent that files nothing, so **you, the orchestrator, own this
  child's work**. After reading the reviewer's sidecar,
    for each promoted finding: file every **at-or-above-floor `kind=fix` NIT** as
    a `review-follow-up` bead labelled `proposed` (it has not passed
    `/create-bead`, so drains skip it until the lead authors it), and append every **`kind=counter`** finding to the
    loop's bead as a `COUNTER: <file:line> <action>` note. Skipping this is a
    silent parity loss against the autonomous run, which performs both
    mechanically (`internal/djinn/beads/filer.go` — `FileNitFollowUps` and
    `AppendCounterNote` / `CounterNotePrefix`).

    Honor the same three filters the autonomous filer applies: the **priority
    floor** (default **P2**, operator ruling D1 — a finding whose graded priority is
    numerically greater than the floor, e.g. a **P3** or **P4**, or one left
    ungraded (it falls back to the batch default P3), stays in the sidecar for
    human reading and does **not** become a bead; only a **P2**-graded NIT files), **kind** (never file a `counter`; never file
    `BLOCKER`/`IMPORTANT` — those route to `fix-review`), and **structural dedup**
    (same `file:line` under the same parent → skip, because the loop may re-run
    the body on a later attempt). File with these commands:

    ```bash
    # Dedup pre-check: what is already filed against this bead?
    bd list --label=review-follow-up --status=open,closed --json \
      | jq -r --arg b "<bead_id>" '.[] | select(any(.dependencies[]?; .type == "discovered-from" and .depends_on_id == $b)) | .title'

    # File one at-or-above-floor kind=fix NIT at its graded priority.
    bd create --type=task --priority="<finding's priority grade, e.g. P2>" --labels=review-follow-up,proposed \
      --title="<action>" \
      --description="$(printf '**Finding:** %s\n**Location:** %s' "<action>" "<file:line>")"
    bd dep add <new-bead-id> <bead_id> --type=discovered-from

    # Append one kind=counter finding as a note on the loop's bead.
    bd update <bead_id> --notes="COUNTER: <file:line> <action>"
    ```

Now run the cycle. For attempt `N` from 1 to `max_attempts`:

1. **Dispatch the body child** to its handler (per the map below). On `N == 1` the
   `fix-*` child is a no-op (no prior feedback exists yet); on `N > 1` you have
   already dispatched the fix below before re-entering here.
2. **Read the body's on-disk verdict artifact** at
   `.djinn/state/<bead-id>/<loop-id>.iter-<N>/<body-step-id>` (the dir-per-iter
   canonical layout — see §4d). Extract the verdict token (the body→verdict map
   below names WHERE in the artifact and the pass token).
3. **Branch on the verdict** — this is the pass-vs-spawn-next decision, mirroring
   `formula.Decide` (`internal/djinn/formula/dispatch.go`):
   - **structural-fault halt** (evaluated **first**, mirroring `formula.Decide`'s
     `HaltExitCode`/`ActionHalt` check ahead of the budget and pass checks) → if the
     body artifact carries a structural-fault signal on a `loop-with-review` step
     (BLOCKER-count mismatch or invented-rate trip — see §4c), **HALT the walk
     immediately regardless of `N`/remaining budget**: never route it through
     spawn-next or terminal-iter/exhaustion. The detection logic and thresholds live
     in §4c and `review_approved.go` — do not restate them here.
   - **pass** (verdict == the shape's pass token) → the loop is satisfied; **exit
     the loop** and advance the spine to the next step. **Interactive checkpoint
     (Step 1b #2):** in `--interactive` mode, pause here and surface the pass verdict
     + body artifact path before advancing; autonomous mode advances without pausing.
   - **spawn-next** (verdict is a non-pass, non-halt value AND `N < max_attempts`)
     → dispatch the paired `fix-*` child via `djinn-fixer` (passing
     the body's findings + the `fix_threshold`, see §4c severity filter), then
     increment `N` and loop back to step 1 to re-run the body and re-read its
     verdict.
   - **terminal-iter (`N == max_attempts` AND still non-pass)** → first apply the
     shape's rescue rule; if the iter is **not** rescued, the loop is **exhausted**
     and you escalate via its `on_exhausted` mode (the **§4c exhaustion matrix**
     immediately below). The rescue rule is shape-dependent, mirroring the
     autonomous checks exactly:
     - **`loop-with-review` has a terminal-iter rescue.** When the final iter's
       verdict is `SHIP-WITH-FIXES` with **no BLOCKER**, the loop **passes**: file
       the remaining actionable IMPORTANT/NIT findings as `review-follow-up` beads
       (**you** file them, per the `file-nits-*` child rule above) and advance
       the spine — do **not** escalate. This mirrors `tryTerminalIterRescue`
       (`internal/djinn/formula/checks/review_approved.go:308-373`), which files
       the residue and returns exit 0. A BLOCKER on the final iter is **not**
       rescued (the "BLOCKER means do not ship" gate fires before the rescue), nor
       is a `REWORK` verdict — both fall through to the exhaustion matrix below.
     - **`loop-with-regression` has NO rescue.** A tree still broken on the final
       iter falls through to the exhaustion matrix.

A verdict the body emits that is none of the recognized tokens (a malformed or
missing artifact) is treated as a non-pass for the branch above — iterate if
budget remains, and on the final iter apply the rescue rule (`loop-with-review`)
or fall through to the exhaustion matrix — never silently passed.

#### §4c — the `on_exhausted` escalation matrix (terminal-iter exhaustion)

When a loop reaches `max_attempts` without a pass verdict **and** without a review
rescue, it is **exhausted**. The orchestrator does not just stop — it applies the
loop's configured `on_exhausted` escalation, the interactive mirror of the
autonomous harness's `on_exhausted` dispatch (the runtime routing is `runOneFormula` in
`internal/djinn/session/run_formula.go`, with the per-class reason stamped by
`classifyIterOutcome` in `internal/djinn/session/iter_beads.go`).

**Read `on_exhausted` from the loop step's `formula describe --json` block** — the
`loop.on_exhausted` field that `djinn formula describe` emits per loop step (e.g.
`regression-loop` → `leave_and_reopen`, the review loops → `soft_fail`). **Do NOT parse
the TOML** — `describe` JSON is the orchestrator's only formula source, exactly as
for `max_attempts` and `check_function`. When `on_exhausted` is **absent** from the
describe block, **default to `soft_fail`** — this mirrors
`CheckSpec.OnExhaustedOrDefault` (`internal/djinn/formula/spec.go`); `describe`
populates the field for every loop today, but the absent→`soft_fail` default keeps
the branch correct against an older describe payload.

Before the loop's first attempt, capture `<pre_iter_sha>` once (e.g.
`git -C "<worktree>" rev-parse HEAD`) — the commit HEAD pointed at on loop entry. Both
`leave_and_reopen` (which reports the retained `<pre_iter_sha>..HEAD` range) and
`hard_reset` (which resets to it) need it. Then branch on the `on_exhausted` value:

| `on_exhausted` | Working tree | bd state | Used by |
|---|---|---|---|
| **`soft_fail`** | **working tree unchanged** — no `git reset`, no commit rollback | **stays `in_progress`** | review loops (`review-loop`) and the `on_exhausted`-absent default |
| **`leave_and_reopen`** | **iteration commits retained** on disk — no `git reset`; commit any dirty tree as WIP first | **reopen** via `bd update <bead-id> --status=open` | `regression-loop` |
| **`hard_reset`** | **`git reset --hard <pre_iter_sha>`** in `<worktree>` — discard the iteration's commits | **reopen** via `bd update <bead-id> --status=open` | supported, currently **unused** by `core/iter.formula.toml` |

For every branch, **HALT** the walk and emit an escalation message naming the
failing loop, the last verdict read, the artifact path, and (for
`leave_and_reopen` / `hard_reset`) the bead reopen plus the
`<pre_iter_sha>..HEAD` commit range. The three branches differ only in what they do
to the working tree and the bead, never in whether they stop — exhaustion is always
a hard stop, never a silent pass.

**Interactive checkpoint (Step 1b #4):** in `--interactive` mode, pause at the
`on_exhausted` escalation BEFORE applying the disposition — surface the failing
loop, its last verdict, and the configured `on_exhausted` mode so the operator can
intervene (inspect, retry by hand, or accept the escalation) before the working-tree
/ bead disposition is applied. Autonomous mode applies the disposition and HALTs
without pausing.

- **`soft_fail`** (review loops' default, and the absent-field default): leave the
  working tree unchanged and the bead `in_progress`; the operator resumes the same
  bead. This is the current `review-loop` semantics.
- **`leave_and_reopen`** (the `regression-loop` mode): commit any dirty tree as
  WIP, retain the iteration commits, and `bd update <bead-id> --status=open` so the
  queue re-offers the bead with its partial work intact — the same
  work-preserving disposition the commit-push regression gate applies to a broken
  tree.
- **`hard_reset`** (supported, unused today): `git reset --hard <pre_iter_sha>`
  in `<worktree>` to discard the iteration's commits, then
  `bd update <bead-id> --status=open`.

#### §4c — structural-fault halt (`ActionHalt`), distinct from budget exhaustion

The `on_exhausted` matrix above is the **budget-exhaustion** path: it fires only at
**terminal-iter exhaustion** (`N == max_attempts` with no pass and no rescue). The
autonomous dispatcher has a **fourth, earlier action that is a different failure
class** — **`ActionHalt`** — and the orchestrator must recognize it as **distinct**,
never fold it into the exhaustion matrix.

When a review loop's check detects a **structural fault** — a **BLOCKER-count
mismatch** or an **invented-rate threshold** trip — `formula.Decide` returns
`ActionHalt` (`internal/djinn/formula/dispatch.go`, `HaltExitCode = 3`), and it does
so **before** the budget check (`Decide` evaluates `HaltExitCode` ahead of
`latest.Number >= c.MaxAttempts`). A structural fault therefore **halts the loop
immediately regardless of remaining `max_attempts` budget** — a fault on iter 1 of a
5-attempt loop stops at **iter 1, not iter 5**. There is no rescue, no spawn-next,
and no `on_exhausted` disposition: it is a hard stop the moment you read it, and it
is **categorically different** from reaching `max_attempts` (the exhaustion matrix
above never runs for a structural fault, because the halt fires mid-budget).

**Where you read the structural-fault signal.** You read it from the **same on-disk
artifact** the loop's verdict token comes from (the §4c "Body → verdict map" below):
for a `loop-with-review` step, the reviewer's fenced ```json``` triage sidecar. This mirrors **`checks.ReviewApproved`'s exit-3 branch**
(`internal/djinn/formula/checks/review_approved.go`) — do **not** re-derive its
thresholds in prose; read the same two signals off that sidecar:

- a **BLOCKER-count mismatch** between the sidecar's `severity_counts` BLOCKER bucket
  and the count of `promoted_findings[]` entries with `severity=BLOCKER` (the
  `ValidateBlockerCount` invariant); and
- an **invented-rate threshold** trip, read off the sidecar's `invented_rate_pct`
  field: the halt fires when `invented_rate_pct` **exceeds 30** (percent) **and** the
  total finding count (sum of every `severity_counts` bucket) **is at least 10** — the
  floor below which a high rate is small-sample noise and stays advisory. Those two
  constants (`inventedRateHaltThreshold = 30.0`, `inventedRateFloorCount = 10`) are
  pinned in `review_approved.go` as the source of truth — mirror that read-condition
  here, do not re-judge or re-derive its logic.

Only the **review loops** (`loop-with-review`) carry this halt; a
`loop-with-regression` non-pass always routes through the verdict map and, at
terminal iter, the `on_exhausted` matrix above.

When you read either structural-fault signal, **HALT the walk immediately** —
regardless of attempts remaining — and escalate to the operator: name the failing
loop, the structural fault detected (BLOCKER-count mismatch or invented-rate
threshold), and the artifact path. Do **not** apply an `on_exhausted` disposition
(that is the budget-exhaustion class, a different failure), and never treat a
structural fault as a silent pass. In `--interactive` mode this surfaces at the same
`on_exhausted` escalation checkpoint (Step 1b #4), **labeled as a structural-fault
halt rather than a budget exhaustion** so the operator knows the loop stopped
mid-budget, not at its attempt ceiling.

#### Body → handler map

| Shape | body child `id` | Handler | Invokes |
|---|---|---|---|
| `loop-with-review` | `review` | ONE context-isolated single-lens reviewer subagent — **NOT** a multi-agent panel | the `/review` skill, run by that ONE subagent — the same body the autonomous harness dispatches |
| `loop-with-regression` | **none** — the loop has no procedure child | YOU, in this context: run the project's full-tree test command and its lint command | the tree itself, not a skill |
| (all, on spawn-next) | `fix-*` | `djinn-fixer` subagent | applies findings ≥ threshold |

**`review` — the single-lens body child.** The default formula
(`core/iter.formula.toml`), and so every variant that extends it, declares
`review-loop`, whose body child is `review` — the only review
binding a formula can declare. Dispatch it as **ONE** context-isolated
subagent under the §4b worktree dispatch preamble, running the **`/review` skill**:

<!-- djinn-dispatch: claude-code -->
have that subagent invoke the `/review` skill via the Skill tool.
<!-- djinn-dispatch: end -->

The subagent MUST NOT invoke any other review skill or command, or
any further subagent —
fanning out would silently reinstate the ~12-agent cost the single-lens chain
exists to cut, which is the whole point of the step. (The skill body carries
that prohibition itself, so honoring it is not a matter of remembering this
paragraph.)

**Do not restate the rubric in the dispatch prompt.** `/review` is a
registered procedure (`internal/djinn/runtime/procbind.go`), which means its
rubric — the nine-concern sweep, the severity grading, the sidecar field
contract — lives in exactly ONE generated body
(`core/skills/review/SKILL.body.md`) that the autonomous
`BuildReviewPrompt` and this handler both dispatch. Paraphrasing it here
is what let this section drift from the Go filer before (djinn-itudg). Pass the
subagent the review dispatch prompt below; let the skill supply the rubric.

#### Review dispatch prompt

The reviewer reads no tracker: it may run in a sandbox with no beads database.
You read the bead for it — `bd show <bead-id> --long --json` (`--long` carries
`notes`) — and fill this block, each field copied verbatim, an empty field left
as an empty section. Every review dispatch carries it whole, after the §4b
preamble: the Claude subagent's prompt, and step 3 of a §4c procedure prompt.

```text
Review bead <bead-id> with the review procedure. Judge the bead from the block
below, copied verbatim from the tracker; do not query the tracker yourself.

<bead>
ID: <bead-id>
Title: <title>

Description:
<description>

Acceptance Criteria:
<acceptance_criteria>

Design:
<design>

Notes:
<notes>
</bead>

The diff under review is `git diff <base>..HEAD`, run in <worktree>. Every
promoted fix finding must cite a file:line inside it.
```

On a re-review, append the two prior-iteration blocks the review skill names
("What iteration N-1 found", "What the last fix changed") after it.

The skill emits a fenced ```json``` sidecar as the LAST thing in the subagent's
visible response text — `verdict` (`SHIP` / `SHIP-WITH-FIXES` / `REWORK`),
`severity_counts`, `invented_rate_pct`, `promoted_findings[]`, and `criteria[]`
(one `{index, status, file, line}` row per acceptance line — the record §4a
`commit-push` transcribes as the bead's evidence). A sidecar missing a
`criteria[]` row is a review-invalid result: re-run the reviewer, do not spawn
the fixer.

**Pass per-step model knobs through on EVERY dispatch, not just review.**
Read `model` (and `effort`) off `djinn formula describe <path> --json` for each
step you dispatch — body children AND `fix-*` children alike — and pass any
declared knob through on the dispatch.

<!-- djinn-dispatch: claude-code -->
A per-call model argument
overrides an agent's frontmatter pin (verified 2026-07-28), so this is the
interactive realization of the formula's per-step knob. Two consequences:

- A knob on a `fix-*` child (e.g. `fix-review`) overrides the fixer agent's
  frontmatter default for that dispatch only.
- **A step whose resolved `adapter` is `codex` RUNS ON CODEX — shell out to
  `codex exec`; do NOT substitute an Opus subagent.**
<!-- djinn-dispatch: end -->

- **The codex-exec rule.** When
  `djinn formula describe <path> --json` reports `adapter: "codex"` for a step,
  that step's declared `model` and `effort` are codex-family values and ARE
  passed through — on the `codex exec` argv. Read both off that
  same `formula describe --json` record for that step (top-level steps at
  `.steps[]`, loop-body children at `.steps[].children[]`) and **never hardcode
  them**. In the shipped default formula `review` resolves to
  `model: "gpt-6-astra"`, `effort: "high"` even though its TOML step carries
  no `model =` line, because the codex model is pinned once at the adapter —
  which is exactly why the values must be read, not typed.

  This rule covers **every** step whose resolved adapter is `codex`, not only
  review steps — **`implement` included** — and every step shape `/wish`
  dispatches: top-level single-agent steps, every loop-body child
  (`review`) and every `fix-*` child.

  **Two prompt shapes — only ONE of them embeds a rubric.** Before building
  anything, decide which shape the step is:

  - A **procedure step** is one registered in `procedureRubricPaths`
    (`internal/djinn/runtime/procbind.go`) — the only one is `review`. Its
    prompt embeds a rendered rubric body, and **that body's path is the
    registry's value, NOT the step id**:

    | procedure | embedded rubric body (`djinn skills print <skill>` prints it) |
    |---|---|
    | `review` | `skills/review/SKILL.md` |

    `procbind.go` is canonical — read the mapping there if it and this table
    ever disagree. Resolve the path from the step's `prompt` binding, not its
    `id`: a renamed step id points at a directory that does not exist, and
    hands codex an empty rubric with no error.

  - A **write step** — `implement`, crucible's `try`, and every `fix-*` child — is **NOT** a
    registered procedure and has **no** generated skill in the binary's
    embedded rubric tree. There is no rubric to embed, and a run that
    tries to embed one feeds codex an empty `PROCEDURE RUBRIC` block. Its
    prompt is the one the Go builder for that step renders, all in
    `internal/djinn/session/`: `buildImplementPrompt` for `implement`,
    `buildTryPrompt` for `try`, `BuildFixPrompt` for `fix-review`,
    `BuildFixRegressionPrompt` for `fix-regression`.
    Build the prompt in **that** builder's shape, with **no `BEGIN` / `END
    PROCEDURE RUBRIC` markers at all**. There are two shapes, not one, and a
    `fix-*` step built in `implement`'s shape carries **no findings** — the
    fixer then has nothing to act on:

    - **`implement`** (`buildImplementPrompt`, `internal/djinn/session/formula_prompts.go`)
      — the header line, the bead block, `## Step contract` with its
      instruction list, the `## Already done at HEAD` exit on a first claim,
      then the `## What this step does NOT do` list. No payload: the bead IS the input.
      A codex `implement` that answers already-done leaves its verdict in
      `.djinn/last-status.json`; read it there and take §4b's close-and-skip.
    - **`try`** (`buildTryPrompt`, `internal/djinn/session/formula_prompts.go`)
      — `implement`'s shape without the already-done exit, plus `## The number` (the bead's
      `crucible` measure, direction, baseline and best) and `## Tries so far`
      (the rows of the bead's `.djinn/crucible/<bead-id>.tsv` record) ahead of `## Step contract`.
    - **Every `fix-*`** — the builder's own header/contract framing wrapped
      around the **verbatim prior-step payload**. The payload is the substance
      of a fix prompt; a fix prompt without it does nothing. Fence it as the
      builder fences it, and reproduce it byte-for-byte rather than
      paraphrasing:

      | step | builder | shape |
      |---|---|---|
      | `fix-review` | `buildReviewFixPrompt`, `internal/djinn/session/fix_prompt.go` | `## Fix step <id>` + the INVENTED/counter guardrail clause + the slow-tier clause + `## Findings to apply` + a numbered list, one line per finding: ``N. [SEVERITY] `<file>:<line>` — <action>``. **No bead block, no `## Step contract`, no does-NOT-do list.** The findings are the ratcheted actionable set for this iter (this review's findings minus the latent IMPORTANTs filed as follow-ups, plus the unresolved carry from earlier passes) — for a codex run, the findings you extracted from the codex sidecar. |
      | `fix-regression` | `BuildFixRegressionPrompt`, `internal/djinn/session/formula_prompts.go` | Header + `## Step contract` + a numbered list whose item 1 embeds the gate's full-tree `go test -short ./...` + lint output **verbatim**, fenced with a backtick run **longer than the longest run inside the payload** (`markdownFenceFor`) — a fixed ``` fence is closed early by a transcript that quotes backticks, and the fixer then reads the step contract as test output. |

    The argv, sandbox and artifact rules on this page apply to a write step
    unchanged. **Verdict extraction and the sidecar check do NOT** — a write
    step emits no structured artifact at all (§4d), so there is no sidecar to
    extract and nothing to parse. Its success signal is the ordinary one: a
    zero exit with the work committed. Running the sidecar check against a
    write step would fail it on every successful run and take the
    fallback every time, doing the work twice — and `implement` is the one
    step this rule fires on under the default formula, so that failure would
    be the common case, not the corner case. Its ONLY fallback trigger is
    trigger 1 below, the run failing.

  **The argv.** Run it from `<worktree>` with the prompt fed on **stdin** —
  the bare trailing `-` is what makes `codex exec` read its instructions from
  stdin:

  ```bash
  # Codex scratch for this step. It sits DELIBERATELY OUTSIDE the
  # verdict-artifact namespace the §4b resume scan reads (`.djinn/state/<bead-id>/`
  # itself and the `<loop-id>.iter-<N>/` iter dirs): a crash mid-`implement` must
  # not leave a `.codex` entry beside the artifacts where a resumer reads it as
  # "implement already ran" and skips implementation. It also keeps the multi-MB
  # rollout.jsonl out of the dirs the loop driver scans.
  #   .djinn/state/<bead-id>/codex/<step-id>.iter-<N>/   (loop body child)
  #   .djinn/state/<bead-id>/codex/<step-id>/            (top-level step)
  OUT=".djinn/state/<bead-id>/codex/<step-id>.iter-<N>"
  mkdir -p "$OUT"

  # PROCEDURE STEPS ONLY. Rubric body = the GENERATED skill named by the
  # procbind.go registry above — NOT <step-id> — YAML frontmatter stripped.
  # A WRITE step (implement / fix-*) SKIPS this line entirely.
  # <registry skill> is the directory in the registry path (review).
  djinn skills print "<registry skill>" \
    | awk 'BEGIN{n=0} /^---$/{n++; next} n>=2{print}' > "$OUT/rubric.md"

  # ...build "$OUT/prompt.txt" in the shape for this step (below)...

  codex exec --skip-git-repo-check -C "<worktree>" \
    -m "<resolved model>" -c model_reasoning_effort="<resolved effort>" \
    -s read-only --json -o "$OUT/last-message.md" - \
    < "$OUT/prompt.txt" > "$OUT/rollout.jsonl" 2> "$OUT/stderr.log"
  ```

  **`-s read-only` is for procedure/review steps only.** A read-only step
  (`review`) passes `-s read-only`,
  matching the "Do not modify code" clause its prompt closes with. A **WRITE**
  step — `implement`, and any `fix-*` resolved to the codex adapter — **omits
  `-s read-only` entirely** and inherits whatever sandbox the operator's
  `~/.codex/config.toml` sets. Passing `-s read-only` to a write step would
  leave it unable to do the one thing it exists to do.

  **The prompt — procedure steps.** Build it on disk in the exact shape
  `buildCodexProcedurePrompt` builds
  (`internal/djinn/session/formula_prompts.go`) — that builder is the canonical
  shape, and mirroring it is what makes an interactive codex step
  transcript-identical to an autonomous one. (A **write step** does not use this
  shape at all: it mirrors its own Go builder, named in the two-shapes list
  above, and skips steps 2 and 4 entirely.)

  1. Header, one line: ``You are running the `<step-id>` step of the Djinn harness for bead <bead-id>.``
  2. `## Step contract`, then the paragraph: "This adapter (codex) has no
     Skill/slash mechanism, so the procedure rubric is embedded below — follow
     it verbatim against bead `<bead-id>`."
  3. Any step-specific inputs the Claude dispatch would have passed. For a
     review body that is the filled review dispatch prompt (§4c), with the
     prior-iteration blocks on a re-review after a fix — never an instruction
     to read the bead: a read-only sandbox has no beads database.
  4. The literal marker line `----- BEGIN PROCEDURE RUBRIC -----`, then the
     rubric body (`$OUT/rubric.md`), then the literal marker line
     `----- END PROCEDURE RUBRIC -----`.
  5. The closing sentence: "Emit the procedure's required output (the fenced
     ```json``` sidecar or report.json the rubric specifies) as the LAST thing
     in your VISIBLE response text — not inside a tool-result, and not delegated
     to a subagent. Do not modify code. After the output is in your visible
     text, exit."

  A **write step**'s builder-rendered prompt carries no such clause and must not
  gain one — "Do not modify code" contradicts the one thing `implement` and
  `fix-*` exist to do. A write step closes on its builder's own terms: commit
  the change and exit.

  There is deliberately **no leading slash-command token** anywhere in the
  prompt: codex has no Skill/slash mechanism, so the embedded rubric IS the
  instruction.

  **Artifacts and the verdict.** The three files above are the run's evidence
  trail and stay in the step's `$OUT` scratch dir — never beside the verdict
  artifacts: `rollout.jsonl` (the `--json` JSONL
  event stream), `last-message.md` (written by `-o`), and `stderr.log`. Do
  **not** re-judge the run — extract the fenced ```json``` block that sits at
  the END of `last-message.md` and write it, unwrapped, to the path the loop
  driver already reads for that step,
  `.djinn/state/<bead-id>/<loop-id>.iter-<N>/<body-step-id>` (§4d — the body
  step `id`, **no `.json` suffix**):

  ````bash
  awk '/^```json$/ {buf=""; inb=1; next}
       inb && /^```$/ {last=buf; inb=0; next}
       inb {buf = buf $0 ORS}
       END {printf "%s", last}' "$OUT/last-message.md" \
    > ".djinn/state/<bead-id>/<loop-id>.iter-<N>/<body-step-id>"
  ````

  **Check the extracted output UNCONDITIONALLY — exit status is not the test.**
  A zero exit proves the CLI ran, not that it emitted what the loop reads. A
  trailing space on a fence line, an indented fence, or a rubric-shaped answer
  that closed without one all leave a **zero-length file at the loop-driver
  path on a ZERO exit**, and the loop then reads an empty verdict and reports
  no error.

  **Assert what the review procedure emits:** a fenced ```json``` sidecar that is
  non-empty, parses, and carries `verdict`.

  A failed assertion is a **fallback trigger** exactly as an auth failure is —
  do not leave the unusable file at the driver path; remove it and take the
  fallback below:

  ```bash
  SIDECAR=".djinn/state/<bead-id>/<loop-id>.iter-<N>/<body-step-id>"
  if ! jq -e '.verdict' "$SIDECAR" >/dev/null 2>&1; then
    rm -f "$SIDECAR"   # an empty/unparseable artifact must not reach the loop
    # → fallback (below), reason: "no parseable verdict artifact in last-message.md"
  fi
  ```

  With a checked sidecar at that path the loop driver branches on the codex verdict
  through the **same** §4c body→verdict table it uses for a Claude body — `SHIP`
  passes `review-loop`; `SHIP-WITH-FIXES` / `REWORK` spawns `fix-review`,
  which then acts on the codex findings *inside* the loop instead of leaving
  them to land as hand-commits outside it.

  **The notice line.** Print, before the dispatch, one line of exactly this
  form, naming what actually ran:

  ```
  notice: <step-id> declares adapter=codex; running it on codex exec (model=<resolved model>, effort=<resolved effort>)
  ```

  <!-- djinn-dispatch: claude-code -->
  **Opus is the FALLBACK ONLY.** Take it on either trigger, and only these two:
  <!-- djinn-dispatch: end -->

  1. **The run failed.** The binary is not on `PATH`, an auth or quota failure,
     a sandbox refusal, or any non-zero exit.
  2. **The run produced no usable artifact** — the unconditional check above
     did not find a non-empty, JSON-parseable file carrying `verdict`. This
     trigger stands **independently of exit status**: a zero-exit run whose
     fence the extractor missed fails it too, and silently passing that empty
     file to the loop is the failure mode this trigger exists to catch. It
     does **not** apply to a write step, which emits no artifact by design.

  <!-- djinn-dispatch: claude-code -->
  Only then dispatch the step as ONE context-isolated Agent subagent
  with `model: opus`, print this notice instead:

  ```
  notice: <step-id> declares adapter=codex; codex exec unavailable (<reason>) — fell back to an opus subagent
  ```

  and **record the fallback on the bead**, naming the step and the reason, so
  the substitution is not invisible in the run's record:

  ```bash
  bd update <bead-id> --append-notes="codex fallback: <step-id> ran as an opus subagent — <reason>"
  ```

  Use `--append-notes`, never `--notes` (which overwrites). The reason the
  fallback is a subagent rather than an in-context run: a step run in-context
  inherits the chair's model, which is the expensive model the adapter pin
  exists to avoid spending in the first place.
  <!-- djinn-dispatch: end -->

The shipped pins (operator ruling 2026-09-25, guarded by
`core/variant_formula_knobs_test.go` and
`TestDefaultReviewModel_CrossFamilyFromImplement`): Claude builds, Astra reviews.
`implement` = opus[1m] / high; `review` = codex / high with NO model line —
the codex model is pinned once at the adapter, and it is the step the codex-exec
rule above reads the resolved `gpt-6-astra` / `high` for, run READ-ONLY;
`fix-review` and `fix-regression` = opus[1m] / high, because a fixer edits code a
judge just condemned. Every single-agent step declares its adapter and `effort`.

#### Body → verdict map (WHERE each verdict is read + its pass token)

Each loop reads its verdict from the body artifact written under the iter dir.
Mirror the autonomous check's verdict logic exactly — do not re-judge:

| Shape | Verdict source (on disk) | Pass token | Non-pass → spawn fix |
|---|---|---|---|
| `loop-with-review` (`review-loop`) | the single-lens reviewer's own fenced ```json``` triage sidecar `verdict` field, captured to the iter dir as the body step's artifact (`<loop-id>.iter-<N>/review`) | **`SHIP`** | `SHIP-WITH-FIXES` / `REWORK` → `fix-review` |
| `loop-with-regression` | the exit status and output of the full-tree test + lint commands you ran — there is no artifact and no sidecar, because there is no body agent | **both commands exit 0** | any non-zero exit → `fix-regression`, handed the captured output verbatim |

These are the same pass tokens the autonomous check functions branch on
(`VerdictShip`, a clean regression run); reading the artifact the skill already
wrote is the interactive realization of the in-process parse.

#### `loop-with-regression` — you run the check, and reopening is the LAST resort

`loop-with-regression` (`regression-loop`: `max_attempts = 2`,
`check_function = djinn.check.regression_clean`, `on_exhausted = leave_and_reopen`)
sits between `review-loop` and `commit-push`. It exists because the full-tree
regression gate used to run ONLY inside the autonomous commit-push handler, where
its single disposition was rollback — a two-line lint finding could discard a
finished iteration with no agent ever reading it.

It inverts the usual loop shape, so do not pattern-match it onto its siblings:

- **There is no procedure body child and no verdict artifact.** The autonomous
  check runs the tree in-process. In this context YOU run it: the project's
  full-tree test command (this repo: `go test -short ./...`) and its configured
  lint command (`djinn.lint_argv` in `.djinn/config.json`), from the configured
  module root inside `<worktree>`. Run the SAME commands the gate runs — a looser check here passes a
  loop that the commit-push gate will still reject.
- **A green tree ends the loop in one attempt with no subagent at all.** Do not
  dispatch `fix-regression` on a clean run; its autonomous counterpart renders an
  empty prompt and is skipped.
- **A broken tree dispatches `fix-regression` to `djinn-fixer`**
  with the captured failure output **verbatim** in the prompt, plus the standing
  instruction that weakening or deleting a failing test — or silencing a lint
  rule — is not an acceptable fix. Then re-run both commands.
- **Exhaustion is the reopen**, not the first response: a tree still broken
  after the budget is the escalation the operator sees, and in autonomous mode it
  reopens the bead under the gate label `regression` with the iteration's commits
  retained — never reset.

#### `keep-loop` — keep or reset each round until a stop rule fires

`keep-loop` (`loop.check_function = djinn.check.keep_decision`) repeats its body
and, after each round, KEEPS the round's commits or RESETS the branch to the last
kept commit. It mirrors `formula.KeepRunner`
(`internal/djinn/formula/keep_runner.go`). It has no pass token, no fix child, no
rescue and no exhaustion: ignore `loop.on_exhausted` (describe prints the
default; a keep-loop never reads it).

1. Before round 1, record the last kept commit: `git -C "<worktree>" rev-parse HEAD`.
2. For round `N` from 1 to `loop.max_attempts`:
   1. **Dispatch the body children** in `children[]` declaration order, each per
      its shape (single-agent children per §4b).
   2. **A body child that fails** (the subagent errors, crashes or times out)
      makes the round a non-keep: reset it (step 4) and go to the next round
      without running the decide.
   3. **Run the decide.** The autonomous decide is the in-process check
      `djinn.check.keep_decision`; there is no command for it, so apply the
      rule the loop step's `description` states (for crucible: measure, and keep
      only a gain larger than the baseline spread). Its answer is two bits:
      **keep or reset**, and **stop or continue**.
   4. **Keep** → `git -C "<worktree>" rev-parse HEAD` is the new last kept commit.
      **Reset** → `git -C "<worktree>" reset --hard <last kept>` then
      `git -C "<worktree>" clean -fd`.
   5. **Stop**, or `N == max_attempts` → exit the loop and advance the spine to the
      next step. Reaching `max_attempts` is a normal end, never an escalation.

The branch is the loop's record: kept rounds' commits stay on it, reset rounds
leave nothing.

#### The `fix-*` severity filter (spawn-next iterations)

When a loop spawns the paired `fix-*` child, dispatch `djinn-fixer`
with the body's findings and a `fix_threshold` (default `important`). The fixer
applies the **same** severity filter the autonomous fix step uses (the `fix-*`
children in `core/iter.formula.toml`, `core/bead-work-contract.md` §5) — flip the
threshold in one place and both surfaces flip:

- **Act on findings at severity >= `fix_threshold` AND `kind=fix`.** With the
  default `important` threshold that is **BLOCKER + IMPORTANT** `fix`-kind
  findings (`nit` additionally admits NIT). Apply each one's `action` at its
  `file:line`.
- **ALWAYS skip COUNTER** — a `kind=counter` finding is an anti-recommendation;
  never apply it and never codify its rationale, at any threshold.
- **NEVER act on INVENTED** — a `severity=INVENTED` finding is a reviewer
  hallucination the triage layer already discarded; leave it untouched.

Findings below the threshold that are neither COUNTER nor INVENTED (e.g. a NIT
under the default `important` threshold) are deferred — never the fixer's job.
**You** file them, per the `file-nits-*` child rule in §4c. A model knob on the dispatched `fix-*` step reaches the
fixer per the knob pass-through rule above. The fixer owns one fix pass and returns a
summary, and the loop driver re-dispatches the body to re-check.

### §4d — loop-driver artifact parity (autonomous-equivalent on-disk layout)

The loop driver writes the **same** on-disk artifacts an autonomous formula run
writes, so an interactive run is schema-equivalent to an autonomous one
(`core/SHAPE-REGISTRY.md` "Canonical output path", reconciled by `djinn-ipee.14`):

- **Per-iter loop-body state** → `.djinn/state/<bead>/<loop-id>.iter-<N>/<step-id>`.
  The iter is a **directory** segment named `<loop-id>.iter-<N>` (e.g.
  `review-loop.iter-2`); the file inside is named by the body step `id` with
  **no `.json` suffix** (the `defaultStepFiles` convention). The `fix-*` child
  writes **no** structured artifact — its output is the working-tree mutation.
  There is no `<session>` segment in the path — provenance lives in fields inside
  the artifacts, not as path segments.
- **Evidence** → a JSON bd comment (`schema: "djinn-evidence"`, `run`, `iter`) on the bead (`bd comments add <bead> -f <tmpfile>`,
  read back with `bd comments <bead> --json`), one `criteria` entry per
  `acceptance_criteria` segment, per `core/bead-work-contract.md` §6. It is a
  comment, not a file, so it outlives the run's worktree (djinn-mr56i).
  `djinn wish land` writes it at `commit-push`, transcribed from the final review
  sidecar (§4a) — the same point, source and mapping the autonomous harness uses.

Writing the body verdict to `.djinn/state/<bead>/<loop-id>.iter-<N>/<step-id>` is
what lets the loop driver re-read "the latest body output" the same way the
autonomous `LatestStepOutput` does — the read path and the write path share one
layout across both modes.

## Step 5 — Walk to completion

Dispatch each step in topological order until `commit-push` has run (the terminal
go-mode bookend) or a stub/checkpoint pauses the walk for the operator. The
orchestrator owns the order and the dispatch; each subagent owns one step's work
and reports back. This is the interactive realization of the autonomous formula
run — same steps, same order, same shape→handler contract, with per-step context
isolation supplied by subagent dispatch instead of subprocess spawning.

---

## Why this exists

The autonomous harness (`internal/djinn/session/run_formula.go`) and this
orchestrator implement the **same** `core/SHAPE-REGISTRY.md` contract from two
sides — the registry's three-point contract names both. Driving the formula from
its `formula describe --json` description, rather than re-encoding the steps here,
is what guarantees the interactive and autonomous paths cannot disagree about
which steps run or in what order: edit the formula, and both walkers follow.

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.