{"id":"0d1da8ea-1118-4f9d-b859-fd7275f7946e","slug":"seeks","kind":"skill","name":"seeks","summary":"Claude Code plugin for long-running agent loops toward one goal. A Stop hook re-runs your checks itself before it accepts done; edits to existing tests go to a human; edit-tool guardrails are enforced","owner":"Bogzx","repoUrl":"https://github.com/Bogzx/seeks","license":"MIT","licenseTier":"permissive","trust":"community","stars":4,"match":null,"fewStars":true,"format":"skill","createdAt":"2026-06-27T15:06:36.000Z","addedAt":"2026-10-22T03:08:03.297Z","logoUrl":"https://avatars.githubusercontent.com/u/34788557?u=1f7c94ba87f5d006e1395373b642700dcdf6d68f&v=4","linkOnly":false,"collection":null,"skillPath":null,"categories":["developer-tools"],"tags":["agent-loop","agentic-ai","ai-agents","ai-safety","anthropic","automation","autonomous-agents","claude"],"runtime":"node","local":true,"cloudCapable":false,"cloudCapability":"unknown","cloudReadyOnPlatform":false,"needsApiKey":false,"reviewed":false,"syncedAt":"2026-10-25T16:02:08.253Z","sample":false,"url":"https://conduits.dev/tools/seeks","language":"JavaScript","transports":["stdio"],"install":{"npm":"seeks"},"requirements":{"env":[],"upstreamMayCharge":false},"tools":null,"readme":"<p align=\"center\">\n  <img src=\"assets/seeks.png\" alt=\"Seeks\" width=\"240\">\n</p>\n\n<h1 align=\"center\">seeks</h1>\n\n<p align=\"center\"><em>Point Claude Code at one goal and leave it running. Deterministic hooks hold the brakes, and <code>done</code> means your checks passed — the hook runs them itself.</em></p>\n\n<p align=\"center\">\n  <code>Node ≥18</code> · <code>zero deps</code> · <code>works on a throwaway branch</code> ·\n  <a href=\"https://github.com/Bogzx/seeks/actions/workflows/ci.yml\"><img src=\"https://github.com/Bogzx/seeks/actions/workflows/ci.yml/badge.svg\" alt=\"CI\" align=\"center\"></a>\n</p>\n\n---\n\n> Seeks seeks but he's young. Try it, break it, tell me what broke.\n\n## Why\n\nTell Claude Code \"fix the failing tests\" and it fixes a few, then stops to check in. Wrap it in `while true; do claude; done` and the opposite happens: it grinds past a green build, burns your quota, or — worst — **claims it's done when it isn't.** The reason is structural: the agent doing the work is also the one grading it. You can't prompt your way out of marking your own homework.\n\n## See it in 60 seconds (no model, no tokens)\n\n```bash\ngit clone https://github.com/Bogzx/seeks && cd seeks\nnode examples/demo.mjs\n```\n\n<p align=\"center\"><img src=\"assets/demo.gif\" alt=\"node examples/demo.mjs: guardrail denials, a forged done refused over a red npm test, a real fix released as done, and a rewritten test routed to needs-human\" width=\"820\"></p>\n\nThe demo copies [`examples/add`](examples/add) (an unimplemented `add()` and its test) into a temp repo, creates two loops with the real CLI, and plays a scripted maker against the real hooks, called the way Claude Code calls them:\n\n1. **Guardrails before every tool call.** A write to `.env`, a `git push` and a write to the loop's own `status.json` are denied.\n2. **`done` is the hook's call.** The maker tries to set `done` itself (refused), then certifies while `npm test` is red. The Stop hook runs `npm test`, sees it fail, and keeps the loop going.\n3. **A real fix.** The hook re-runs `npm test`, it passes, ✅ done.\n4. **Rewriting the test** makes the check green too, but a changed pre-existing test ends in ⏸ needs-human, even after the verifier signs off on it.\n5. **Every verdict is logged**, and `seeks why` replays it.\n\nIt exits non-zero if any verdict differs from the above, and CI runs it on every pull request, on Linux, macOS and Windows.\n\n## Install\n\n```\n/plugin marketplace add Bogzx/seeks\n/plugin install seeks@seeks\n```\n\nThen `/reload-plugins` or restart. (Hacking on it locally? `claude --plugin-dir \"/path/to/seeks\"`.) Installed it before 2026-10-01 and `/plugin` still shows 0.1.0? The [0.2.0 notes](CHANGELOG.md#020--2026-10-01) say how to update.\n\nYou need **Node ≥18 installed system-wide** (hooks run in a non-interactive shell, so a node that only nvm/fnm/asdf puts on `PATH` isn't found) and **git ≥2.25**. `/seeks:doctor` reports both; details under [Requirements](#requirements).\n\n## Your first loop\n\n```\n/seeks:new fix the flaky auth tests   # interviews for done-conditions + a budget, scaffolds the loop\n/seeks:start                          # drives until it hits an end state\n/seeks:harvest                        # review the branch diff (and the PR, at L3)\n```\n\nEach pass prints one line:\n\n```\n▸ fix-auth-tests · pass 3 · items 9→7 · edited session.ts · ⏰ 2h left · continuing\n```\n\nNo interactive session needed: [`seeks run`](#headless-seeks-run) drives the same loop from a terminal, and [`examples/add`](examples/add) is a copy-paste first run.\n\n## How a loop ends\n\n| You give it… | It ends in… |\n|---|---|\n| a solvable task | ✅ **done**: the maker fixes it, the verifier signs off, and the Stop hook re-runs your checks green |\n| an impossible or subjective one | ⏸ **needs-human**: repeated red checks escalate, and a goal with no runnable check never reaches `done` |\n| one that never converges | ⛔ **stopped** — hits its iteration cap, time budget, or stops improving |\n\n## How it works\n\nseeks runs the loop inside a **control plane**: fast Node hooks between Claude and your repo that veto actions in deterministic code, before they run.\n\n- **`done` is a hook's call, not the model's.** Your done-conditions are stored when the loop is created. When the loop claims it is finished, the Stop hook runs each one itself in the worktree and releases `done` only if they exit green. A verifier subagent reviews the work first (was a test weakened? does the fix address the goal?), but its sign-off is advisory. It cannot make a red check pass.\n- **Guardrails on every edit.** The file-editing tools can't write `.env` / secrets / `.git`, can't leave the worktree, and can't hand-write loop state or seeks' own code. No level may `git push`, `merge` or `rebase`; the hook *parses* the command for it rather than grepping.\n- **Adding tests is free; changing the ones you had goes to you.** If the loop modified or deleted a test, a runner config, a CI file or the test-relevant part of a build manifest that existed when it started, a green check ends in ⏸ **needs-human** with the file list instead of ✅ done; so does a green check when seeks can't compute that diff at all. A dependency bump doesn't count. [The full list, and how to opt out](GUARANTEES.md#how-it-works).\n- **A budget it has to work to reach.** Iteration and wall-clock caps live in hook-owned files. The edit tools can't write them, and the seeks CLI won't move them on a running loop unless *you* just typed `/seeks:start`, `/seeks:stop` or `/seeks:delete`.\n\nWhat it can still do: game the code under test itself (special-casing the inputs a test uses), edit a helper script outside the oracle globs or the installed tools in `node_modules/`, or reach loop state through Bash if it tries hard enough, and loop state holds the stored done-conditions, so that can fake `done` ([details](GUARANTEES.md#the-one-guarantee-that-is-not-by-construction)). Read the diff. Work lands on a `seeks/<name>` branch, and seeks itself never merges it.\n\n## What is enforced, and what isn't\n\n| | |\n|---|---|\n| **Edits** (`Edit`/`Write`/`MultiEdit`/`NotebookEdit`) | **Enforced in code**, against a resolved path: denylist, worktree confinement, loop state, seeks' own code, L1 report-only, the wrap-up window. |\n| **`done`** | **Enforced against the checks you gave it, and only as strong as the loop-state row below.** Only the Stop hook writes it, after running them, and a changed pre-existing oracle file (or an oracle check that fails) routes the loop to you. But the stored done-conditions and the gate's verification cache live in loop state, which Bash can reach on a best-effort basis, and the checks run in the maker's own worktree. |\n| **The seeks CLI** | **Enforced in the CLI.** A running loop's brakes move only with the one-shot grant that a user-typed `/seeks:start`, `/seeks:stop` or `/seeks:delete` mints. |\n| **`git push` / `merge` / `rebase`, loop state and seeks' code, via Bash** | **Best-effort.** The command is parsed, not pattern-matched, and the known misses are pinned as passing tests. |\n| **Everything else Bash can do; reads** | **Not policed by default.** [`SEEKS_STRICT_BASH`](GUARANTEES.md#strict-bash-mode) turns Bash into an allowlist; [`seeks run --container`](#--container-keep-the-rest-of-your-machine-out-of-reach) keeps the rest of your machine out of reach. |\n\nEvery verdict and every hook crash is logged. The hooks fail open by design, and `/seeks:why` is how you tell \"allowed\" from \"enforcement was off\". The full table, the evasions that are caught and the gaps that are deliberately left open are in **[GUARANTEES.md](GUARANTEES.md)**.\n\n## Compared with\n\n| | Who decides \"done\" | Runs your check itself | Edits to your existing tests | Limits |\n|---|---|---|---|---|\n| [`/goal`](https://code.claude.com/docs/en/goal) (built into Claude Code) | a small fast model (Haiku by default) reading the conversation; it doesn't run commands or read files | no | not checked | a turn or time clause you write into the condition |\n| [`/ralph-loop`](https://github.com/anthropics/claude-code/tree/main/plugins/ralph-wiggum) (ralph-wiggum plugin) | the model, by printing an exact `--completion-promise` string | no | not checked | `--max-iterations` (unlimited by default) |\n| A Stop hook you write | your script | if you write it | if you write it | Claude Code overrides a Stop hook after 8 blocks in a row without progress, unless you raise `CLAUDE_CODE_STOP_HOOK_BLOCK_CAP` |\n| **seeks** | the Stop hook, after it re-runs the stored done-conditions | yes, in the loop's worktree | a modified or deleted pre-existing oracle file ends in ⏸ needs-human | iteration and wall-clock caps in hook-owned state; work on a `seeks/<name>` branch |\n\n`/goal` is the lighter tool: one command, no setup, any condition you can phrase. seeks is for when \"done\" should mean that a command exited green, and a loop that got there by editing your tests should come back to you instead of finishing.\n\n## Requirements\n\n**Node ≥18 and git ≥2.25 on the _hook's_ `PATH`.** Install Node system-wide, **not** via nvm/fnm/asdf — version managers only reach interactive shells, so hooks fail with `node not found`. (On nvm: `sudo ln -s \"$(command -v node)\" /usr/local/bin/node`.) `/seeks:doctor` diagnoses it and prints the fix. For L3 PRs, authenticate `gh`. **On Windows** the Stop hook runs your done-conditions in Git Bash, as Claude Code's Bash tool does (found via `CLAUDE_CODE_GIT_BASH_PATH`, then the `git` on `PATH`, then `bash.exe` on `PATH`). Without Git Bash it falls back to cmd.exe and logs a `condition-shell` warning that `/seeks:why` shows.\n\n**\"Leave it running\" means leaving the Claude Code session open.** The loop is driven by the Stop hook of that session. If a long run halts after about 8 passes while it is still making progress, add `\"env\": {\"CLAUDE_CODE_STOP_HOOK_BLOCK_CAP\": \"0\"}` to `~/.claude/settings.json`. A plugin cannot set that itself. The Stop hook runs your done-conditions when the loop certifies, so that stop takes as long as your checks do (the hook's timeout is 1 hour; each condition defaults to 10 minutes, and `timeout_sec` raises it).\n\n## Commands\n\n| Command | Does |\n|---|---|\n| `/seeks:new <goal>` | plain-English goal → an auto-named loop (interviews, picks a level + budget) |\n| `/seeks:start [name] [--for 8h]` | arm + drive — the most-recent loop if no name |\n| `/seeks:status` · `/seeks:add <task>` · `/seeks:stop` | show state · append a backlog task · disarm (you have to type it; the loop can't) |\n| `/seeks:harvest [name]` | finished or wound-down loops + their diffs / PR link |\n| `/seeks:why [name] [--denied]` | replay exactly why an action was allowed or denied (and whether a hook crashed) |\n| `/seeks:export [name]` | bundle a loop's state + transcript into a tarball (for bug reports) |\n| `/seeks:delete [name]` · `/seeks:doctor` | tear down · health check |\n| `seeks run <name> [--goal … --check …]` *(terminal)* | [headless](#headless-seeks-run): a separate `claude -p` maker, exit code = the gate's verdict |\n\n## Levels — how much rope\n\nChosen per loop at `/seeks:new`. Enforced by the hooks on the edit tools and on `git` via Bash; see [GUARANTEES.md](GUARANTEES.md#what-the-guardrails-cover--and-what-they-dont) for what plain Bash can still do.\n\n| Level | Can | Your base branch |\n|---|---|---|\n| **L1** | report-only: the edit tools can't touch source and `git commit` is denied (a Bash file write is not, unless strict mode) | untouched |\n| **L2** *(default)* | edits + commits on a throwaway `seeks/<name>` branch | untouched by seeks; `git push`/`merge`/`rebase` denied |\n| **L3** | once the Stop hook has verified the checks, pushes the branch + opens a PR | untouched — PR only |\n\n## Tiers — which agents, how hard\n\nSeeks runs several agents per loop. A **tier** sets which model each one uses and how deep it digs. Pick once (stored in `~/.claude/seeks.json`), or override per loop at `/seeks:new`; `/seeks:doctor` shows the active one.\n\n| | Light | Balanced *(default)* | All-out |\n|---|---|---|---|\n| **Maker** — writes the fix | sonnet | opus | opus |\n| **Verifier** — independent done-check | sonnet | opus | opus · max effort |\n| **Bug-hunter** — discovery sweeps | haiku | sonnet | opus |\n| **Analyzer / intake** — scopes + interviews | sonnet | sonnet | opus |\n| **Max iterations** — task / open-ended | 30 / 80 | 50 / 200 | 80 / 400 |\n| **Dry sweeps before done** | 1 | 2 | 3 |\n\nA lighter tier costs *thoroughness*, not *safety*. The done-condition check, the denylist and the no-push rules are the same code at every tier.\n\n## Running deep / overnight\n\nTell it how hard to dig at `/seeks:new` — *quick*, *thorough*, or *overnight* (or `/seeks:start --for 8h`). On an open-ended goal (\"find every bug\") seeks doesn't stop at the first green: it reviews the code through rotating **lenses** (concurrency, boundaries, security, timezones…) and keeps going deeper until it runs dry or the clock runs out. Near the deadline it **winds down** — commits, writes a summary — so you wake to `▸ ⏰ halt: time budget · 9 found · 2 open` and a branch to review, not a half-applied edit.\n\n## Headless: `seeks run`\n\nNo interactive session needed. From a terminal in your repo:\n\n```bash\nnpm install -g github:Bogzx/seeks     # once; or: git clone https://github.com/Bogzx/seeks ~/seeks && alias seeks='node ~/seeks/bin/seeks.mjs'\nseeks run fix-auth --goal \"fix the flaky auth tests\" --check \"npm test\" --budget 2h --strict\nseeks run fix-auth --budget 8h        # or: a loop you already made with /seeks:new\n```\n\n`seeks run` arms the loop, holds its lock and spawns the maker as a **separate `claude -p` process** in the loop's worktree. The child gets `--plugin-dir` pointing at this plugin, `--permission-mode bypassPermissions`, stream-json output and `CLAUDE_CODE_STOP_HOOK_BLOCK_CAP=0`. The runner streams the gate's one-line banners as they happen. The Stop gate inside the child is still the only thing that can release `done`; the runner only reports what it decided, then disarms and unlocks on the way out.\n\n- **Exit code:** `0` done · `2` needs-human · `3` halted (stuck, max-iters, time budget) · `1` anything else (the maker crashed, `claude` not found, …). A CI job can gate on it. **`--json`** prints exactly one summary object on stdout (outcome, exit code, passes, cost, branch, session id, …) and moves the progress lines to stderr.\n- **Budgets:** `--budget` is enforced by the gate. The runner also kills a maker that never yields once the budget plus a grace period (10%, at least 2 minutes) has passed. `--max-iters`, `--max-budget-usd`, `--max-turns` and `--model` pass through.\n- **`--goal … --check …`** scaffolds a new loop without the `/seeks:new` interview. It creates the worktree on `seeks/<name>` and stores each `--check` as a done-condition (exit 0). Run state is ignored via `.git/info/exclude`; your `.gitignore` is not touched. At least one `--check` is required.\n- **`--resume`**: if the maker process dies before the gate releases the loop, `seeks run <name> --resume` continues **the same conversation** (`claude --resume <session>`, recorded in hook-owned state) with the **same budget**. The iteration counter and the clock carry on instead of restarting.\n- **`--strict`** sets `SEEKS_STRICT_BASH=1` for the maker. `--dry-run` prints the exact command and env without touching anything. `--claude <path>` or `SEEKS_CLAUDE_BIN` picks the binary.\n- The child loads seeks from the copy you ran `seeks run` from (your clone, or the npm install). If the marketplace copy is also enabled in your Claude Code config, its hooks may fire as well; disable one of the two for headless runs.\n\n### `--container`: keep the rest of your machine out of reach\n\nThe maker runs with `bypassPermissions`, and seeks' Bash guardrails are best-effort. `--container` removes the host from reach instead:\n\n```bash\ndocker build -t seeks-maker docker/            # once: node + git + Claude Code (extend it with your toolchain)\nseeks run fix-auth --goal \"…\" --check \"npm test\" --container [--image seeks-maker] [--network bridge]\n```\n\n`seeks run` starts the maker in `docker run --rm` as **your uid:gid**. It mounts only what the loop needs, each at the same absolute path it has on the host: the loop's **worktree** (read-write), the repo's **`.git`** (read-write, since a worktree commits into it) and **`.seeks`** (read-write, loop state), and **the plugin read-only**. The guardrails can't be edited from inside, even by a Bash trick. **Your HOME is not mounted.** The container's HOME is `.seeks/run/<name>/container-home`, which persists so `--resume` works. Credentials pass **by name only** (`ANTHROPIC_API_KEY`, `CLAUDE_CODE_OAUTH_TOKEN`, Bedrock/Vertex/proxy vars); nothing else from your environment gets in. `--network` defaults to `bridge`, because the maker must reach the API; point it at a restricted network or proxy to narrow egress. A stuck maker is stopped with `docker kill`. POSIX hosts only (use WSL on Windows).\n\nWhat the container does *not* change: the `.git` and `.seeks` mounts are writable, so loop state (the budget, the stored done-conditions) and refs are protected by the same best-effort policy as without it. A maker set on faking `done` can still do it from inside. What it removes is everything else on your machine.\n\n---\n\n<details>\n<summary>🔵</summary>\n\n> *I'm Mr. Seeks! **Look at me!*** A Seeks is summoned for **one** goal. It seeks. It verifies. When the oracle goes green, *poof* — it ceases to exist. **Caaan do!**\n>\n> ```\n> node \"${CLAUDE_PLUGIN_ROOT}/bin/seeks.mjs\" --iam\n> ```\n\n</details>\n"}