Skip to content
Back to skills

Loop

FSecurity

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.

  • 4 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 6, 2026
ai-agentspythongoshellbashnodeawsgit

Works with

  • terminal
  • cli

Security analysis

F10/100
  • highPerforms destructive filesystem operations
  • criticalReads or references SSH private keys
  • criticalReads or references SSH private keys
  • criticalReads or references SSH private keys

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

Scanned October 7, 2026

npx -y skills add Bogzx/seeks --skill loop --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Loop?

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

Security grade badge for Loop
[![Security: F — Skills Directory](https://www.skillsdirectory.com/api/skills/bogzx-loop/badge)](https://www.skillsdirectory.com/skills/bogzx-loop)

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

Download with Pro
SKILL.md
---
name: 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
1. **Orient** — read `.seeks/run/<name>/{state.md,backlog.md,context.md}` and `.seeks/loops/<name>/spec.md` (`node "${CLAUDE_PLUGIN_ROOT}/bin/seeks.mjs" seeks-dir` prints the absolute `.seeks` path if you need it).
2. **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+).
3. **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** (tests *and* build manifests/runner configs/CI — by default, if any **pre-existing** oracle file was modified or deleted, or a new runner config (`pytest.ini`, `conftest.py`, `.npmrc`, `tsconfig.json`, …) was added, the gate ends the loop in needs-human at release no matter what you ack, so a fix that needs to change an existing test, a `package.json` script or a runner config is a human's call; *adding* test files is free): 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: `… certify <name>`. That is the verifier's sign-off, **not** the verdict: at the next stop the **Stop hook runs every executable done-condition itself** in the worktree, and releases `done` only if each exits with its expected code (and `sweep-status` is `satisfied:true`, and the oracle ack matches the current diff). If one fails, the gate clears the certification, counts a condition-reject and shows you the output tail — fix it and re-verify. `status-set` can no longer write `done`, `verifier_certified` or `armed`, and cannot touch the budget, sweep, oracle or condition keys while the loop is live — it will refuse. **If `level` is L3, the loop must also deliver before it can finish:** when the gate's block reason says the conditions passed but the loop is undelivered, 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}'`.
4. **Second-to-last action:** `… progress-tick <name>` (recomputes open_items/no_progress/items_closed). Do ≥1 real tool action each pass (you always do).
5. **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 caller of `seeks certify`. Its job is the judgment the hook can't make (was a changed test weakened? do the fixes actually address the goal?); the exit codes are re-checked by the Stop hook regardless. 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. Never touch seeks' own plugin files. `seeks start` / `stop` / `reset-fires` / `budget-set` and the frozen status keys belong to the **user** while a loop is live — the CLI refuses them without the grant a user-typed `/seeks:start|stop|delete` creates; don't try to get around that.

**Hard-enforced — the PreToolUse hook denies these deterministically (you'll get a tool error; adapt, don't retry):** editing denylist paths (`**/.env`, `**/.env.*`, `**/secrets/**`, `**/.git`, `**/.git/**`, `**/*.pem`, `**/id_rsa*`, `**/id_ed25519*`, `**/id_ecdsa*`, `**/id_dsa*`, `**/.npmrc`, `**/.aws/**`, `**/.ssh/**` — a floor the loop can extend but not narrow); editing outside the worktree; touching `status.json`/`hook-state.json`/`decisions.jsonl`/`control-grant.json` **by any route — edit tool or Bash, read or write** (use `seeks status-get` / `status-set` / `why`). Via Bash the block reads the command: it follows `cd`, collapses `..`, expands `{a,b}` braces, resolves `*`/`?`/`[…]` globs, recurses into `eval`/`sh -c`, and scans `python -c`/`node -e`/`awk`/`sed` payloads, so a redirect, `tee`, `sed -i`, a relative path after a `cd`, a name spelled as a pattern, or `rm -rf` on the run dir are all denied. **It is best-effort, not a cage — which is exactly why the rule is a rule.** A shell can defeat any such check; reaching loop state by *any* means, including one that happens to slip past the hook, is a violation of this skill, not a clever workaround. At L1, any source edit or `git commit`; `git push` / `git merge` / `git rebase` at **every** level, including via `env`/`sudo`/`timeout`, a subshell `(git push)`, or `eval`/`sh -c` (delivery is automated via `seeks deliver` at L3 — the agent never pushes/merges directly). Oracle edits are NOT blocked, but by default modifying or deleting a *pre-existing* test or build manifest (`package.json`, `Makefile`, `pyproject.toml`, runner configs, CI) ends the loop in needs-human instead of done — prefer fixing the code; add new tests freely.

**Every verdict is logged** to `.seeks/run/<name>/decisions.jsonl` — allow, deny, and any hook crash. If a guardrail seems to have misfired, don't guess: run `seeks why <name> --denied` (or `--crashes`) and read the `rule`. If `SEEKS_STRICT_BASH` is on, Bash is deny-by-default and the error names the offending command — use an allowlisted tool instead of retrying.

Attribution

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

Comments

Loading comments…