| name | loop |
| description | Per-pass discipline for an active seeks loop. Drive state ONLY through bin/seeks.mjs (which self-resolves .seeks via git). Never hand-write status.json. |
seeks — per-pass protocol
CARDINAL RULE: do exactly ONE pass, then STOP and end your turn. Never loop internally across passes. Ending your turn is what lets the Stop hook drive the next pass, advance stop_fires (so the max_iters backstop + stuck guard can fire), and survive context compaction. A loop where you never yield is unkillable by the hook.
Wind-down: if the Stop-hook continue-message says the time budget is nearly up, treat it as a wind-down — do NOT start new work: write/refresh summary.md (what you found, what's still open), commit it, then end your turn. The clock will halt the loop shortly after.
State for loop <name> lives in the PRIMARY checkout's .seeks/run/<name>/. You are usually inside the loop's worktree; always mutate state through the CLI, which resolves the control plane via git — never write .seeks/... by relative path yourself:
node "${CLAUDE_PLUGIN_ROOT}/bin/seeks.mjs" <subcommand> <name> ... (run via the Bash tool).
Every pass
-
Orient — read .seeks/run/<name>/{state.md,backlog.md,context.md} and .seeks/loops/<name>/spec.md (resolve .seeks with git rev-parse --path-format=absolute --git-common-dir if needed).
-
Backlog has - [ ] items → do the SINGLE next one (respect level: L1 = no edits, findings to state.md; L2 = edit + commit on seeks/<name>). Mark it - [x]; record:
… status-set <name> '{"last_change":"<what you did>"}'; … log-add <name> "pass N — <summary>" (appends to log.md; never hand-append by relative path); commit seeks(<name>): pass N — <summary> (L2+).
-
Backlog EMPTY → ask the engine whether the sweep bar is met — never re-derive it yourself: STATUS=$(node "${CLAUDE_PLUGIN_ROOT}/bin/seeks.mjs" sweep-status <name>). This returns the exact predicate the gate releases on ({mode, satisfied, label, …}). Reading dry_sweeps/min_dry_sweeps by hand is the trap that deadlocks an exhaustive loop: in mode:"exhaustive" the gate keys off dry_depth_rounds, not dry_sweeps, so a "3 dry sweeps → certify" shortcut sets done while the gate holds out forever.
satisfied:true (this includes mode:"none" — no sweep configured, the legacy default) → go to the verifier (below).
satisfied:false → run exactly one creative discovery sweep through a fresh lens, then end the pass. Get the angle: LENS=$(… sweep-next-lens <name>) (breadth-first through the catalog; a repeated lens won't advance the streak, so always take the one you're handed). Depth (exhaustive): the sweep-status label shows depth — depth 1 = each module's logic; depth 2 = cross-module dataflow; depth 3 = adversarial inputs / probing tests. Review through $LENS at the current depth; when a full catalog pass comes up dry the engine deepens automatically (via sweep-tick). You MUST dispatch a bug-hunter subagent for the sweep (triage role's model — node "${CLAUDE_PLUGIN_ROOT}/bin/seeks.mjs" role triage), handing it $LENS + the goal's blast radius. Bound its reply so a high-effort model doesn't stall generating a huge final answer (the failure mode where an opus hunter has to be killed and re-dispatched): tell it to reason internally but return ONLY a terse findings list — per real, demonstrable defect one line of file:line · one-line defect · severity · one-line repro/why; cap ~5; if it finds nothing, reply exactly FINDINGS: 0. No prose essay, no re-listing the source. This is not optional: a sweep you run inline is anchored on your own edits, so an inline "found nothing" is the self-grading seeks exists to prevent — and in until-dry/exhaustive mode those dry sweeps are exactly what advances the gate toward release. The subagent READS source and reasons about correctness through $LENS; re-running tests/linters is only a regression floor, NOT the bug finder. Then … sweep-tick <name> <count-of-NEW-real-bugs> "$LENS":
- Found > 0 →
… backlog-add <name> "<each new item>" (re-seed); end the pass. (sweep-tick reset the dry streak to 0.)
- Found == 0 → end the pass. The loop keeps sweeping — and in exhaustive mode deepening — until
sweep-status reports satisfied:true or the time budget winds it down. For a rich/overnight target the time-budget halt is the expected end; do NOT short-circuit to the verifier on a few dry sweeps, and do NOT disarm — if the gate keeps blocking a loop you think is finished, read its block reason, which names the unmet bar (e.g. dry depth-round k/N).
Verifier subagent (separate context, maker ≠ checker): dispatch it with the verifier role's model (node "${CLAUDE_PLUGIN_ROOT}/bin/seeks.mjs" role verifier → {model,effort}); hand it the absolute worktree path; it must cd there and RUN each executable done-condition from spec.md itself (subagents do NOT inherit cwd), write verify/<n>.md, and report per condition.
- All non-human conditions pass → first account for the oracle: run
… oracle-diff <name>; for each changed/added oracle (test) file, open it and justify the change in verify/oracle.md (a relaxed assertion that hides a real failure is a REJECT, not an account), then … oracle-ack <name>. Check oracle_globs_present in that output: if it's 0 while a done-condition is test-based, the hash-accounting is vacuous (no test-glob files to pin) — note it in verify/oracle.md and rely on actually running the oracle + your judgment, not the hash. Before you certify, re-confirm … sweep-status <name> is satisfied:true — setting done while the sweep bar is unmet only deadlocks against the gate. Then certify: … status-set <name> '{"verifier_certified":true,"done":true,"last_verdict":"pass"}'. (The gate still won't release until sweep-status is satisfied:true and the oracle ack matches the current diff — a test edited after the ack forces a re-verify.) If level is L3, the loop must also deliver before it can finish: once done is set, run … deliver <name> (pushes seeks/<name> and opens a PR; degrades to push-only/local if gh/remote are absent). The gate will not release done until delivered is true.
- Any fails → for each unmet condition:
… backlog-add <name> "<remediation>" and … condition-reject <name> <condition-id> (atomic; auto-sets needs_human at the threshold). Set last_verdict (e.g. "REJECT (typecheck)"). human-required condition → … status-set <name> '{"needs_human":true}'.
-
Second-to-last action: … progress-tick <name> (recomputes open_items/no_progress/items_closed). Do ≥1 real tool action each pass (you always do).
-
LAST action — STOP. One pass per turn. After progress-tick, end your turn: print a one-line recap and nothing more. Do NOT begin the next pass yourself. The Stop hook re-invokes you for the next pass, or releases you when the loop is done / needs-human / stuck / at max_iters. This is mandatory — ending your turn is what advances stop_fires so the hook-owned max_iters backstop and the stuck guard can actually fire, and what lets state survive compaction between passes. Always stop after exactly one pass (one backlog item, OR one verifier round). Do NOT disarm a loop yourself — when a loop is genuinely terminal the gate allows the stop and prints the ✅ done / ⏸ needs-human / ⛔/⏰ halt banner; a disarmed loop is invisible to the hook (no terminal banner) and self-disarm lets a maker bypass the sweep + delivery gates. If the gate keeps blocking a loop you believe is finished, that means a terminal condition is still unmet: read the block reason — it names the exact bar (dry depth-round k/N, undelivered L3, stale oracle ack). Act on it (keep sweeping, run seeks deliver, re-verify); never "unstick" the loop by disarming or re-certifying. Teardown is the user's call via /seeks:stop or /seeks:harvest.
Verifier subagent (maker ≠ checker)
Fresh context; runs checks in the worktree; cites evidence; rejects on ambiguity; the ONLY source of the verifier_certified verdict. A loop with no executable condition (subjective / human_required) can never be certified done by the gate — converge the mechanics, write findings/summary, then set needs_human. Do NOT set done.
If the verifier subagent errors, stalls, or returns nothing, RE-DISPATCH it — never run the done-conditions yourself and certify on your own run. That collapses maker≠checker (you become both the maker and the checker), and it's the whole point of the gate. Waiting one more pass for a fresh verifier is always correct; self-certifying to "unstick" the loop never is. You may run the conditions yourself for your own situational awareness, but the value written into verifier_certified must come from the independent verifier's verdict, not your inline run.
Never
Never hand-write status.json/hook-state.json. Never let the maker self-certify. Never edit denylist paths.
Hard-enforced — the PreToolUse hook denies these deterministically (you'll get a tool error; adapt, don't retry): editing denylist paths (**/.env, **/secrets/**, .git/**); editing outside the worktree; hand-writing status.json/hook-state.json (use the CLI); at L1, any source edit or git commit; git push / git merge / git rebase at every level (delivery is automated via seeks deliver at L3 — the agent never pushes/merges directly). Oracle/test edits are NOT blocked — they're accounted for at verify (above).