# loop-creator **Repository Path**: vince4win/loop-creator ## Basic Information - **Project Name**: loop-creator - **Description**: MCP server that adds loop-engineering services (trajectory, stop conditions, verifiers) to any coding agent. - **Primary Language**: Unknown - **License**: AGPL-3.0 - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-07-04 - **Last Updated**: 2026-09-05 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # loop-creator > ๐ŸŒ **Languages:** [English](README.md) ยท [็ฎ€ไฝ“ไธญๆ–‡](docs/i18n/README.zh-CN.md) A stdio MCP server that adds loop-engineering services to any MCP-capable coding agent โ€” trajectory logging, stop conditions, and verifiers. The host owns its own `while`-loop; this server provides the loop services. **Status:** v0.9.1 โ€” 14 MCP tools (incl. crash recovery), 5 Skills (tiered: 3 recipes / 1 router / 1 meta), 4 host adapters (zcode / traecn / opencode / claude-code), full install/update/uninstall support. Coordinator mode (stable) + Runner mode (Phase 2) + Condenser (Mode A + B, LLM-backed) + Bilevel mode (Phase 4) + Config rewrite (Phase 4.1) + Meta-Loop diagnose + evaluate + plan (Phase 5) + Crash recovery (loop.recover). 538 tests. Seven rounds of deep audit (round 5: a 2026-09-04 four-probe adversarial evaluation; tail fixes + hardening landed 2026-09-05) have hardened the runtime against server crashes, hung subprocesses, OOM, input edge cases, and command-injection attempts (see the [CHANGELOG](CHANGELOG.md)). ## Why An agent is an LLM in a loop, calling tools based on environment feedback. As models got stronger and interchangeable, the bottleneck moved from "how to prompt" to "how to design the loop" โ€” when to stop, what counts as done, who judges the work. Loop Engineering is that shift: humans design the loop (stop conditions, verifiers, trajectory); the model just runs inside it. In that world, **the verifier is the bottleneck, not the model** โ€” a weak verifier doesn't fail loudly, it confidently produces garbage forever. The idea entered wide circulation through Karpathy's `AutoResearch` (March 2026, a 630-line repo that hit 66k+ stars in a month; Fortune named the pattern "the Karpathy Loop"). The setup is three files: a script the agent edits, an evaluator the agent cannot touch, and a `program.md` describing the goal โ€” the agent then runs "propose โ†’ evaluate โ†’ keep/discard" while you sleep. Two months later the *Bilevel Autoresearch* line of work (arXiv, 2026-03, *"Bilevel Autoresearch: Meta-Autoresearching Itself"*) stacked a second loop on top whose job is to watch the inner loop's trajectory and rewrite *how the inner loop searches* โ€” same LLM, 5x improvement. The lesson: the win comes from loop architecture, not from a smarter model. `loop-creator` is the **infrastructure layer** for that style of work โ€” host-agnostic (any MCP-capable agent), language-agnostic, and explicitly limited to the three pieces every coding-agent loop reinvents: trajectory logging, stop conditions, verifiers. The host keeps its own `while`-loop and its own LLM; this server provides the loop services. ### What this server deliberately does NOT do Loop Engineering has five moving parts (the Karpathy-era literature converges on: automation, skills, sub-agents, connectors, verifier). `loop-creator` owns exactly one of them โ€” **the verifier** (plus the trajectory + stop-condition substrate that makes it useful). The other four belong elsewhere: | Part | Owner | Why | |---|---|---| | **Verifier** (+ trajectory, stop conditions) | `loop-creator` | The piece every loop reinvents; this is the project's reason to exist. | | **Automation** (schedule, heartbeat, event trigger) | The host | A stdio MCP server can't reliably own a timer or fire on host events. The host's `/loop`, `/goal`, cron, CI, etc. are the heartbeat. | | **Skills** (project knowledge, conventions, build steps) | Sibling `skills/` (see [Skills](#skills)) | Procedures are written once, read by every run โ€” orthogonal to the runtime primitives. | | **Sub-agents** (maker/checker separation) | The host | The runtime doesn't own an LLM, so it cannot spawn a reviewer model. The host decides whether to grade with a different model/system-prompt; see *Phase 4 โ€” Bilevel* in the [roadmap](CHANGELOG.md#future-roadmap). | | **Connectors** (PR, Slack, Linear, issue tracker) | The host's tool ecosystem | A loop server that grows a Slack client has lost its shape. `Result` + host-decides keeps the runtime narrow. | Reading those boundaries as "missing features" misreads the design. They are what keeps a stdio server host-agnostic. ## What it does Fourteen tools, one JSONL trajectory per loop. The host calls them per step. | Capability | Provided by | Notes | |---|---|---| | Lifecycle | `loop.start` / `loop.finish` | 4 states: `running` / `finished` / `stopped` / `errored`. | | Trajectory | `loop.observe` writes, `loop.replay` reads | Append-only JSONL โ€” one event per line, `~/.loop-creator/loops/{loop_id}.jsonl`. | | Stop conditions | `any: [max_iter, max_time, verifier_pass, max_cost]` | Any one fires โ†’ `should_stop: true`. Day-1 ships `any` semantics only. | | Verifiers | `shell` (exit code) or `llm_judge` (host sampling) | `shell` runs **synchronously** on `observe`; `llm_judge` runs **asynchronously** (Phase 1.5) โ€” `observe` returns immediately with `verifier_pending: true`, the result lands later, host probes it via `loop.poll`. Optional `verifier.every: N` skips the verifier on action steps not divisible by N. Result recorded as a `VerifierResultEvent` either way. | | Cost tracking | `ObservationEvent.cost_usd` (host) + `RuntimeCostEvent` (runtime) | Two channels summed for the `max_cost` stop condition. `RuntimeCostEvent` has no emitter on day-1 (reserved for future shell-subprocess / external-API billing); the schema and evaluator are wired so adding emitters is additive. | | Error model | Every tool returns `Result` as a value | Never throws. App errors surface as `{ isError: true, structuredContent: { code, message } }`. | Day-1 ships **Coordinator mode only**: the host owns its `while`-loop and the LLM; this server never drives the host's tools or model. That keeps the runtime host-agnostic โ€” any MCP-capable agent (Claude Code, Cursor, Cline, Continue, TraeCN, custom) works without an adapter. Runner mode (runtime drives the host) is Phase 2. ## How it works ### Design principles 1. **Coordinator mode.** Host owns the `while`-loop and the LLM. Runtime provides services: lifecycle, trajectory, stop evaluation, verifier execution. Runtime never drives the host โ€” pure MCP is hostโ†’server unidirectional, so the runtime can't reliably invoke the host's tools. 2. **Event sourcing.** The JSONL event log is the single source of truth. `state` is the last `MetaEvent`; `iter_count` is the count of `ActionEvent`s; `verifier_pass` count is the count of passing `VerifierResultEvent`s. No side-channel state โ€” the in-memory `events` cache mirrors the file, never overrides it. 3. **`Result` as values.** Runtime never throws to the host. Every tool returns `Result` โ€” `ok: true` payloads or `ok: false, error: { code, message }`. Only protocol-level errors (tool not found, schema mismatch) go through MCP error responses. 4. **Host-agnostic.** No host-specific adapter on day-1. Day-1 abstractions (`Loop` / `Event` / `State` / `Service`) are mode-agnostic so Phase 2 Runner mode adds a driver, not a rewrite. ### Core abstractions - **Loop** โ€” stateful instance: state machine + append-only event log + derived stats (`iter_count`, `duration_ms`). - **5 event types** โ€” `ActionEvent` (host: tool call), `ObservationEvent` (host: tool result, may carry `cost_usd`), `VerifierResultEvent` (runtime: pass + feedback), `RuntimeCostEvent` (runtime: cost; reserved, no day-1 emitter), `MetaEvent` (runtime: lifecycle marker โ€” `started` / `finished` / `stopped` / `errored`). Every event carries `id`, `loop_id`, `seq`, `timestamp`, `type`, `payload`. - **4 states** โ€” `running` โ†’ `finished` | `stopped` | `errored`. All terminal states are absorbing: any further `observe` returns `loop_not_running`. ### State machine ``` loop.start โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ RUNNING โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ loop.finish loop.finish runtime outcome="completed" outcome="aborted" error โ”‚ โ”‚ โ”‚ โ–ผ โ–ผ โ–ผ FINISHED STOPPED ERRORED ``` **Transition rule:** `observe` never transitions to a terminal state itself โ€” it only returns `should_stop: true` when a stop condition fires. The host calls `finish()` to transition. The single exception is `runtime_error` (storage write fails mid-`observe`), which forces `errored` in-band โ€” host should not call `finish` afterwards. ### One `observe` step, end to end ``` host calls loop.observe(loop_id, { action, observation }) โ”‚ โ–ผ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ 1. append ActionEvent โ”‚ โ”‚ 2. append ObservationEvent โ”‚ โ”‚ 3. if config.verifier: run it โ”‚ โ”‚ โ€ข shell โ†’ spawn(command, cwd) โ”‚ โ”‚ โ€ข llm_judge โ†’ host sampling โ”‚ โ”‚ append VerifierResultEvent โ”‚ โ”‚ 4. evaluate stop.any[] against events โ”‚ โ”‚ 5. return { state, should_stop, ... } โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ–ผ state stays "running" (or "errored" on write fail) host decides: continue, or call loop.finish() ``` ### Persistence One file per loop: `~/.loop-creator/loops/{loop_id}.jsonl` (override with `LOOP_STORAGE_DIR`). Append on every event, flush before the tool returns. No `meta.json` โ€” config and state are derivable from events. No global index โ€” no cross-loop queries on day-1. A malformed last line (runtime crashed mid-append) is skipped on replay with `truncated: true` โ€” `replay` never throws. ### Error categories | Category | Example | State effect | |---|---|---| | **recoverable** | config invalid; verifier command not found; sampling unsupported; `loop_id` not found | none โ€” host decides next step | | **terminal-clean** | `max_iter` / `max_time` / `verifier_pass` fired | `running` until host calls `finish` | | **fatal-runtime** | storage write fails during `observe` | state forced to `errored` in-band | ## Install loop-creator has two independent install paths โ€” the **MCP server** (the 14 `loop.*` tools) and the **Skills** (the `verify-and-fix` / `code-audit-bugfix` / `meta-loop-trigger` / `loop-prospector` / `host-configs-builder` procedures). Both are needed for full functionality. The MCP server provides primitives; Skills provide procedure (when to use which tool, in what order). ### Scope model โ€” what goes where, and why the two installers differ The four pillars install into **two different scopes** with different safety postures. This is deliberate but was previously undiscovered โ€” it reconciles why `install-host` takes backups while `install-skills` overwrites-with-warn: | Pillar | Install scope | State scope | Installer | Safety posture | |---|---|---|---|---| | **A. MCP runtime** (`src/`) | **Project** โ€” a config entry pointing at `dist/index.js` is written into the consumer project's host config (`.zcode/config.json`, `.trae/mcp.json`, โ€ฆ) | **Project** โ€” trajectories write to `/.loop-creator/loops/` (per-project, via `LOOP_STORAGE_DIR` env the installer sets) | `install-host.ts` | rotate-2 backup (atomic tmp-copy + rename, per-step guarded) + atomic write + sibling-deletion invariant at two layers (in-memory merge check AND a disk compare-and-swap re-read before commit โ€” a bad install must not drop a sibling MCP server, even under a concurrent writer) | | **B. Skills** (`skills/`) | **User-home** โ€” copies into `~/.zcode/skills/` + `~/.trae-cn/skills/` (shared across all projects; procedure is host-level knowledge) | n/a (skills are documents, no runtime state) | `install-zcode-skills.mjs` | overwrite-with-warn (drift detection: warns if an installed copy was hand-edited and would be overwritten) | | **C. Host-config adapter** (`host-configs/`) | **Project** โ€” same as A (the adapter *is* the mechanism that writes A's entry) | n/a | (consumed by `install-host.ts`) | inherits A's safety posture | | **D. Introspection** (`introspect/`) | **Follows `LOOP_STORAGE_DIR`** โ€” state writes wherever pillar A's storage lives (typically `/.loop-creator/introspect/`) | **Project** (namespaced under A's storage root per P2) | n/a (scripts, not an install target) | n/a | **Why the split.** MCP server wiring is project-specific (each consumer points at its own `dist/index.js` path + its own `LOOP_STORAGE_DIR`) and a bad write could drop a sibling MCP server โ€” so `install-host` is defensive. Skills are user-level (the *procedure* for driving a verify-and-fix loop is the same in every project), shared, and there's no sibling-deletion risk โ€” so `install-skills` is simpler. There is no "wrong" installer; the two scopes genuinely warrant different postures. **Cross-project usage pattern.** One loop-creator checkout (built once) serves every consumer project. Each consumer runs `install-host --host --consumer ` to wire its project-level config + storage; the user-level Skills (`install-skills`, run once per machine) are shared across all of them. ### Quick start (from source) ```bash git clone https://gitee.com/vince4win/loop-creator.git cd loop-creator npm install npm run build # compile src/ โ†’ dist/ npm run install-skills # deploy 5 Skills to ~/.zcode/skills/ + ~/.trae-cn/skills/ ``` Then point your MCP host at `dist/index.js`. For ZCode / Trae CN, a checked-in `.zcode/config.json` and `.trae/mcp.json` are already in the repo โ€” just open the project and the server auto-discovers after `npm run build`. ### Published package Add to your MCP host's config (`.mcp.json`, TraeCN MCP settings, Cursor MCP config, etc.): ```json { "mcpServers": { "loop-creator": { "command": "npx", "args": ["-y", "@loop-creator/mcp"], "env": { "LOOP_STORAGE_DIR": "~/.loop-creator" } } } } ``` ### From source (manual config) ```json { "mcpServers": { "loop-creator": { "command": "node", "args": ["/path/to/loop-creator/dist/index.js"] } } } ``` `LOOP_STORAGE_DIR` is the storage **root** (defaults to `~/.loop-creator`). The server appends `/loops` internally; loops are persisted as `$LOOP_STORAGE_DIR/loops/{loop_id}.jsonl`. Set the env var to the root โ€” do **not** include `/loops` in the value (that produces `.../loops/loops/.jsonl`). ### Install via installer script (consumer projects) For a **consumer project** (e.g. AAR) that wants to use loop-creator without cloning this repo: ```bash # From the loop-creator repo: npm run install-host -- --host zcode --consumer /c/Projects/SPA/AAR npm run install-skills # deploy Skills to the user-level directory (once per machine) ``` `install-host` merges the `loop-creator` entry into the consumer's project-level config (`.zcode/config.json` for zcode, `.trae/mcp.json` for traecn, `opencode.json` for opencode, `.mcp.json` for claude-code). The merge is idempotent, preserves other servers, creates the consumer's own `LOOP_STORAGE_DIR`, and backs up on each run (rotate-2: keeps current + previous). Restart the host, then verify the 14 `loop.*` tools appear. Supported hosts (see `host-configs/manifest.json`): - `zcode` โ€” `.zcode/config.json`, nested `mcp.servers.` - `traecn` โ€” `.trae/mcp.json`, flat `mcpServers.`, expands `${workspaceFolder}` - `opencode` โ€” `opencode.json`, flat `mcp.`, flat command array, `environment` key - `claude-code` โ€” `.mcp.json`, flat `mcpServers.`, absolute paths **Note:** For the loop-creator repo itself, host configs are hand-curated and checked in. The installer deliberately refuses to write them โ€” edit by hand. ### Update To update an existing installation: ```bash cd /path/to/loop-creator git pull npm install # in case dependencies changed npm run build # recompile src/ โ†’ dist/ # Refresh Skills (if SKILL.md files changed): npm run install-skills # Re-run the consumer installer if dist/index.js path or env changed: npm run install-host -- --host zcode --consumer /c/Projects/SPA/AAR ``` Both installers are **idempotent** โ€” re-running them overwrites the installed copies with the current state. Restart the host after updating so the MCP server process picks up the new `dist/`. ### Uninstall loop-creator has two independent install paths, each with a matching uninstall command: ```bash # Remove the MCP server entry from a consumer project's host config: npm run uninstall-host -- --host zcode --consumer /c/Projects/SPA/AAR # Remove the Skills from both ~/.zcode/skills and ~/.trae-cn/skills: npm run uninstall-skills ``` **`uninstall-host`** removes the `loop-creator` entry from the consumer's config file. Sibling MCP servers are always preserved. If the config becomes empty (no remaining server entries and no other keys), the config **file is deleted** to leave the consumer project clean. A rotate-2 backup is taken before mutation (same as install). The `.loop-creator/` storage directory (trajectory data) is **NOT removed** โ€” the script reports its path so you can delete it by hand: ```bash rm -rf /.loop-creator # remove trajectory data (optional, destructive) ``` Idempotent: running `uninstall-host` when the entry is absent (or the config file doesn't exist) is a no-op that exits 0. **`uninstall-skills`** removes all 5 skill directories from both `~/.zcode/skills/` and `~/.trae-cn/skills/`. Idempotent โ€” missing directories are reported as "not found" and skipped. Restart the host after uninstalling so the MCP server process is killed and the skill registry drops the removed entries. ### All npm scripts | Script | Purpose | |---|---| | `npm run build` | Compile `src/` โ†’ `dist/` + copy templates | | `npm run check` | Type-check only (`tsc --noEmit`) | | `npm test` | Run the fast unit test suite (349 tests) | | `npm run test:host-configs` | Run the adversarial host-config gate (spawns real servers) | | `npm run test:all` | Run both test suites | | `npm run smoke` | Build + end-to-end smoke test against `test/dogfood/recipes/fix-failing-test/` | | `npm run install-host` | Install/update MCP server entry in a consumer project's config | | `npm run uninstall-host` | Remove MCP server entry from a consumer project's config | | `npm run install-skills` | Install/update Skills to `~/.zcode/skills/` + `~/.trae-cn/skills/` | | `npm run uninstall-skills` | Remove Skills from both user-level skill directories | | `npm start` | Start the MCP server (`node dist/index.js`) | | `npm run verify-pack` | Verify the npm tarball packs + installs + boots correctly | ## Tools Fourteen tools (six lifecycle + trajectory tools incl. crash recovery via `loop.recover`, a read-only template library, an LLM-driven config generator, a Runner-mode driver, a bilevel multi-loop driver, the mid-run config rewriter, plus three Meta-Loop tools: diagnose + evaluate + plan). The host owns its `while`-loop and calls these per step. | Tool | Purpose | Returns | |---|---|---| | `loop.start` | Open a loop with a `StartConfig`. | `{ loop_id, state: "running" }` | | `loop.observe` | Record one agent step (action + observation). Runs the verifier if configured, evaluates stop conditions. For `llm_judge`, returns immediately with `verifier_pending: true` (Phase 1.5) โ€” use `loop.poll` to pick up the late result. | `{ state, should_stop, stop_reason?, verifier_pending, verifier_feedback }` | | `loop.poll` | Read-only probe of stop + async-verifier-pending state. Does NOT trigger a new verifier run. On a loop evicted from the registry (already terminal), returns `loop_not_found`. | `{ state, verifier_pending, should_stop, stop_reason?, verifier_feedback }` | | `loop.finish` | Close the loop. `outcome: "completed"` โ†’ `finished`; `"aborted"` โ†’ `stopped`. | `{ final_state, event_count, duration_ms }` | | `loop.run` | Run a loop autonomously via a registered HostDriver (Phase 2 Runner mode). Spawns a Runner that drives `loop.start โ†’ loop.observe* โ†’ loop.finish`. Coordinator mode (`loop.start` + `loop.observe` + `loop.finish`) remains available for hosts that own their while-loop. | `RunSummary { final_state, reason, steps, duration_ms, loop_id }` | | `loop.run_bilevel` | Run in Bilevel mode: an outer judge observes N inner loops on the same task and decides each step whether to spawn/resume/kill/finish. | `BilevelRunSummary { ...RunSummary, inner_loops: [...] }` | | `loop.update_verifier` | Rewrite the verifier and/or stop-conditions of a RUNNING loop mid-trajectory (Phase 4.1, extended in 4.1.1โ€“4.1.3). At least one of {`verifier`, `stop`, `reset_every_counter`, `verifier_timeout_ms`, `verifier_timeout_s`, `cwd`} must be set; `condense` is the only frozen field. `verifier`/`stop` are replaced; the timeout keys are shallow-merged into `config.metadata`; `cwd` takes effect on the next verifier spawn; `reset_every_counter` restarts the `verifier.every` cadence. Records a `meta/config_changed` audit event (with before/after/changed). | `{ state, config: {verifier, stop, metadata, cwd}, changed_fields }` | | `loop.replay` | Read the event log (read-only). | `{ events, total_count, truncated? }` | | `loop.recover` | Recover a loop whose server process crashed. Reads the orphaned trajectory, reconstructs state, writes a `meta/resumed` event, puts the loop back in `running`. Explicit โ€” the host calls this after discovering a crashed loop via `loop.replay`. Errors: `loop_not_found`, `already_running`, `already_terminal`, `recovery_failed`. | `{ loop_id, state: "running", recovered: true, event_count, iter_count, verifier_pass_count }` | | `loop.templates` | List curated `StartConfig` presets bundled with the runtime. Read-only. | `{ templates: [{ name, description, config, overrides }] }` | | `loop.suggest` | Generate a `StartConfig` from a natural-language task description via the host's LLM (Phase 3.5). Uses the 5 bundled templates as few-shot calibration. Read-only (no loop created). Optional `hints` can pin the verifier command and/or max_iter. A pinned `verifier_command` passes the shell sanitizer (round-5 hardening): chained operators (`;` `|` `&`), `%VAR%` expansion, `for`/`if`/`call` as the command word, and any redirection except the nul-device idioms (`>nul`, `N>nul`, `>>nul`, `2>&1`) are rejected. Returns a config ready to pass directly to `loop.start`. | `{ config: StartConfig, source: "llm" }` | | `loop.diagnose` | Assess whether a task fits the Meta-Loop pattern (planโ†’executeโ†’evaluateโ†’optimize). Two-phase: (1) free rule-based 4-feature scoring (longevity/verifiable/repetitive/convergent), (2) threshold-gated LLM plan generation producing a `Plan` with structured completion criteria (SCC) + cost estimate. Read-only (no loop created). On reject returns `should_loop: false`; the agent proceeds normally. (Phase 5.1) | `{ should_loop, scores?, plan?, confidence?, blind_spots?, threshold?, reason? }` | | `loop.evaluate` | Evaluate a Meta-Loop iteration: objective metrics (trajectory + SCC status) + trend detection (improving/stagnant/regressing) + five-part causal analysis (LLM). Returns `recommendation`: `continue` (SCC passed โ†’ `finish("completed")`), `abort` (regressing/stagnant-vague โ†’ `finish("aborted")`), or `revise_plan` (call `loop.plan` next). (Phase 5.2) | `{ metrics, trend, causal_analysis, should_continue, recommendation }` | | `loop.plan` | Generate or revise a Meta-Loop plan. First call (no feedback): returns the current plan verbatim (no LLM). Subsequent calls (with feedback from `loop.evaluate`): LLM revises the plan with mandatory `changes` diff and anti-degeneration validation. Trend-based `max_iter` decay: improving=keep, stagnant=halve(floor 3), regressing=0. (Phase 5.2) | `{ plan, changes, max_iter_adjustment? }` | ### StartConfig ```ts { stop: { any: StopCondition[] } // any one fires โ†’ should_stop: true verifier?: VerifierSpec cwd?: string // for shell verifier, default "." metadata?: Record // see "metadata keys" below condense?: { every: number; chunk_size?: number } // Phase 2: condense every N events; chunk_size rotates files (Phase 4.1.4) } type StopCondition = | { type: "max_iter", value: number } // positive integer | { type: "max_time", value: string } // "5m" / "1h" / "30s" (max 7 days; "0s" rejected) | { type: "verifier_pass", count: number } // positive integer (0 rejected) | { type: "max_cost", value: number } // USD, positive; sums ObservationEvent.cost_usd + RuntimeCostEvent.cost_usd type VerifierSpec = | { type: "shell", command: string, pass_exit?: number[], every?: number } // default pass_exit=[0], every=1; pass_exit must be non-empty if set | { type: "llm_judge", prompt: string, every?: number } // async (Phase 1.5); requires host sampling ``` **metadata keys** (read from `StartConfig.metadata`): | Key | Type | Purpose | |---|---|---| | `verifier_timeout_ms` | number | Shell verifier child-process timeout (default 30000ms). Also used as the async-poll fallback timeout when `verifier_poll_timeout_ms` is not set. | | `verifier_timeout_s` | number | Same as `_ms` but in seconds (lower precedence โ€” `_ms` wins if both set). | | `verifier_poll_timeout_ms` | number | Runner-mode async-poll budget โ€” how long the Runner waits for an `llm_judge` sampling call to resolve (default: falls back to `verifier_timeout_ms`, then to 300s). Separate from the shell-execution timeout so a tight 5s shell budget doesn't kill a slow LLM call. | | `verifier_poll_timeout_s` | number | Same as `_poll_ms` but in seconds (lower precedence). | | `task` | string | Optional tag for identifying the loop's purpose (informational only; not read by the runtime). | `observe` never transitions to `finished` โ€” only `finish` does. When `should_stop: true` with `max_iter` / `max_time` / `verifier_pass` / `max_cost`, state stays `"running"` and the host calls `finish`. `runtime_error` is the exception โ€” state becomes `"errored"` and the host should not call `finish`. **Async verifier note (Phase 1.5):** `shell` runs synchronously and blocks `observe` until the result lands. `llm_judge` runs asynchronously โ€” `observe` returns immediately with `verifier_pending: true` and `verifier_feedback: null`; the `VerifierResultEvent` is appended when the run resolves. Call `loop.poll(loop_id)` to re-evaluate stop conditions against the latest events and read the current pending state. Multiple async verifiers may be in flight simultaneously (one per `observe` step that ran the verifier); each appends its own `VerifierResultEvent` in completion order. `verifier.every: N` (default `1`) skips the verifier on action steps whose 1-based sequence number is not divisible by `N` โ€” useful for expensive `llm_judge` runs that don't need to grade every step. **Windows shell-verifier caveat:** `shell` verifiers run via `child_process.spawn(command, { shell: true })`, which on Windows defaults to `cmd.exe` (not Git Bash / PowerShell). `cmd.exe` mangles non-ASCII characters in the command string โ€” e.g., grepping for `โ„น` (U+2139, the `node --test` info marker) silently fails because `cmd.exe` corrupts the Unicode byte sequence during quoting. Prefer exit-code-based checks (`npm test >nul 2>&1`) over output-content grepping when the output contains non-ASCII, and avoid Unicode literals in `verifier.command` on Windows. Note that `verifier.command` passes a sanitizer (round-5 hardening): command chaining (`;`, `|`, `&`, `&&`), env-var expansion (`%VAR%`), `for`/`if`/`call` as the command word, and every redirection except the no-op nul-device idioms (`>nul`, `N>nul`, `>>nul`, `2>&1`) are rejected โ€” inside double quotes, `<`/`>` are literal text and allowed. Dogfooding finding 2026-07-06, recorded in spec ยง13 limitation #9. Every tool returns `Result` as a value: application errors come back with `isError: true` + a structured `{ code, message }` payload, never thrown. ### Templates Five curated `StartConfig` presets are bundled with the runtime and discoverable via `loop.templates()`: `verify-and-fix`, `code-audit-bugfix`, `plan-execute`, `refactor-with-tests`, `triage`. Each ships with a `max_iter` ceiling and `verifier.command` placeholder that you override per task. The three new families (`plan-execute`, `refactor-with-tests`, `triage`) ship with educated-guess defaults โ€” they will be tuned as recipes are written for them. ### Runner mode (Phase 2) `loop.run(config, host_driver, max_steps?)` is the 12th tool (registration order in `src/index.ts`). It spawns a `Runner` that drives the loop autonomously via a `HostDriver` โ€” an interface (`src/adapters/types.ts`) that abstracts "ask the host's LLM for the next action" (`nextAction`) and "execute a tool on the host" (`execute`). The first concrete adapter is `StdioSpawnDriver`, which spawns a child Node process per call (dogfoodable via `scripts/agent-step.mjs`). HostDrivers are registered at server startup via the `LOOP_HOST_DRIVERS` env var (function refs can't cross MCP): ```json { "mcpServers": { "loop-creator": { "command": "node", "args": ["/path/to/loop-creator/dist/index.js"], "env": { "LOOP_STORAGE_DIR": "/path/to/.loop-creator", "LOOP_HOST_DRIVERS": "[{\"name\":\"agent-step\",\"command\":\"node\",\"args\":[\"/path/to/agent-step.mjs\"],\"cwd\":\".\"}]" } } } } ``` If `LOOP_HOST_DRIVERS` is unset, `loop.run` returns `host_driver_not_found` but the other 12 tools work normally. Coordinator mode is fully backwards-compatible. **Verifier note:** `shell` verifiers work in Runner mode. `llm_judge` verifiers record `verifier_error: "sampling_unsupported"` because the Runner is host-agnostic and doesn't bridge the MCP `sampling/createMessage` capability โ€” use `shell` for Runner-mode loops, or wait for the sampling bridge (post-Phase 2). ### Condenser (Phase 2) `StartConfig.condense?: { every: number }` is a new optional field. When present, the runtime emits a `CondensedEvent` summary sidecar every `every` events. The summary is produced by an LLM-backed `SummarizeFn` (`buildLlmSummarize`) that calls the host's sampling capability for a real semantic summary of the last `every` events โ€” a genuine compression, not a mechanical join. Hosts without sampling capability (or on sampling failure) degrade to a trivial event-list summary; the loop is never blocked. `evaluateStop` ignores `CondensedEvent`; `loop.replay` returns it as-is; `HostDriver` adapters can use it for context-window compression. Addresses Known Limitation #3. **Phase 4.1.4 added Mode B** โ€” set `condense.chunk_size` to rotate the trajectory across `/NNNN.jsonl` chunk files (independent of the `every` summary cadence; each chunk capped at `chunk_size` events). Memory stays full-history (only the on-disk layout rotates). `Loop.replay` reads both the new directory layout and legacy flat files via a stat-and-branch (free backwards compat). ### Bilevel mode (Phase 4) `loop.run_bilevel(config, host_driver, max_steps?)` is the 13th tool (registration order in `src/index.ts`). It runs an outer judge loop (a `BilevelDriver` implementing the existing `HostDriver` interface) that observes N inner loops on the same task and decides each step whether to `spawn_inner` / `resume_inner` / `kill_inner` / `finish_outer`. Inner loops are driven to completion by the existing `Runner.run` via the named `host_driver`. The outer loop's `finish_outer` decision terminalizes the run with a new `outer_finished` reason code (judge-terminated, distinct from budget-exhaustion). Zero behavioral changes to `Loop`/`Event`/`LoopState`/`Runner`/`HostDriver`. **Phase 4.1.1 added a 5th action `update_inner`** โ€” the judge rewrites an inner loop's config as a native decision (delegating to `Loop.updateConfig`), landing between resume cycles. Writer/reviewer model-slot enforcement is deferred to Phase 4.2. Full design: [`docs/superpowers/specs/2026-07-07-phase-4-bilevel-mode-design.md`](docs/superpowers/specs/2026-07-07-phase-4-bilevel-mode-design.md). ### Config rewrite (Phase 4.1) `loop.update_verifier(loop_id, verifier?, stop?)` is the 14th tool (registration order in `src/index.ts`). It rewrites the verifier and/or stop-conditions of a **running** loop mid-trajectory โ€” the runtime primitive an outer judge (or any host) uses to re-steer a loop's search without respawning it. Either or both fields may be set (at least one required); `condense` is still frozen this phase. Every rewrite appends a `meta/config_changed` event (with before/after/changed) so `loop.replay` and audit tooling stay accurate. State-gated (running-only); the merged full config is re-validated with rollback on failure. The day-1 abstraction already dereferences `config.verifier`/`config.stop` fresh on every `observe`, so a mutation flows through to the next step with zero stale references. **Phase 4.1.1 added an optional `reset_every_counter` patch field** โ€” restarts the `verifier.every` cadence from the next action (the audit event records `every_offset_reset_to`). **Phase 4.1.2 added `verifier_timeout_ms`/`verifier_timeout_s` patch keys** (shallow-merged into `config.metadata`, preserving other host-set keys) and fixed the Runner async-poll ceiling to read them fresh via `readVerifierTimeoutMs` (previously cached at run start, `_s`-only โ€” a two-dimensional staleness documented in the 4.1 spec ยง11.2). **Phase 4.1.3 added `cwd`** (fresh-read each step via `pickExecutor`; the next verifier spawn runs in the new directory โ€” no inherited-process issue since each spawn is a fresh child). Full design: [`docs/superpowers/specs/2026-07-09-phase-4.1-update-verifier-design.md`](docs/superpowers/specs/2026-07-09-phase-4.1-update-verifier-design.md). ## Recipes Fixed loop recipes (QA fixtures, not a framework pillar) live under [`test/dogfood/recipes/`](test/dogfood/recipes/). Each is a self-contained project with a scripted `fake-host.mjs` e2e walkthrough (no real agent required): - [`test/dogfood/recipes/fix-failing-test/`](test/dogfood/recipes/fix-failing-test/README.md) โ€” verify-and-fix: an intentionally-failing test driven through `loop.observe` until the shell verifier (`npm test`) passes. Consumed by `scripts/dogfood-runner.mjs`. - [`test/dogfood/recipes/code-audit-bugfix/`](test/dogfood/recipes/code-audit-bugfix/README.md) โ€” verify-and-fix: audit buggy source, fix bugs one at a time, verifier passes only when all three are resolved. - [`test/dogfood/recipes/bilevel-smoke/`](test/dogfood/recipes/bilevel-smoke/) โ€” outer/inner StartConfig seed for the Phase 4 bilevel dogfood gate. Consumed by `scripts/dogfood-bilevel.mjs`. ## Skills Five Skills live under [`skills/`](skills/), covering the loop-engineering workflow with non-overlapping trigger boundaries: - [`verify-and-fix`](skills/verify-and-fix/SKILL.md) โ€” use when the failure is already known (failing test, lint error, build break). - [`code-audit-bugfix`](skills/code-audit-bugfix/SKILL.md) โ€” use when you need to read the code to find what's broken before fixing it (code review feedback, security scan results, "fix the bugs in this file"). - [`meta-loop-trigger`](skills/meta-loop-trigger/SKILL.md) โ€” use when deciding whether to engage the Meta-Loop (planโ†’executeโ†’evaluateโ†’optimize) for a complex task. - [`loop-prospector`](skills/loop-prospector/SKILL.md) โ€” use to discover recurring loop-shaped patterns across sessions (confirmed by `introspect/scan-opportunities.mjs`) and materialize them as new reusable Skills. Supports both on-demand (`/discover loops`) and spontaneous (session-start hook, `introspect/prospector-hook.mjs`) discovery. Includes sampling fallback (works without MCP sampling), Gitee/GitHub-aware PR creation, and frequency/cadence config. - [`host-configs-builder`](skills/host-configs-builder/SKILL.md) โ€” use when adding a new host adapter to the host-configs layer. **The first skill created BY loop-prospector** โ€” materialized end-to-end as the pipeline's dogfood proof. ### Installing / updating / uninstalling Skills See the [Install](#install) section for the full `npm run install-skills` / `npm run uninstall-skills` commands. Skills deploy to the **user-level** skill repository (not project-level): `~/.zcode/skills//` for ZCode and `~/.trae-cn/skills//` for Trae CN. Both use the same `SKILL.md` + YAML frontmatter format, so the same file is deployed to both. The repo's `skills/` is the single source of truth โ€” re-run `npm run install-skills` after editing any `SKILL.md` to refresh the installed copies (the script warns if an installed copy was hand-edited and would be overwritten). Restart the host for the skill registry to pick up changes. **Bโ†’A pathway gate (P1).** Before copying, `install-zcode-skills` validates every `loop.` tool reference in each `SKILL.md` against the tools actually registered in `src/index.ts` (the authoritative set, also emitted to `dist/tool-manifest.json` at build time). A skill that references a tool the server doesn't register โ€” e.g. after a tool rename in `src/index.ts` that wasn't reflected in the `SKILL.md` โ€” **fails the install with a non-zero exit and lists the unknown reference(s)**. This turns the skillโ†’tool coupling from implicit (free-text mentions that break silently at runtime) into gated (a rename in pillar A surfaces immediately at install time, the same way a host-adapter interface change surfaces at `tsc`). Skills may legitimately reference a *subset* of tools; they just may not reference tools that don't exist. ### How activation works The Skill and the MCP server activate via **two independent paths with zero RUNTIME coupling** โ€” at runtime, they bind only through a natural-language contract: the `SKILL.md` text references `loop.*` tool names, and the agent reads the Skill before deciding which tools to call. There is no code-level wiring between them at runtime. (Note: there IS install-time coupling by design โ€” `install-zcode-skills` gates each SKILL.md's `loop.` tool refs against the server's registered tools, so a tool rename surfaces at install time rather than silently at runtime. See the [Bโ†’A pathway gate](#install) and [Scope model](#install).) | Aspect | MCP server | Skill | |---|---|---| | Discovery source | Project-level `/.zcode/config.json` | User-level `~/.zcode/skills//SKILL.md` | | Loaded at | ZCode startup, one spawn per server entry | ZCode startup, scan + index all `SKILL.md` frontmatter | | Lifecycle | Child process kept alive for the whole ZCode session | Static text file; loaded into context on trigger | | Trigger | Always available in the agent toolset once spawned | `$` (explicit) or auto-suggestion via `description` match | | What breaks if removed | `loop.*` tools disappear from the toolset; `$verify-and-fix` still triggers but fails on first tool call | `$verify-and-fix` no longer recognized; `loop.*` tools still callable directly | #### MCP discovery path ZCode reads `/.zcode/config.json` and spawns one child process per entry under `mcp.servers`. For each entry: ``` ZCode startup โ†’ read /.zcode/config.json โ†’ for each mcp.servers entry: spawn with env โ†’ stdio MCP initialize handshake โ†’ tools/list โ†’ register tools into agent toolset โ†’ child process kept alive for the session ``` The global `~/.zcode/cli/config.json`'s `mcp.servers` field feeds a Trae-CN compatibility layer and is **ignored** by the ZCode agent toolset (see [spec ยง13 limitation #6](docs/superpowers/specs/2026-07-04-loop-creator-plugin-design.md)). Note this applies specifically to the `mcp.servers` field โ€” other MCP-capable plugins (Playwright, Firecrawl, etc.) installed via the `plugins` field of the global config do load and surface their tools, but they take a different loading path. For loop-creator, only the project-level file matters. Editing `.zcode/config.json` after ZCode has started does **not** automatically reload the server โ€” restart ZCode (or its MCP host) for the new entry/env value to take effect. #### Skill discovery path ZCode scans `~/.zcode/skills/*/SKILL.md` and indexes each file's YAML frontmatter (`name` + `description`) into the skill registry. The body is **not** parsed at scan time โ€” only loaded into the agent context on trigger: ``` ZCode startup โ†’ scan ~/.zcode/skills/*/SKILL.md โ†’ parse YAML frontmatter (name + description) โ†’ index into skill registry โ†’ on trigger ($ or description-match auto-suggestion): load full SKILL.md body into agent system context โ†’ agent reads Procedure section โ†’ calls tools (including loop.*) ``` The repo's `skills/` is the single source of truth; the files under `~/.zcode/skills/` and `~/.trae-cn/skills/` are plain **copies** (not symlinks โ€” Node's `fs.symlink` is unreliable on Windows without Developer Mode). Re-run `npm run install-skills` after editing any `SKILL.md` to refresh both install locations. #### The binding is natural-language, not code There is no compile-time or runtime link between a Skill and the MCP tools it references. Two diagnostic perspectives: - **Remove the MCP server entry** from `.zcode/config.json` โ†’ `$verify-and-fix` still triggers, the SKILL.md still loads, but the agent fails when it reaches "call `loop.start`" โ€” the Skill does not self-check tool availability. - **Remove the installed Skill** from `~/.zcode/skills/` โ†’ `$verify-and-fix` is no longer recognized, but `loop.start` / `loop.observe` / etc. remain directly callable as MCP tools. This is by design: the Skill encodes *procedure* (when to use, what order to call tools in); the MCP server encodes *primitives* (the tools themselves). Either can be replaced without touching the other. #### Maintenance timing | Change | Required action | |---|---| | MCP server source (`src/*.ts`) | `npm run build` โ†’ restart ZCode (so the spawned child process picks up the new `dist/`) | | `.zcode/config.json` (env, command, args) | Restart ZCode (the child process reads env at spawn) | | `SKILL.md` text (procedure, wording) | `npm run install-skills` (refreshes both install locations) โ†’ continues to work in the next session, no ZCode restart needed | | Adding a new Skill | Add the skill to the appropriate tier's `skills[]` array in `skills/manifest.json` โ†’ `npm run install-skills` | | Adding loop-creator to a new consumer project | From the loop-creator repo: `npm run install-host -- --host zcode --consumer ` โ†’ restart ZCode with the consumer as workspace | | Host config (adapters, manifest) | Run `npm run test:host-configs` (adversarial gate), then commit | ### Wiring the MCP server into Trae CN Trae CN reads project-level MCP config from [`.trae/mcp.json`](.trae/mcp.json), which is already checked in and points at `${workspaceFolder}/dist/index.js`. After `npm run build`, the server is auto-discovered when Trae CN opens the project. No manual config needed. ### Other MCP-capable hosts (Claude Code, Cursor, Cline, Continue) The Skill format (`SKILL.md` + YAML frontmatter with `name` + `description`) is identical to Claude Code's, so porting is a file copy to the host's skills directory (`.claude/skills/`, etc.). See the design specs ([verify-and-fix](docs/superpowers/specs/2026-07-04-verify-and-fix-skill-design.md), [code-audit-bugfix](docs/superpowers/specs/2026-07-04-code-audit-bugfix-skill-design.md)) for rationale. ## Develop ```bash npm install npm run build # tsc โ†’ dist/ npm test # 296 unit tests (168 through Phase 4.1.4 + 18 Phase 5.1 schema + 40 Phase 5.1 diagnose + 36 Phase 5.2 evaluator + 34 Phase 5.2 planner) npm run smoke # Phase 4 MCP plumbing smoke node test/dogfood/recipes/fix-failing-test/fake-host.mjs # Phase 5 e2e against real shell verifier node test/dogfood/recipes/code-audit-bugfix/fake-host.mjs # verify-and-fix with 3-bug audit trajectory # Agent integration (run once per host after build, re-run after editing Skills): npm run install-skills # copy skills/* โ†’ ~/.zcode/skills/ + ~/.trae-cn/skills/ npm run install-host -- --host zcode --consumer # merge loop-creator into /.zcode/config.json; pass consumer path as arg ``` ## Spec Full design: [`docs/superpowers/specs/2026-07-04-loop-creator-plugin-design.md`](docs/superpowers/specs/2026-07-04-loop-creator-plugin-design.md). Phase 5 Meta-Loop design: [`docs/superpowers/specs/2026-07-09-phase-5-meta-loop-design.md`](docs/superpowers/specs/2026-07-09-phase-5-meta-loop-design.md). **Must-follow development rules**: [`AGENTS.md`](AGENTS.md) โ€” 20 rules covering error handling, event sourcing, state machine, pillar boundaries, zod schemas, testing, lint gates, and spec-first workflow. Future roadmap: [`CHANGELOG.md#future-roadmap`](CHANGELOG.md#future-roadmap). ## License Dual-licensed under **AGPL v3** (open-source, free) and a **Commercial License**. - **Open-source use** โ€” [AGPL v3](./LICENSE). Free for personal use, open-source projects, and evaluation. Any code that interacts with the software over a network must also be open-sourced under AGPL v3. - **Commercial use** โ€” [Commercial License](./COMMERCIAL-LICENSE.md). Required for closed-source commercial products, proprietary SaaS / cloud services, or internal use where you cannot comply with AGPL v3 disclosure obligations. Contact the copyright holder to arrange a Commercial License. | Use Case | License | |---|---| | Personal / open-source project | AGPL v3 (free) | | Closed-source commercial product | Commercial License | | Proprietary SaaS / cloud service | Commercial License | See [COMMERCIAL-LICENSE.md](./COMMERCIAL-LICENSE.md) for details.