wish
Skill4 starsChanged 7 days agoMITView on GitHub
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 (`design`, `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.
- Deletes or force-pushes
- Commits and pushes
4 other projects use a version of this.
Adopted by
Most recently taken up by
- automagik-si/genietheir own version, first seen Nov 15, 2026
- bo-516/NiceDesigntheir own version, first seen Oct 20, 2026
- namastexlabs/automagik-genietheir own version, first seen Oct 8, 2026
- automagik-dev/genietheir own version, first seen Apr 2, 2026
Other versions4 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 4
- automagik-dev/genie★ 353same name
- automagik-si/genie★ 353same name
- namastexlabs/automagik-genie★ 351same name
- bo-516/NiceDesign★ 1same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository14 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- ac-quality-reviewSkill
- beadsSkill
- consult-councilSkill
- create-beadSkill
- freshness-triageSkill
- playwright-cliSkill
- reaperSkill
- reviewSkill
- rubSkill
- verify-acceptanceSkill
- create-beadSkill
- wishSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
33 rules. 0 are common, 8 are distinctive, and 25 are very specific to this project.
0
Common
Most of the 4 edited versions we read keep these.
8
Distinctive
This author's own choices. Worth a look.
25
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 11
Before anything else: run djinn skills check --here in the repo you are working in.
- Distinctive: line 77
Explicit second argument wins: If the operator passed a second positional argument, it overrides the label scan entirely.
- Distinctive: line 163
Only when the embedded tier ALSO reports no such formula do you stop and report the error — its diagnostic lists the variants this binary …
- Very specific: line 277
If the djinn binary is not on PATH, build it once (go build -o /tmp/djinn ./cmd/djinn) and invoke /tmp/djinn instead.
- Very specific: line 282
NOT hand-author the step list — the JSON from formula describe is the single source of truth so the orchestrator tracks formula edits …
- Very specific: line 295
Never run such a step on codex exec instead: that would silently replace the model the operator chose.
- Very specific: line 325
NOT re-run the walk and do NOT re-dispatch any step.
- Very specific: line 353
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 …
- Distinctive: line 465
Stop ONLY on disagreement: After printing the announce, compare the walked formula against Step 1a's shadow resolution.
- Distinctive: line 476
No explicit argument: When no explicit formula argument was passed, there is nothing to disagree with: the bead's label-or-default IS the …
- Distinctive: line 479
An explicit argument that agrees: When the explicit argument resolves to the SAME formula the bead's label-or-default would have, the …
- Distinctive: line 491
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 …
- Very specific: line 510
If the needs graph has a cycle (no step is selectable but steps remain), stop and report the malformed formula rather than guessing an …
- Very specific: line 608
Write the same text, from revision: down, to .djinn/state/<bead-id>/<loop-id>.iter-<N>/suite-run, and append the block to that attempt's …
- Very specific: line 644
Never force-remove a dirty worktree: Never run git worktree remove --force on a path whose git -C <path> status --porcelain is non-empty — …
- Very specific: line 654
Every step after claim runs with <worktree> as its working directory.
- Very specific: line 660
Run the whole finish as one command, from <repo-root> (never from <worktree>, which it removes):
- Very specific: line 667
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.
- Very specific: line 676
Commit whatever is left in <worktree>.
- Very specific: line 827
No dispatch names the shared checkout or its path as a place to work.
- Very specific: line 950
loop-with-reviewhas a terminal-iter rescue: When the final iter's verdict is SHIP-WITH-FIXES with no BLOCKER, the loop passes: file the … - Very specific: line 969
When a loop reaches max_attempts without a pass verdict and without a review rescue, it is exhausted.
- Very specific: line 986
Before the loop's first attempt, capture <pre_iter_sha> once (e.g.
- Very specific: line 1032
When a review loop's check detects a structural fault — a BLOCKER-count mismatch or an invented-rate threshold trip — formula.Decide …
- Very specific: line 1060
Only the review loops (loop-with-review) carry this halt.
- Distinctive: line 1064
When you read either structural-fault signal, HALT the walk immediately — regardless of attempts remaining — and escalate to the operator: …
- Very specific: line 1145
When the walk ran a design loop, append the approved design note after it under ## The approved design note, with reviewDesignRule's text …
- Very specific: line 1166
Read model (and effort) off djinn formula describe <path> --json for each step you dispatch — body children AND fix-* children alike — and …
- Very specific: line 1193
Run it from <worktree>, outside this session's sandbox (it needs the network, and bd its database).
- Very specific: line 1207
The codex-exec rule: When djinn formula describe <path> --json reports adapter: "codex" for a step, that step's declared model and effort …
- Distinctive: line 1223
Two prompt shapes — only ONE of them embeds a rubric: Before building anything, decide which shape the step is:
- Very specific: line 1288
The argv: Run it from <worktree> with the prompt fed on stdin — the bare trailing - is what makes codex exec read its instructions from …
- Very specific: line 1335
Never leave a write step's sandbox off the argv: codex then falls back to ~/.codex/config.toml, read-only on a fresh install, and even …
“Common” is measured: kept by at least 6 in 10 of the 4 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
<!-- 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 design single-agent step writes the bead's design note first, and the
implement step dispatches djinn-implementer unconditionally
once the design-review loop has approved it (§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 firstworktreeline ofgit 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 branchwish/<bead-id>. §4aclaimcreates 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:
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.tomlforces the all-codex variant regardless of labels. A bare variant name — anything else:
/wish <bead-id> codexis shorthand for the iter-codex variant. The name is the SHORT variant name (codex, the<name>initer-<name>.formula.tomland in theformula:<name>label) — NOT the full file stem (iter-codex), which would probe a doublediter-iter-codex.formula.tomland error.Resolve it by the
formula:<name>label rule below, in full — all THREE tiers, including the embedded one./wish <bead-id> codexand aformula:codexlabel 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 nocore/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).
- A formula path — the argument contains a path separator (
Otherwise, resolve from the bead's
formula:*label. Read the bead's labels and scan for theformula:namespace: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-diskcore/probe): if.djinn/iter.formula.tomlexists (the optional local override) use it; otherwise fall through tocore/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 toformula describe, the first two tiers resolve an on-disk PATH, so the concrete.djinn/-then-core/probe matchesresolvePerBeadFormula(internal/djinn/session/run_formula.go) rather thanResolvePayload's embed fallthrough. This is the common case, and in this repo it resolves tocore/iter.formula.toml. Probing.djinn/first thencore/is mandatory — naming the local override path unconditionally wouldformula describea 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-myttldjinn initno 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:djinn formula describe --embedded iter --jsonThis tier is LAST, never first: a
.djinn/iter.formula.tomloverride wins over it, and an on-diskcore/iter.formula.tomlwins over it, exactly as they do for the autonomous harness. Note the shape difference —--embeddedtakes 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 → theformula:<name>variant, resolved by the same override → canonical probe order as the harness'sresolvePerBeadFormula(internal/djinn/session/run_formula.go): probe.djinn/iter-<name>.formula.tomlfirst (the optional local override), and if it does not exist fall through tocore/iter-<name>.formula.toml— the canonical home where the variant formulas ship next to the embedded default. For exampleformula:codexresolves tocore/iter-codex.formula.tomlin 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 — mirroringLoadFormulaVariant'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 andformula:../etc/passwdmust not resolve outside the formula dirs (this mirrors the harness'sValidateFormulaNameguard). - 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).
<!-- djinn-dispatch: codex -->On Codex, the default arm resolves the codex variant. Where arm 3 would
take the default formula, resolve the bare variant name codex instead, by the
same three tiers (.djinn/iter-codex.formula.toml →
core/iter-codex.formula.toml → djinn formula describe --embedded iter-codex
--json), and record the arm as default. It is the formula a drain runs under
the codex provider profile: every step pins a codex model, where the default
formula builds on Claude, which a Codex session cannot start.
<!-- djinn-dispatch: end -->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):
- After
implementreturns — surface the implementer's summary, or itsalready-doneverdict and evidence before the bead is closed (§4b). (Fires at §4bimplement.) - After each loop reaches its pass verdict — the review loop
(
review-loop) and theregression-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.) - Before
commit-push— the full working-tree diff is inspectable. Surfacegit 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 §4acommit-push, before the commit.) - At any
on_exhaustedescalation — before the §4c exhaustion matrix HALTs, pause so the operator can inspect the failing loop's last verdict and choosecontinue(accept the escalation and apply its disposition) orabort. This ties into the W4.1 failure-handling branch — the operator gets a say before thesoft_fail/leave_and_reopen/hard_resetdisposition 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:
djinn formula describe <formula-path> --jsonThis emits a steps[] array. Each step object carries:
id— the step id (e.g.claim,implement,review-loop).type— one of the shapes incore/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—nullfor 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 intochildrenat 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.
<!-- djinn-dispatch: codex -->Check that every declared provider can start, before claiming. When any
step or loop-body child in the JSON resolves to adapter: "claude-code" (§4c's
claude-p rule runs it on claude -p), run command -v claude. When it prints
nothing, stop here — before claim touches the bead — and print:
stop: <step-id> declares adapter=claude-code, and this Codex session cannot start it: claude is not on PATH. Install Claude Code, run /wish from a Claude session, or pick a codex formula: /wish <bead-id> codexNever run such a step on codex exec instead: that would silently replace the
model the operator chose.
<!-- djinn-dispatch: end -->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:
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 → resumedAn 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 already-done check before design is skipped, 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 whenbd show <bead-id>reports the beadin_progressAND<worktree>exists on branchwish/<bead-id>(the claim's effects are the bd state change and the worktree, so they ARE the evidence). Otherwise resume atclaim; it is idempotent (§4a). loop-with-reviewstep → DONE when a<step>-iter-<N>.jsonwith 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 stepid, no.jsonsuffix — §4d), and treat the loop as DONE when the highest recordediter-<N>holds the pass token (SHIP, per the §4c body→verdict map). (The bead's<step>-iter-<N>.jsonframing names the per-iter verdict record; §4d is its canonical on-disk realization — see §4d, do not duplicate the layout here.)loop-with-regressionstep → 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-loopstep → 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>/. Theimplementstep's artifact present means authoring already ran; resume past it. (Thefix-*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
(internal/djinn/session/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.
- The resolved formula — the
<formula-path>Step 1a produced, or, at the embedded tier, thedescribe --embedded <name>invocation. - 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, ordefault. - Where the formula came from — exactly one provenance value. For a
resolution that went through Step 1a's probe sequence — the
defaultarm, theformula:<name> labelarm, 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/, orembedded. 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.tomlis a supported resolution that came from none of the three — so it has no tier to name: print the fourth literal provenance valuesupplied pathinstead. 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. - A per-step knob table — one row per step the walk will dispatch, with the
four columns step id,
adapter,modelandeffort: the knobs this run will ACTUALLY spend on. Include loop-body children andfix-*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 — — —
design codex gpt-6-sol high
design-review-loop — — —
revise-design codex gpt-6-sol high
design-review codex gpt-6-astra high
implement codex gpt-6-sol high
review-loop — — —The announce names design and design-review whenever the formula declares
them — the default and both variants do — so the operator sees, before any
spend, that the bead's design note is written and checked before code.
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 → design → design-review-loop → 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 — design → a design subagent; implement → djinn-implementer; fix-* → djinn-fixer (see §4b) | isolated subagent |
loop-with-design-review | loop driver — body via ONE design reviewer on the other model family, after a revise-design rewrite on an iteration that follows a REVISE; you post each note and verdict to the bead (see §4c loop-with-design-review) | driver + isolated body |
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: codex -->Starting a dispatch. Codex has no in-session agent call this walk can wait
on, so every dispatch is its own headless run: a fresh process with its own
context, the same isolation the autonomous harness gets. A step whose resolved
adapter is claude-code runs on claude -p by the claude-p rule in §4c;
every other step is a codex exec run built by the codex-exec rule in §4c (the
prompt on stdin, the run's files under $OUT, the step's resolved model /
effort on the argv). The implementer and fixer are
the Codex agents djinn init writes, .codex/agents/djinn-implementer.toml
and .codex/agents/djinn-fixer.toml; in a repo carrying no .codex/agents/,
they are the user-scope copies djinn skills install --global writes,
~/.codex/agents/<name>.toml. A write step's prompt opens with the line naming
its agent file, <agent file> being whichever of the two exists, the repo's
first:
Act as the <name> agent: read <agent file> and follow its developer_instructions as your own.codex exec --skip-git-repo-check -C "<worktree>" \
-m "<resolved model>" -c model_reasoning_effort="<resolved effort>" \
-s danger-full-access -c approval_policy=never \
--json -o "$OUT/last-message.md" - \
< "$OUT/prompt.txt" > "$OUT/rollout.jsonl" 2> "$OUT/stderr.log"The agent's summary is $OUT/last-message.md. That argv is a write step's;
its sandbox flags are §4c's write-step rule. A reviewer takes no agent file:
its prompt embeds the review rubric instead and its argv passes -s read-only
(§4c). codex exec needs the
network and bd its database, so run it outside this session's sandbox
(approve the escalation).
<!-- 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.
suite-run is the other go-mode review-loop child, and /wish does not
skip it: the reviewer may run in a sandbox where part of the suite cannot run,
so it reads the full-suite result from you. Before each review dispatch,
commit the worktree's tree, then run the module's full test command in this
orchestrator's own context, in <worktree> — go test -short ./... from the
Go module root, or the repo's djinn.test_argv when it sets one — and build
the ## Test suite block from that run:
## Test suite
The harness ran the module's full test command on this iteration's tree
before you, outside your sandbox. This is the full-suite result; cite it:
```
revision: <git rev-parse HEAD in <worktree>, run before the tests>
command: <the exact command>
exit: <its exit code>
failing tests: <count>
- <package> --- FAIL: <TestName> (one line per `--- FAIL:` line)
--- gate output (tail) ---
<the last 200 lines of its output>
```
Copy its revision and exit into the sidecar as
`"suite": {"revision": "<revision>", "exit": <exit>}`.Write the same text, from revision: down, to
.djinn/state/<bead-id>/<loop-id>.iter-<N>/suite-run, and append the block to
that attempt's review dispatch prompt. A failing suite is still a result: the
reviewer grades it. When the command cannot run at all (the binary is missing,
it hangs past its timeout), do not dispatch the reviewer; stop the loop and
report it. The sidecar's suite must match the block's revision and exit, as
the autonomous review_approved check requires: a sidecar whose suite is
missing or differs is a review-invalid result — re-run the reviewer, do not
spawn the fixer.
claim
Open (or reuse) the bead worktree, then claim the bead in-process:
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 --forceon a path whosegit -C <path> status --porcelainis 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>onwish/*stays clear of the summoner'ssummoner-<bead-id>worktrees onsummoner/work/*, which its startup reap removes. - The bead MUST be
in_progressbefore any implementation step runs (the cardinal beads rule). This mirrors the autonomousdjinn.bead_claimStepFunc.
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):
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:
- A refusal check: the latest
reviewsidecar under<worktree>/.djinn/state/<bead-id>/must approve the bead (SHIP, orSHIP-WITH-FIXESwhose every actionable IMPORTANT finding is fixed or filed as areview-follow-upbead discovered from<bead-id>),<repo-root>must be onmain, andmainmust still fast-forward towish/<bead-id>. - Commit whatever is left in
<worktree>. - The repo's test command inside
<worktree>:djinn.test_argvfrom.djinn/config.json, elsego test -short ./.... git -C "<repo-root>" merge --ff-only wish/<bead-id>.- The §6 evidence comment and the four
evidence_*labels, transcribed from that sidecar with the mapping the autonomousdjinn.commit_pushStepFunc uses (ReviewEvidence,internal/djinn/session/formula_evidence.go). git pushofmain,bd close <bead-id>, thenbd dolt commitandbd dolt push— a code-only push leaves the bead's closed status and labels invisible to collaborators (core/bead-work-contract.md§8).git worktree remove "<worktree>"(never--force), thengit 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_progressand the worktree is kept. Escalate. - Fast-forward refused (
mainmoved 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 atregression-loop, which re-tests the rebased tree, thencommit-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:
design→ ONE context-isolated design subagent under the worktree dispatch preamble (below), on the step's resolved adapter, model and effort (acodexstep is acodex execwrite-step run, §4c). It runs right after theclaimgo-mode bookend (§4a) — the step order is claim → design → design-review-loop → implement — so nothing is written before the bead is claimed, and no code before its design is approved.The already-done check comes first (fresh run only). On a fresh run (Step 2b, as for
implement's exit below), before any design subagent, dispatch ONE read-only subagent on thedesignstep's resolved adapter, model and effort with the promptbuildAlreadyDoneCheckPromptrenders (internal/djinn/session/design_step.go): the bead block and one question — does every acceptance criterion already hold at HEAD? It returnsalready-donewith thefile:lineor command output proving each criterion, ornot donewith the first unmet one. Onalready-done, takeimplement's close-and-skip below: no design, design review, implement or review subagent is ever dispatched, so an already-done bead pays for no design. Onnot done, or on a resumed run (no check), dispatch the design subagent.Build the design prompt in the shape
buildDesignPromptrenders (internal/djinn/session/design_step.go): the header line, the bead block,## Step contract(read the bead, the parent epic,DECISIONS.mdand the code; write no file and no commit; end the reply with the note), the five-section note contract, the parent epic's description (bd show <parent> --json),## Beads whose touches overlap(every open or in-progress bead whosemetadata.touchesshares a path with this bead's, by the summoner's overlap rule: equal paths, or a path under one that ends in/), and## What this step does NOT do.The subagent's final reply IS the design note. Save it verbatim to
.djinn/state/<bead-id>/design.iter-1/designand post it to the bead withbd comments add <bead-id> -f <tmpfile>, the body opening with the linedjinn design note — round 1 (design)— the same comment the drain posts. Then check the tree:git status --porcelainempty and HEAD unmoved sinceclaim; if the subagent wrote anything,git reset --hard <claim HEAD>andgit clean -fdoutside.djinn/— the drain's review tree guard does the same. A note or verdict thatbd comments addrefuses stops the walk beforeimplement, as a drain stops: fix the cause, post it, then continue.implement→ thedjinn-implementersubagent. This is the orchestrator's first code-writing step: it runs after thedesign-review-loophas approved the design note — so the bead is claimed and its design approved before any code is written. Append the approved note (the note in force when the design loop passed, §4cloop-with-design-review) to the dispatch under## The approved design note, with the ruleimplementDesignRulestates (internal/djinn/session/design_step.go): build to the note; the criteria win where they disagree; any departure carries aDesign departure: <what changed> — <why>line in the body of the commit that departs. Dispatch theimplementstep unconditionally todjinn-implementeras a subagent, passing the claimed<bead-id>under the worktree dispatch preamble (below). The implementer reads that bead'sacceptance_criteria/design/ notes and builds the complete feature to every criterion percore/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 frommain), add to the dispatch: "If every acceptance criterion already holds at HEAD, commit nothing and returnalready-donewith thefile:lineor command output proving each criterion." On a resumed run, omit it — the bead's own earlier commits would read as done. When the implementer returnsalready-done, take the close-and-skip disposition, the mirror of the autonomousErrFreshnessStaleroute (the 2026-09-26 lean-formula ruling):bd close <bead-id>with the implementer's evidence as the reason, skip every later step, runbd dolt commit -m "<bead-id>: already done" && bd dolt push, tear the worktree down with §4acommit-push's stage 7 commands, and end the walk. It is a successful finish — never anon_exhausteddisposition, never a reopen.Interactive checkpoint (Step 1b #1): in
--interactivemode, pause afterimplementreturns and surface its summary — or itsalready-doneevidence, before the close. An already-done check that returnsalready-donepauses the same way. 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-implementeris 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 percore/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.revise-design→ ONE design subagent likedesign's, dispatched by the §4cloop-with-design-reviewdriver, never at the top level.fix-*(fix-review,fix-regression) → dispatch todjinn-fixeras a subagent under the worktree dispatch preamble, passing the<bead-id>, the findings from the paired review (or the failing tree output forfix-regression), and afix_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: codex -->The Codex agent files pin no model: the step's resolved model and effort
ride the codex exec argv (the claude -p argv for a claude-code step),
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>:
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 three walked loop shapes (loop-with-design-review, loop-with-review,
loop-with-regression) each drive an iterate-until-pass cycle;
loop-with-design-review has its own subsection below. 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-stepchild whoseiddoes NOT start withfix-:review. Itsidselects the handler (the body→handler map below).loop-with-regressionhas none. fix-*child — thesingle-agent-stepchild whoseidstarts withfix-:fix-review, orfix-regression.- Run the two in the order the
children[]array declares them, honouring theirneeds[]— do NOT assume body-then-fix. On aloop-with-reviewthe 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-checkchild (loop-with-reviewonly) — ago-mode-stepchild; skip it (§4a).suite-runchild (loop-with-reviewonly) — ago-mode-stepchild; run it yourself before eachreviewdispatch and hand the reviewer the## Test suiteblock (§4a).file-nits-*child (loop-with-reviewonly,file-nits) — ago-mode-stepchild; 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-floorkind=fixNIT as areview-follow-upbead labelledproposed(it has not passed/create-bead, so drains skip it until the lead authors it), and append everykind=counterfinding to the loop's bead as aCOUNTER: <file:line> <action>note. Skipping this is a silent parity loss against the autonomous run, which performs both mechanically (internal/djinn/beads/filer.go—FileNitFollowUpsandAppendCounterNote/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 fileBLOCKER/IMPORTANT— those route tofix-review), and structural dedup (samefile:lineunder the same parent → skip, because the loop may re-run the body on a later attempt). File with these commands:# 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:
- Dispatch the body child to its handler (per the map below). On
N == 1thefix-*child is a no-op (no prior feedback exists yet); onN > 1you have already dispatched the fix below before re-entering here. - 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). - 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'sHaltExitCode/ActionHaltcheck ahead of the budget and pass checks) → if the body artifact carries a structural-fault signal on aloop-with-reviewstep (BLOCKER-count mismatch or invented-rate trip — see §4c), HALT the walk immediately regardless ofN/remaining budget: never route it through spawn-next or terminal-iter/exhaustion. The detection logic and thresholds live in §4c andinternal/djinn/formula/checks/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
--interactivemode, 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 pairedfix-*child viadjinn-fixer(passing the body's findings + thefix_threshold, see §4c severity filter), then incrementNand loop back to step 1 to re-run the body and re-read its verdict. - terminal-iter (
N == max_attemptsAND still non-pass) → first apply the shape's rescue rule; if the iter is not rescued, the loop is exhausted and you escalate via itson_exhaustedmode (the §4c exhaustion matrix immediately below). The rescue rule is shape-dependent, mirroring the autonomous checks exactly:loop-with-reviewhas a terminal-iter rescue. When the final iter's verdict isSHIP-WITH-FIXESwith no BLOCKER, the loop passes: file the remaining actionable IMPORTANT/NIT findings asreview-follow-upbeads (you file them, per thefile-nits-*child rule above) and advance the spine — do not escalate. This mirrorstryTerminalIterRescue(internal/djinn/formula/checks/review_approved.go), 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 aREWORKverdict — both fall through to the exhaustion matrix below.loop-with-regressionhas NO rescue. A tree still broken on the final iter falls through to the exhaustion matrix.
- structural-fault halt (evaluated first, mirroring
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, except design-review-loop, which defers with needs-operator (§4c loop-with-design-review) | review loops (design-review-loop, 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 beadin_progress; the operator resumes the same bead. This is the currentreview-loopsemantics. The one exception isdesign-review-loop: its exhaustion defers the bead (below).leave_and_reopen(theregression-loopmode): commit any dirty tree as WIP, retain the iteration commits, andbd update <bead-id> --status=openso 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, thenbd 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_countsBLOCKER bucket and the count ofpromoted_findings[]entries withseverity=BLOCKER(theValidateBlockerCountinvariant); and - an invented-rate threshold trip, read off the sidecar's
invented_rate_pctfield: the halt fires wheninvented_rate_pctexceeds 30 (percent) and the total finding count (sum of everyseverity_countsbucket) 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 ininternal/djinn/formula/checks/review_approved.goas 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-design-review | design-review (after revise-design) | ONE context-isolated design reviewer on the step's resolved adapter | the prompt buildDesignReviewPrompt renders (internal/djinn/session/design_step.go) — a Go builder, not a skill |
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: codex -->one codex exec run built as a §4c procedure step (a claude -p run by the
claude-p rule when review resolves to adapter: "claude-code"): its prompt
embeds the review rubric (djinn skills print review), and it runs
read-only.
<!-- 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.
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.When the walk ran a design loop, append the approved design note after it under
## The approved design note, with reviewDesignRule's text
(internal/djinn/session/design_step.go): the review checks the diff against
the note as well as the criteria, and a departure with no Design departure:
line in git log <base>..HEAD is a finding.
Append the attempt's ## Test suite block (§4a suite-run) after it, on
every attempt. On a re-review, also append the two prior-iteration blocks the
review skill names ("What iteration N-1 found", "What the last fix changed").
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[], suite
({revision, exit} from the ## Test suite block), 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, or whose suite is missing or disagrees with the
## Test suite block, 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: codex -->A step whose resolved adapter is codex runs through codex exec by the
codex-exec rule below. A step whose resolved adapter is claude-code (a
formula:claude label, or a mixed formula passed explicitly) RUNS ON CLAUDE —
shell out to claude -p; never run it on codex exec, and never drop its
knobs. The operator chose that provider; Step 2's claude check has already
stopped the walk when this machine cannot honor the choice.
The claude-p rule. Build the step exactly as the codex-exec rule below builds it — the same prompt shape, the same
$OUTdir, the same artifact, verdict and fallback rules — and change only the argv, to the one the harness's claude-code driver builds (internal/djinn/runtime/claudecode/profile.go), carrying that step's resolvedmodelandeffortoff the sameformula describe --jsonrecord:claude -p --dangerously-skip-permissions --output-format stream-json --verbose \ --model "<resolved model>" --effort "<resolved effort>" \ < "$OUT/prompt.txt" > "$OUT/stream.jsonl" 2> "$OUT/stderr.log" jq -rs 'map(select(.type == "result")) | last | .result // empty' \ "$OUT/stream.jsonl" > "$OUT/last-message.md"Run it from
<worktree>, outside this session's sandbox (it needs the network, andbdits database). A write step's prompt opens with the line naming its Claude agent file,<agent file>: the repo's.claude/agents/<name>.md, whichdjinn initwrites, or, in a repo carrying none,~/.claude/agents/<name>.md, whichdjinn skills install --globalwrites:Act as the <name> agent: read <agent file> and follow its instructions as your own.Print
notice: <step-id> declares adapter=claude-code; running it on claude -p (model=<resolved model>, effort=<resolved effort>)before the dispatch.
<!-- djinn-dispatch: end -->The codex-exec rule. When
djinn formula describe <path> --jsonreportsadapter: "codex"for a step, that step's declaredmodelandeffortare codex-family values and ARE passed through — on thecodex execargv. Read both off that sameformula describe --jsonrecord for that step (top-level steps at.steps[], loop-body children at.steps[].children[]) and never hardcode them. In the shipped default formulareviewresolves tomodel: "gpt-6-astra",effort: "high"even though its TOML step carries nomodel =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 —implementincluded — and every step shape/wishdispatches: top-level single-agent steps, every loop-body child (review) and everyfix-*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 isreview. 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)reviewskills/review/SKILL.mdinternal/djinn/runtime/procbind.gois canonical — read the mapping there if it and this table ever disagree. Resolve the path from the step'spromptbinding, not itsid: 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'stry, and everyfix-*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 emptyPROCEDURE RUBRICblock. Its prompt is the one the Go builder for that step renders, all ininternal/djinn/session/:buildImplementPromptforimplement,buildTryPromptfortry,BuildFixPromptforfix-review,BuildFixRegressionPromptforfix-regression. Build the prompt in that builder's shape, with noBEGIN/END PROCEDURE RUBRICmarkers at all. There are two shapes, not one, and afix-*step built inimplement'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 contractwith its instruction list, the## Already done at HEADexit on a first claim, then the## What this step does NOT dolist. No payload: the bead IS the input. A codeximplementthat answers already-done leaves its verdict in the handoff file the prompt names, under the git common dir (<git common dir>/djinn/last-status.json, or<git common dir>/djinn/worktrees/<name>/last-status.jsonin a linked worktree); 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'scruciblemeasure, direction, baseline and best) and## Tries so far(the rows of the bead's.djinn/crucible/<bead-id>.tsvrecord) 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-reviewbuildReviewFixPrompt,internal/djinn/session/fix_prompt.go## Fix step <id>+ abead_id: <bead-id>line + afix_threshold: <threshold>line + the INVENTED/counter guardrail clause + the slow-tier clause +## Findings to apply(the fixer'sfindingsinput) + a numbered list, one line per finding:N. [SEVERITY] `<file>:<line>` — <action>. The two input lines are the djinn-fixer's own inputs, which the Go builder omits; withoutbead_idthe fixer stops and changes nothing. 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-regressionBuildFixRegressionPrompt,internal/djinn/session/formula_prompts.goHeader + ## Step contract+ a numbered list whose item 1 embeds the gate's full-treego 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
implementis 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 makescodex execread its instructions from stdin:# 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-onlyis 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 anyfix-*resolved to the codex adapter — passes-s danger-full-access -c approval_policy=neverin its place, the policy the drain gives the same steps (CapabilityDefault,internal/djinn/runtime/codex/profile.go):codex exec --skip-git-repo-check -C "<worktree>" \ -m "<resolved model>" -c model_reasoning_effort="<resolved effort>" \ -s danger-full-access -c approval_policy=never \ --json -o "$OUT/last-message.md" - \ < "$OUT/prompt.txt" > "$OUT/rollout.jsonl" 2> "$OUT/stderr.log"Never leave a write step's sandbox off the argv: codex then falls back to
~/.codex/config.toml, read-only on a fresh install, and evenworkspace-writecannot write a linked worktree's index under the main repo's.git/worktrees/or the beads database, sogit addfails onindex.lockand the step cannot commit. Never retry a failed write step with a different sandbox either; the argv above is the only one.The prompt — procedure steps. Build it on disk in the exact shape
buildCodexProcedurePromptbuilds (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.)- Header, one line:
You are running the `<step-id>` step of the Djinn harness for bead <bead-id>. ## 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>."- 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.
- The literal marker line
----- BEGIN PROCEDURE RUBRIC -----, then the rubric body ($OUT/rubric.md), then the literal marker line----- END PROCEDURE RUBRIC -----. - The closing sentence: "Emit the procedure's required output (the fenced
jsonsidecar 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
implementandfix-*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
$OUTscratch dir — never beside the verdict artifacts:rollout.jsonl(the--jsonJSONL event stream),last-message.md(written by-o), andstderr.log. Do not re-judge the run — extract the fencedjsonblock that sits at the END oflast-message.mdand 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 stepid, no.jsonsuffix):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
jsonsidecar that is non-empty, parses, and carriesverdict.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:
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" fiWith 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 —
SHIPpassesreview-loop;SHIP-WITH-FIXES/REWORKspawnsfix-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: codex -->The fallback is one retry. Take it on either trigger, and only these two:
<!-- djinn-dispatch: end -->- The run failed. The binary is not on
PATH, an auth or quota failure, a sandbox refusal, or any non-zero exit. - 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: codex -->Only then re-run the same command once (
codex exec, orclaude -pfor aclaude-codestep), printingnotice: <step-id> <command> failed (<reason>) — retrying once. A second failure on either trigger is not retried again: stop the walk, keep the beadin_progressand<worktree>, record it on the bead withbd update <bead-id> --append-notes="codex dispatch failed: <step-id> — <reason>"(never--notes, which overwrites), and report the step, the reason and$OUT/stderr.logto the operator. There is no other harness to fall back to here.<!-- 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.
design and revise-design take implement's knobs and design-review
takes review's. 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-design-review (design-review-loop) | the design reviewer's fenced json sidecar verdict field (<loop-id>.iter-<N>/design-review), AND the note in force carrying all five section headings and naming every overlapping bead | APPROVE | REVISE, or an APPROVE of a note missing a section or an overlapping bead → revise-design |
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-design-review — the design is approved before any code
loop-with-design-review (design-review-loop: max_attempts = 3,
check_function = djinn.check.design_approved, on_exhausted = soft_fail)
sits between design and implement. Its args name the three steps
(design_step, revise_step, review_step). Each attempt N:
revise-design, onN ≥ 2only. Skip it on attempt 1, and when attemptN-1ended in a review-invalid re-run rather than a verdict. Otherwise dispatch it with the promptbuildReviseDesignPromptrenders: the note in force after attemptN-1, and every finding held against it — the reviewer's REVISE findings plus the mechanical ones (a missing section, an overlapping bead the note never names) — as a numbered list. Its reply is the whole rewritten note ending in## Answers to the review: save it to.djinn/state/<bead-id>/design-review-loop.iter-<N>/revise-designand post it asdjinn design note — round <N> (revise-design). Restore the tree as afterdesign.design-review. Dispatch ONE reviewer on the step's resolved adapter, model and effort — read-only, the same sandbox asreview(acodexstep passes-s read-only) — with the promptbuildDesignReviewPromptrenders, the bead block appended as forreview: the note in force (the newestrevise-designcapture at or beforeN, else thedesigncapture), the parent epic, the overlapping beads, and onN ≥ 2the previous round's findings. Save its reply to<loop-id>.iter-<N>/design-reviewand postdjinn design review — round <N>: <verdict>with its findings.- The check — you run it, mirroring
checks.DesignApproved. No fenced json, an unknown verdict, or a REVISE with no findings is review-invalid: re-run the reviewer once in place.APPROVEpasses only when the note in force has a heading for each of Data shapes, Parts touched, Approaches, Proof and Out of scope, and names every overlapping bead; then the loop passes andimplementruns next. Anything else iterates. When thebd listread of the overlapping beads fails, never approve: stop the walk beforeimplementand re-run the check once bd answers, as a drain halts (design_transport) and retries the bead.
On a REVISE at N = max_attempts (the third), leave the tree as it is and
defer the bead for the operator, as a drain does — the bead does NOT stay
in_progress. Write the reason to a temp file: loop design-review-loop
exhausted budget (last verdict: fail): design-review-loop exhausted budget;
design not approved after <N> review rounds (last verdict <verdict>). Last
findings: <every finding the check held against the last note, numbered, on
one line> (the text designExhaustionReason builds), then run:
bd update <bead-id> --status=deferred --add-label needs-operator \
--append-notes "deferred (operator-required): $(cat <reason-file>)"Confirm with bd show <bead-id> --json that the status is deferred and the
labels carry needs-operator, then HALT with an escalation message naming
design-review-loop, the rounds spent and the same findings. No implement
runs and no commit was made: the design steps never write.
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_argvin.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-regressionon a clean run; its autonomous counterpart renders an empty prompt and is skipped. - A broken tree dispatches
fix-regressiontodjinn-fixerwith 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
regressionwith 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).
- Before round 1, record the last kept commit:
git -C "<worktree>" rev-parse HEAD. - For round
Nfrom 1 toloop.max_attempts:- Dispatch the body children in
children[]declaration order, each per its shape (single-agent children per §4b). - 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.
- 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'sdescriptionstates (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. - Keep →
git -C "<worktree>" rev-parse HEADis the new last kept commit. Reset →git -C "<worktree>" reset --hard <last kept>thengit -C "<worktree>" clean -fd. - Stop, or
N == max_attempts→ exit the loop and advance the spine to the next step. Reachingmax_attemptsis a normal end, never an escalation.
- Dispatch the body children in
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_thresholdANDkind=fix. With the defaultimportantthreshold that is BLOCKER + IMPORTANTfix-kind findings (nitadditionally admits NIT). Apply each one'sactionat itsfile:line. - ALWAYS skip COUNTER — a
kind=counterfinding is an anti-recommendation; never apply it and never codify its rationale, at any threshold. - NEVER act on INVENTED — a
severity=INVENTEDfinding 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 stepidwith no.jsonsuffix (thedefaultStepFilesconvention). Thefix-*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 withbd comments <bead> --json), onecriteriaentry peracceptance_criteriasegment, percore/bead-work-contract.md§6. It is a comment, not a file, so it outlives the run's worktree (djinn-mr56i).djinn wish landwrites it atcommit-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.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

