From ee8690d47f647603ea19a709c0b8ab5332ab23f8 Mon Sep 17 00:00:00 2001 From: tickets-forge-dev Date: Mon, 6 Jul 2026 10:16:45 -0400 Subject: [PATCH 1/2] feat: Remove ctx usage across runtime packages --- packages/runtime/src/ctx.ts | 134 ------------------------------- packages/runtime/src/ownModel.ts | 51 ------------ 2 files changed, 185 deletions(-) delete mode 100644 packages/runtime/src/ctx.ts delete mode 100644 packages/runtime/src/ownModel.ts diff --git a/packages/runtime/src/ctx.ts b/packages/runtime/src/ctx.ts deleted file mode 100644 index 4308d2e..0000000 --- a/packages/runtime/src/ctx.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; -import type { CtxAdapter, CtxProvisionResult } from "./types.js"; - -const EMPTY: CtxProvisionResult = { useSkills: [] }; - -/** - * Parse the JSON object a ctx loop tool returns from its MCP text content. The ctx - * `ctx__loop_provision` / `ctx__loop_topup` tools return `{ use_skills, installed, skipped }` - * encoded as a text content block. Anything unexpected degrades to "no skills". - */ -function parseResult(raw: unknown): CtxProvisionResult { - const content = (raw as { content?: Array<{ type?: string; text?: string }> })?.content; - const text = Array.isArray(content) - ? content.filter((c) => c?.type === "text").map((c) => c.text ?? "").join("") - : ""; - if (!text) return EMPTY; - let obj: Record; - try { - obj = JSON.parse(text) as Record; - } catch { - return EMPTY; - } - const names = (obj.use_skills ?? obj.useSkills) as unknown; - // capabilities. is a list of {name,...} entries (ctx.loop_adapter.v1); pull bare names. - const caps = obj.capabilities as Record | undefined; - const groupNames = (g: string): string[] | undefined => { - const list = caps?.[g]; - if (!Array.isArray(list)) return undefined; - return list - .map((e) => (e && typeof e === "object" ? (e as { name?: unknown }).name : e)) - .filter((s): s is string => typeof s === "string"); - }; - const capabilities = caps - ? { - skills: groupNames("skills"), - agents: groupNames("agents"), - mcps: groupNames("mcps"), - harnesses: groupNames("harnesses"), - } - : undefined; - return { - useSkills: Array.isArray(names) ? names.filter((s): s is string => typeof s === "string") : [], - installed: Array.isArray(obj.installed) ? (obj.installed as string[]) : undefined, - skipped: Array.isArray(obj.skipped) ? (obj.skipped as string[]) : undefined, - capabilities, - harnessInstall: typeof obj.harness_install === "string" ? (obj.harness_install as string) : null, - warnings: Array.isArray(obj.warnings) ? (obj.warnings as string[]) : undefined, - }; -} - -/** Build the optional ctx tool args (permissions + own-model) shared by provision and topup. */ -function ctxArgs( - permissions?: string[], - ownModel?: { provider: string; model: string } -): Record { - const a: Record = {}; - if (permissions && permissions.length) a.permissions = permissions; - if (ownModel) { - a.own_llm = true; - a.model_provider = ownModel.provider; - a.model = ownModel.model; - } - return a; -} - -/** - * Talks to a ctx MCP server (`ctx-mcp-server`) over stdio to provision skills for a loop. - * The child process is spawned lazily on the first call, so attaching this adapter to a loop - * that never triggers discovery costs nothing. - */ -export class McpCtxAdapter implements CtxAdapter { - private client: Client | null = null; - private connecting: Promise | null = null; - - constructor( - private opts: { command?: string; args?: string[]; env?: Record } = {} - ) {} - - private connect(): Promise { - if (this.client) return Promise.resolve(this.client); - if (!this.connecting) { - this.connecting = (async () => { - const transport = new StdioClientTransport({ - command: this.opts.command ?? "ctx-mcp-server", - args: this.opts.args ?? [], - env: this.opts.env ?? (process.env as Record), - }); - const client = new Client({ name: "loop-runtime", version: "0.3.0" }, { capabilities: {} }); - await client.connect(transport); - this.client = client; - return client; - })(); - } - return this.connecting; - } - - private async call(name: string, args: Record): Promise { - const client = await this.connect(); - const res = await client.callTool({ name, arguments: args }); - return parseResult(res); - } - - provision(input: { - goal: string; intent?: string; baseDir: string; - permissions?: string[]; ownModel?: { provider: string; model: string }; - }): Promise { - return this.call("ctx__loop_provision", { - goal: input.goal, - intent: input.intent, - ...ctxArgs(input.permissions, input.ownModel), - }); - } - - topup(input: { - goal: string; reflection: string; loaded: string[]; baseDir: string; - permissions?: string[]; ownModel?: { provider: string; model: string }; - }): Promise { - return this.call("ctx__loop_topup", { - goal: input.goal, - reflection: input.reflection, - loaded: input.loaded, - ...ctxArgs(input.permissions, input.ownModel), - }); - } - - async close(): Promise { - if (this.client) { - await this.client.close(); - this.client = null; - this.connecting = null; - } - } -} diff --git a/packages/runtime/src/ownModel.ts b/packages/runtime/src/ownModel.ts deleted file mode 100644 index dee4044..0000000 --- a/packages/runtime/src/ownModel.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { existsSync } from "node:fs"; -import { join, delimiter } from "node:path"; - -/** - * Local model providers that ship a CLI binary we can detect on PATH. API providers - * (openai, anthropic, openrouter, litellm, …) authenticate by key — there is no binary to - * check, so a declared `ctx may use my own model "openai/…"` never warns. - */ -const LOCAL_MODEL_BINARIES: Record = { - ollama: "ollama", -}; - -/** True if `bin` resolves to an executable on PATH. Cross-platform, no subprocess. */ -export function commandOnPath(bin: string): boolean { - const exts = - process.platform === "win32" - ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";") - : [""]; - for (const dir of (process.env.PATH ?? "").split(delimiter)) { - if (!dir) continue; - for (const ext of exts) { - try { - if (existsSync(join(dir, bin + ext))) return true; - } catch { - /* unreadable PATH entry — skip it */ - } - } - } - return false; -} - -/** - * Warn when a `.loop` declares its own local model (`ctx may use my own model …`) but the - * provider's binary isn't installed. Returns the warning string, or null when there is nothing - * to warn about: no model declared, an API/unknown provider with no local binary, or the binary - * is present. Pure — pass `onPath` to test without touching the real PATH. - */ -export function ownModelBinaryWarning( - ownModel: { provider: string; model: string } | undefined, - onPath: (bin: string) => boolean = commandOnPath, -): string | null { - if (!ownModel) return null; - const bin = LOCAL_MODEL_BINARIES[ownModel.provider.trim().toLowerCase()]; - if (!bin) return null; // API/unknown provider — no local binary to check - if (onPath(bin)) return null; - return ( - `⚠ ctx: this file declares its own model "${ownModel.model}" (provider "${ownModel.provider}"), ` + - `but the \`${bin}\` binary isn't on PATH. The loop still runs on its normal runner; ctx will ` + - `recommend ${ownModel.provider} harnesses, but actually running one needs \`${bin}\` installed.` - ); -} From 77eb2361bf9fb70a17f642acd21254974e4d14e7 Mon Sep 17 00:00:00 2001 From: tickets-forge-dev Date: Mon, 6 Jul 2026 10:27:56 -0400 Subject: [PATCH 2/2] feat: remove ctx integration and document loop config --- .claude/skills/loopflow/SKILL.md | 40 --- .gitignore | 1 + AGENTS.md | 50 --- CHANGELOG.md | 16 +- README.md | 2 +- docs/MANUAL.md | 28 +- docs/ctx-integration-guide.md | 388 --------------------- docs/ctx-skill-source.md | 105 ------ docs/game.html | 4 + docs/index.html | 49 ++- docs/keywords/style.css | 4 + docs/keywords/use-skills.html | 13 +- docs/playground.html | 9 +- docs/playground/loop-lang.js | 18 +- docs/workshop.html | 4 + examples/ctx_capabilities.loop | 23 -- examples/ctx_skills.loop | 15 - packages/parser/src/parser.ts | 58 +-- packages/parser/src/types.ts | 51 +-- packages/parser/test/parser.test.js | 55 +-- packages/runtime/src/cli.ts | 39 --- packages/runtime/src/engine.ts | 106 ++---- packages/runtime/src/index.ts | 1 - packages/runtime/src/runners/claudeCode.ts | 16 +- packages/runtime/src/types.ts | 55 --- packages/runtime/test/engine.test.js | 134 +------ packages/runtime/test/eventSink.test.js | 4 +- packages/viz/src/live.ts | 3 +- packages/vscode/README.md | 2 +- packages/vscode/src/extension.ts | 4 +- packages/vscode/src/language.ts | 8 +- spec/loop-spec.schema.json | 43 +-- 32 files changed, 147 insertions(+), 1201 deletions(-) delete mode 100644 docs/ctx-integration-guide.md delete mode 100644 docs/ctx-skill-source.md delete mode 100644 examples/ctx_capabilities.loop delete mode 100644 examples/ctx_skills.loop diff --git a/.claude/skills/loopflow/SKILL.md b/.claude/skills/loopflow/SKILL.md index 09a0588..01646d3 100644 --- a/.claude/skills/loopflow/SKILL.md +++ b/.claude/skills/loopflow/SKILL.md @@ -51,8 +51,6 @@ allow edits automatically, but ask me before action policy each cycle: plan, then act, then observe the repeated steps (any subset, in order) also: , extra finishing passes after the goal is met use skills: , named skills the loop may invoke during plan/act -use skills recommended by ctx ctx recommends + installs the skills for the goal (needs the ctx MCP server); add `for ""` to override the query -top up skills from ctx run-time: pull more skills from ctx when a cycle fails and reflects remember in "" cross-run memory: read lessons on start, append an outcome on stop when it fails: reflect on , then plan again when it passes and the goal is met: stop @@ -69,9 +67,6 @@ models: fast , strong model tiering — plan/reflect/also→fast, act→s schedule: run unattended on a cadence runner: which agent executes the loop target: operate on another directory/repo -recommend skills with ctx ctx is this file's skill source — recommends + installs skills per loop goal -grant ctx: skills, agents, mcps, harnesses capability groups ctx may recommend (fail-closed; default skills+agents; mcps/harnesses are recommend-only) -ctx may use my own model "/" declares a user-owned model — unlocks harness recommendations (dry-run only) ``` Predicates: @@ -173,34 +168,6 @@ Walk these quickly, naming the keyword each time so they learn it: Don't forget the menu in Step 2 also covers `use the method` — pull a whole preset (e.g. BMAD) instead of hand-picking passes. -### Skill discovery — offer ctx when the right skills aren't named yet - -`use skills:` assumes the skills already exist in `~/.claude/skills`. If the user doesn't -already know which skills the loop needs, offer to let **ctx** -([claude-ctx](https://github.com/stevesolun/ctx)) pick them: it recommends the smallest -useful bundle for the goal and installs the bodies, so the names resolve. It is **opt-in** -and only works when the ctx MCP server is attached (`claude mcp add ctx -- ctx-mcp-server`). - -When ctx's tools are available (`ctx__loop_provision`, `ctx__recommend_bundle`): -1. After the goal is set, call `ctx__recommend_bundle` (read-only preview) or - `ctx__loop_provision` with the goal — show the user the recommended skills with their - reasons before installing anything. -2. On approval, `ctx__loop_provision` installs them and returns the resolved names. Write a - real `use skills: ` line **and** a `use skills recommended by ctx` line — the - first keeps the `.loop` self-contained and reproducible; the second lets a headless - `loop run` re-resolve the bundle from ctx. -3. Offer `top up skills from ctx` if the loop should pull more skills when a cycle fails. -4. **Beyond skills** — if the goal needs more than skills, add a `grant ctx: skills, agents, - mcps, harnesses` line for the groups that apply (fail-closed; omit it for skills-only). - `mcps` and `harnesses` are **recommend-only** — ctx surfaces them with an install command - the user runs; the loop never auto-installs them. Harnesses additionally need a - `ctx may use my own model "/"` line, and always come as a `--dry-run` - command. Pass the granted groups (and own-model) to `ctx__loop_provision` as `permissions` / - `own_llm` / `model_provider` / `model`. - -When ctx is **not** attached, skip this silently and author `use skills:` by hand as usual — -the loop runs the same either way. - Offer the defaults inline (*"I'll add a tests + security pass, gate the migration, a 6-try guard, work on a branch, one model throughout, no schedule — sound right?"*) so the whole interview is one exchange. Name every topic once even when you default it, so the @@ -323,13 +290,6 @@ the user explicitly asks for the headless runner.) - **plan** — inspect the `look at:` files; decide the smallest change toward the goal. If the loop declares `use skills:`, you may invoke those named skills (via the Skill tool) to do the work — coordinate them rather than re-deriving everything inline. - - **ctx skills** — if the loop declares `recommend skills with ctx` / - `use skills recommended by ctx`, resolve the bundle once at the start of the run: call - `ctx__loop_provision` with the goal (and any `for ""`) to install the skills - and get their names, then treat those as part of `use skills:` for this run. If the - loop also says `top up skills from ctx`, call `ctx__loop_topup` with your reflection - after a failed cycle and fold any new skills in before re-planning. If the ctx tools - aren't attached, skip this and run with whatever `use skills:` already names. - **act** — make the edits. Honor the policy: for `ask me before `, ask the user before doing X (migrations, pushes, etc.); auto classes you may do directly. - **observe** — run the `done when` check and read pass/fail. For a command or named test, diff --git a/.gitignore b/.gitignore index 855dc84..aa5de18 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ packages/cli/.claude/ # Synced from the repo template library at build (single source of truth: /templates) packages/vscode/templates/ +.env* diff --git a/AGENTS.md b/AGENTS.md index 60bd249..0261edc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -69,8 +69,6 @@ allow edits automatically, but ask me before action policy each cycle: plan, then act, then observe the repeated steps (any subset, in order) also: , extra finishing passes run after the goal is met use skills: , named skills the loop may invoke during plan/act -use skills recommended by ctx let ctx pick + install the skills for the goal (needs the ctx MCP server); add `for ""` to override the query -top up skills from ctx run-time: pull more skills from ctx when a cycle fails and reflects (pairs with the line above) remember in "" cross-run memory: read lessons on start, append an outcome on stop reflect turn a failure into context for the next plan (the back-edge) @@ -91,9 +89,6 @@ each cycle: plan, then act, then observe (config tier: the default cycle for e rigor: vibe coding | structured ai-assisted | agentic engineering (the spectrum dial; structured/agentic give every loop a back-edge + thrash guard for free) mode: conductor | orchestrator (supervision posture: in-session/sync vs async/opens-a-PR) runs as: (an auditable principal for unattended runs) -recommend skills with ctx (config tier: ctx is this file's skill source — recommends + installs skills per loop goal; see "Skill source: ctx" below) -grant ctx: skills, agents, mcps, harnesses (config tier: capability groups the file lets ctx recommend; fails closed, default skills+agents; mcps/harnesses are recommend-only) -ctx may use my own model "/" (config tier: declares a user-owned/local/API model — unlocks ctx harness recommendations, always dry-run) observe: (config-tier block) trace every cycle / meter tokens and cost / stop and warn if cost exceeds "$N" sandbox: (config-tier block) no network access / allow egress to "host" only / cap cpu at … memory at … time at … hooks: (loop body block) before each cycle | after act | on commit | on stop : "" passes|finds nothing (a failing hook blocks) @@ -160,51 +155,6 @@ then have the loop coordinate them. Don't invent a loop around skills that don't prove the skill manually, then wire it in (as an execution skill via `use skills:`, or as a verifier via `done when the skill "…" approves`). See `examples/skills_memory.loop`. -### Skill source: ctx — let a recommender pick + install the skills - -`use skills:` assumes the skills already exist in `~/.claude/skills`. **ctx** -([claude-ctx](https://github.com/stevesolun/ctx)) fills that gap: point it at a goal and it -recommends + installs the smallest useful bundle so the names resolve. - -```loop -recommend skills with ctx # config tier: ctx is this file's skill source - -loop "harden the stripe webhook handler": - goal: webhook retries are idempotent and signature-checked, with tests - use skills recommended by ctx for "stripe webhook idempotency" # bake at author time, resolve at run time - top up skills from ctx when a step needs more # pull more on a failing cycle - done when "pnpm test api/webhooks" passes -``` - -- **Author time** (`/loopflow`): ctx recommends for the goal, you approve, the names are - installed and written into a `use skills:` line so the `.loop` stays self-contained. -- **Run time** (`loop run`): the runtime resolves `use skills recommended by ctx` via the ctx - MCP server before the first plan, and `top up skills from ctx` after a failed cycle reflects. -- **No ctx attached?** The lines are inert — the loop runs exactly as it would without them. - -**Beyond skills — the full capability set.** By default ctx provisions only `skills` -(and the agents Loop loads the same way). A `grant ctx:` line widens what ctx may recommend to -any of `skills, agents, mcps, harnesses`, **failing closed** — only listed groups are returned: - -```loop -recommend skills with ctx -grant ctx: skills, agents, mcps, harnesses # capability grants (fail-closed) -ctx may use my own model "ollama/llama3.1" # unlocks harness recs (dry-run only) - -loop "stand up a local agent loop": - goal: an MCP agent loop running on local ollama with filesystem access - use skills recommended by ctx - done when "pytest tests/agent_loop" passes -``` - -- **skills / agents** — install into `~/.claude/skills`, merge into the cycle's skill set. -- **mcps** — recommend-only: surfaced on a `ctx` event, never auto-registered. -- **harnesses** — only with `ctx may use my own model "…"`, ship as an explicit `--dry-run` - command, never auto-install. - -Setup: `claude mcp add ctx -- ctx-mcp-server` (needs `pip install claude-ctx`). Full -walkthrough: `docs/ctx-integration-guide.md`. - ### `remember in` — cross-run memory A loop forgets everything between runs unless you give it a memory file. `remember in` makes diff --git a/CHANGELOG.md b/CHANGELOG.md index 58f6d26..a7339b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ Versions track the `@loop-lang/loop` installer package. ## [Unreleased] +### Removed +- **External skill-recommender integration** — the external skill-source clauses have been + removed from the grammar, runtime, and docs. The plain `use skills: , ` clause and + named-skill verifiers (`done when the skill "…" approves`) are unaffected — list resolved + skill names directly. The removed lines no longer parse; delete them from any existing + `.loop` files. + ## [0.7.1] — 2026-07-02 > `@loop-lang/loop` 0.7.1 · `@loop-lang/{parser,runtime,stdlib,viz}` 0.4.1 · `loopflow` (vscode) 0.5.4 @@ -28,11 +35,6 @@ Versions track the `@loop-lang/loop` installer package. > `@loop-lang/loop` 0.7.0 · `@loop-lang/{parser,runtime,stdlib,viz}` 0.4.0 · `loopflow` (vscode) 0.5.0 ### Added -- **ctx as a skill source** — `recommend skills with ctx` / `use skills recommended by ctx` - / `top up skills from ctx`: a loop equips itself via the ctx MCP server before the first - plan and re-equips after a failed cycle reflects. Capability grants - (`grant ctx: skills, agents, mcps, harnesses`, fail-closed) and own-model gating - (`ctx may use my own model "…"`, dry-run-only harness recommendations). - **Verification reliability** — the flake guard (`done when "…" passes 3 times`: every run must pass, first failure short-circuits) and judge panels (`the skill "…" approves by 3 judges`: majority of independent verdicts, early-exit @@ -56,8 +58,8 @@ Versions track the `@loop-lang/loop` installer package. ### Changed - **New logo** — the gap ring (one ring, one gap: the loop still iterating), with a solid-tile variant as the favicon / app icon across the site and README. -- `loopflow` (vscode) 0.5.0 — bundles the 0.4.0 parser (judge panels, flake guard, ctx - lines all recognized); output panel renders `⏩ resumed` events. +- `loopflow` (vscode) 0.5.0 — bundles the 0.4.0 parser (judge panels, flake guard all + recognized); output panel renders `⏩ resumed` events. ## [0.6.0] — 2026-06-29 diff --git a/README.md b/README.md index 543bd0d..d61dc66 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ Taught with a worked example in the [tutorial](https://loopflow.live/#evals); fu ## Skills and memory -A loop can coordinate proven skills (`use skills: check-weather, analyze-workout`), let a review skill be the verdict (`done when the skill "workout-review" approves`), and keep cross-run memory in a markdown file (`remember in "morning-run.memory.md"`). With [ctx](https://github.com/stevesolun/ctx) attached as the skill source, `use skills recommended by ctx` provisions the right bundle before the first plan — opt-in, fail-closed, inert without ctx. Details: [manual](docs/MANUAL.md#skill-source-ctx), [integration guide](docs/ctx-integration-guide.md), [`examples/skills_memory.loop`](examples/skills_memory.loop), [`examples/ctx_capabilities.loop`](examples/ctx_capabilities.loop). +A loop can coordinate proven skills (`use skills: check-weather, analyze-workout`), let a review skill be the verdict (`done when the skill "workout-review" approves`), and keep cross-run memory in a markdown file (`remember in "morning-run.memory.md"`). Details: [manual](docs/MANUAL.md), [`examples/skills_memory.loop`](examples/skills_memory.loop). ## The vocabulary — learn it once diff --git a/docs/MANUAL.md b/docs/MANUAL.md index f335e01..89b0e17 100644 --- a/docs/MANUAL.md +++ b/docs/MANUAL.md @@ -89,7 +89,7 @@ loop "": | Line | Zone | |---|---| -| `use skills:` / `use skills recommended by ctx` / `remember in` / `knowledge:` / `examples:` | 2 · boundaries (capabilities & context) | +| `use skills:` / `remember in` / `knowledge:` / `examples:` | 2 · boundaries (capabilities & context) | | `plan from ""` | 2 · boundaries (the plan is an input) | | `also:` (finishing passes) | 3 · engine (extra movement after the goal) | | `hooks:` | 4 · safety net (deterministic checkpoints) | @@ -220,7 +220,7 @@ self-contained (no external assets) and binds to `127.0.0.1` only. Every meaningful thing a run does is a **structured event** — `loop-start`, each `node-enter` / `node-exit` (with the attempt number), `observe` (pass/fail + output), -`transition`, `reflect`, `loop-back`, human gates, `ctx` provision/top-up, `git` actions, +`transition`, `reflect`, `loop-back`, human gates, `git` actions, `hook` results, the `model` tier per phase, `stop` (with the reason), `loop-end`, and the pipeline / flow / for-each envelopes. The live dashboard renders this stream; you can also **persist it** — to a local file and/or a remote collector — for auditing, debugging a @@ -599,30 +599,6 @@ These add agentic-engineering discipline to Loop. All are optional; a simple loo See [`examples/agentic/`](../examples/agentic/) for one file per feature; run `loop-run explain ` on any of them to read it back in plain English. -### Skill source: ctx - -[ctx](https://github.com/stevesolun/ctx) can act as a file's **skill source** — -recommending and installing capabilities per loop goal. All lines are opt-in and **inert -without the ctx MCP server** (the loop runs exactly as it would without them). - -| Line | Tier | What it does | -|---|---|---| -| `recommend skills with ctx` | config | ctx is this file's skill source. | -| `use skills recommended by ctx` (optional `for ""`) | loop body | Resolve + install the skill bundle for the goal before the first plan; `for "…"` overrides the query. | -| `top up skills from ctx` (optional `when a step needs more`) | loop body | Pull more skills after a failed cycle reflects. | -| `grant ctx: skills, agents, mcps, harnesses` | config | Capability groups ctx may recommend — **fail-closed** (default `skills, agents`; only listed groups are returned). | -| `ctx may use my own model "/"` | config | Declares a user-owned/local/API model — unlocks harness recommendations. | - -Semantics of the groups: **skills / agents** install into `~/.claude/skills` and merge into -the cycle's skill set. **mcps** are recommend-only — surfaced on a `ctx` event with a -suggested install command, never auto-registered. **harnesses** recommend only when an -own-model is declared, and ship as an explicit `--dry-run` install command — never an -automatic install. - -Setup and the full capability walkthrough: [ctx-integration-guide](ctx-integration-guide.md). -Examples: [`examples/ctx_skills.loop`](../examples/ctx_skills.loop), -[`examples/ctx_capabilities.loop`](../examples/ctx_capabilities.loop). - ### Git strategy A `git:` block sets the version-control strategy for the run. It can appear at the top of diff --git a/docs/ctx-integration-guide.md b/docs/ctx-integration-guide.md deleted file mode 100644 index 86b1269..0000000 --- a/docs/ctx-integration-guide.md +++ /dev/null @@ -1,388 +0,0 @@ -# Loop × ctx — the self-equipping coding loop - -> Your loop already knows *what* to build and *how to check it's done*. -> ctx makes it know *what to bring* — the skills, agents, MCP servers, and model -> harnesses the job needs — and loads them before the first plan. - -This is the complete guide to the Loop ⇄ ctx integration: what it is, why it -matters, how to set it up, and how to drive the full capability set — including -running on **your own local or API model**. - ---- - -## 1. The 60-second pitch - -A `.loop` file is a plain-English, self-correcting workflow: a goal, a way to -verify "done", human gates, and a retry edge. It already runs your agent in a -tight plan → act → observe → reflect cycle until the tests pass. - -The one thing a loop *couldn't* do was **equip itself**. `use skills: a, b` -assumes `a` and `b` already exist on disk. Someone had to know the right skills, -find them, and install them by hand. - -**ctx closes that gap.** Point a loop at a goal and ctx recommends the smallest -useful bundle of capabilities for it and provisions them — so the loop walks in -already holding the right tools: - -- **Skills & agents** — installed straight into `~/.claude/skills`, ready for the - loop's very first plan. -- **MCP servers** — recommended with a one-line install command (e.g. a - filesystem or database server the goal implies). -- **Model harnesses** — when you bring your own model (local Ollama, an API - model), ctx recommends a fitting agent harness (AutoGen, Langfuse, …) as a - ready-to-run, **dry-run** install command. - -It is **opt-in, fail-closed, and human-gated by design.** A loop with no ctx -attached runs exactly as before. Nothing heavier than a skill is ever installed -without you asking. - -**The outcome you're buying:** stop hand-curating tooling for every workflow. -Describe the goal; the loop arrives equipped. - ---- - -## 2. The problem it solves - -Teams writing agentic workflows hit the same wall: - -| Without ctx | With ctx | -|---|---| -| You must already know which skills a task needs. | Describe the goal; ctx recommends the bundle. | -| Skills are installed by hand, per machine, per person. | The loop installs them at run time, reproducibly. | -| MCP servers and model harnesses are wired up manually. | Recommended for the goal, with the exact install command. | -| "Bring your own model" means assembling a harness yourself. | Declare your model; ctx recommends a fitting harness. | -| Tooling drift between author's box and CI. | The `.loop` re-resolves its bundle on every headless run. | - -ctx is the **provisioning layer beneath Loop**. Loop stays the driver; ctx is -the quartermaster. - ---- - -## 3. What you get — the capability set - -ctx recommends across four capability groups. A `.loop` *grants* which ones -apply (see §6). Each group behaves differently, on purpose: - -| Group | Installed automatically? | What happens | -|-------|--------------------------|--------------| -| **skills** | ✅ into `~/.claude/skills` | Merged into the loop's skill set for plan/act. | -| **agents** | ✅ into `~/.claude` | Sub-agents the loop can invoke, loaded the same way. | -| **mcps** | ❌ **recommend-only** | Fitting MCP servers surfaced with a `ctx-mcp-install ` command. The loop never auto-registers one. | -| **harnesses** | ❌ **recommend-only, gated** | Recommended only when you declare your own model; shipped as a `ctx-harness-install --dry-run` command you run. Never auto-installed. | - -**Why the split?** Skills and agents are small, sandboxed, and the loop needs -them in hand to work. MCP servers and harnesses pull real software and touch your -machine's configuration — so ctx *recommends* them and hands you the exact -command, but the decision to install stays yours. That's the trust boundary that -makes this safe to run unattended. - ---- - -## 4. How it works - -``` - ┌────────────┐ grant + goal + own-model ┌─────────────────┐ - You → │ .loop │ ────────────────────────────► │ ctx-mcp-server │ - │ (Loop) │ ◄──────────────────────────── │ (recommender) │ - └─────┬──────┘ ctx.loop_adapter.v1 contract └────────┬────────┘ - │ │ - │ skills/agents → installed │ recommend_bundle - │ mcps/harnesses → surfaced (recommend-only) │ + harness recommender - ▼ ▼ - plan → act → observe → reflect ↺ ~/.claude/skills + the graph -``` - -1. A loop that opts into ctx calls `ctx__loop_provision` **once before the first - plan**, passing its goal, the capability grants, and (optionally) your model. -2. ctx returns a single read-only JSON contract (`ctx.loop_adapter.v1`): the - skills/agents it installed, and the MCP servers / harnesses it recommends. -3. The loop merges skills + agents into its working set, and surfaces the - recommend-only items on its event stream for you (or your host) to act on. -4. On a failed cycle, `top up skills from ctx` asks for *more* — the loop learns - what it was missing from the failure and re-equips before the next plan. - -If ctx isn't attached, every ctx line is inert and the loop runs unchanged. A -ctx call that fails emits one "skipped" event and the loop continues. **A loop -never fails because ctx is missing.** - ---- - -## 5. Setup - -### Prerequisites -- [Loop](https://github.com/tickets-forge-dev/loop-lang) (`.loop` runtime / the - `/loopflow` skill in Claude Code). -- Python 3.11+ for ctx. - -### Install & attach ctx - -```bash -# 1. Install ctx and seed its recommendation graph -pip install claude-ctx -ctx-init --graph --model-mode skip # extracts the recommendation graph into ~/.claude/skill-wiki - -# 2. Attach ctx's tools to Claude Code over MCP -claude mcp add ctx -- ctx-mcp-server -claude mcp list # → ctx: ✔ Connected -``` - -That exposes the tools the Loop bridge uses: - -- `ctx__recommend_bundle` — read-only preview of what ctx would recommend. -- `ctx__loop_provision` — recommend + install skills/agents, recommend mcps/harnesses, return the contract. -- `ctx__loop_topup` — the same, for *additional* capabilities after a failed cycle. - -> **No graph yet?** ctx will return an empty (but valid) contract — the loop runs -> on whatever it already names. Re-run `ctx-init --graph` to seed or refresh. - ---- - -## 6. The grammar - -Five lines, all additive, all inert without ctx attached. - -```loop -recommend skills with ctx # config: ctx is this file's capability source -grant ctx: skills, agents, mcps, harnesses # config: which groups ctx may recommend (fail-closed) -ctx may use my own model "ollama/llama3.1" # config: declare your model → unlocks harnesses - -loop "stand up a local agent loop": - goal: an MCP agent loop on local ollama with filesystem access, with passing tests - use skills recommended by ctx for "local ollama agent loop with filesystem MCP" # loop body - top up skills from ctx when a step needs more # loop body - done when "pytest agent/tests/test_loop.py" passes -``` - -| Line | Tier | Effect | -|------|------|--------| -| `recommend skills with ctx` | config | Declares ctx as the file's capability source. | -| `grant ctx: ` | config | Capability groups ctx may recommend. **Fails closed** — omit it and ctx defaults to `skills + agents`; list only what you want. | -| `ctx may use my own model "/"` | config | Declares a user-owned/local/API model. Required to unlock **harness** recommendations. | -| `use skills recommended by ctx [for ""]` | loop body | Provision the bundle for the goal (or an explicit intent) before the first plan. | -| `top up skills from ctx when a step needs more` | loop body | After a failed cycle reflects, pull additional capabilities before re-planning. | - -### Fail-closed permissions — what it means - -`grant ctx:` is an allow-list, not a wish-list. ctx returns **only** the groups -you name: - -- No `grant ctx:` line → `skills + agents` (the original, safe default). -- `grant ctx: skills` → skills only; agents/mcps/harnesses are never returned. -- `grant ctx: skills, mcps` → skills installed, MCP servers recommended; no agents, no harnesses. - -A typo in a group name grants nothing for that token — it can never accidentally -widen access. - ---- - -## 7. Using your own model (the harness story) - -This is the feature that turns Loop × ctx from "skill installer" into "bring your -own model agent platform". - -If you run on a **local model** (Ollama, llama.cpp) or **your own API model**, -you usually need a *harness* — an agent framework like AutoGen or an -observability layer like Langfuse — wired to that model. ctx recommends one for -your goal and model, and hands you the command to install it. - -### Step 1 — declare your model - -```loop -ctx may use my own model "ollama/llama3.1" -``` - -The string is `"/"`. The provider (before the first `/`) and the -full model id are both passed to ctx so it can score harnesses for your exact -setup. - -### Step 2 — grant the harness group - -```loop -grant ctx: skills, harnesses -``` - -Harnesses are **double-gated**: they're returned only when *both* `harnesses` is -granted *and* a model is declared. Grant `harnesses` without a model and ctx -fails closed with a clear warning instead of recommending something it can't fit: - -```json -"warnings": ["harnesses granted but no user-owned model declared - (set own_llm / model_provider / model) — skipping harness recs."] -``` - -### Step 3 — run, review, install - -ctx returns the recommended harnesses with fit scores and a **dry-run** install -command: - -```json -"capabilities": { - "harnesses": [ - { "name": "autogen", "type": "harness", "fit_score": 1.0, - "install_command": "ctx-harness-install autogen --dry-run" }, - { "name": "langfuse", "type": "harness", "fit_score": 0.9, - "install_command": "ctx-harness-install langfuse --dry-run" } - ] -}, -"harness_install": "ctx-harness-install autogen --dry-run" -``` - -The loop **never installs a harness for you.** It surfaces the command; you run -it. `--dry-run` shows exactly what would be installed before anything touches -your machine. Drop `--dry-run` when you're ready. - -> **Why gated and dry-run?** A harness is the one capability that pulls a full -> framework and runs code against your model. Keeping it an explicit, previewable -> step is what lets you grant `harnesses` in a workflow that otherwise runs -> unattended. - ---- - -## 8. A full worked example - -`examples/ctx_capabilities.loop`: - -```loop -recommend skills with ctx -grant ctx: skills, agents, mcps, harnesses -ctx may use my own model "ollama/llama3.1" - -loop "stand up a local agent loop": - goal: an MCP agent loop running on local ollama with filesystem access, with passing tests - look at: agent/loop.py, agent/tests/test_loop.py - use skills recommended by ctx for "local ollama agent loop with filesystem MCP" - top up skills from ctx when a step needs more - each cycle: plan, then act, then observe - done when "pytest agent/tests/test_loop.py" passes - when it fails: reflect on the failing assertion, then plan again - after 6 tries: stop and warn "local agent loop still red — needs a human" -``` - -Print its shape: - -```bash -loop show examples/ctx_capabilities.loop -``` -``` -loop "stand up a local agent loop" - ↻ plan → act → observe (each cycle) - ↺ on fail: reflect → plan (the back-edge) - ✓ done when: "pytest agent/tests/test_loop.py" passes - ⛔ guard: after 6 tries → stop & warn "local agent loop still red — needs a human" -``` - -Run it: - -```bash -loop run examples/ctx_capabilities.loop --events -``` - -What happens on the first cycle: -1. ctx provisions skills + agents for the goal → installed, merged into the plan. -2. The filesystem MCP server is **recommended** (with its install command) on the - `ctx` event — you decide whether to register it. -3. Because a model is declared, a fitting **harness** is recommended as a dry-run - command. -4. plan → act → observe runs. If the tests fail, `top up skills from ctx` pulls - more before the next plan. - ---- - -## 9. The contract (for integrators) - -Every provision/top-up call returns one stable, versioned JSON object. Build -against it directly if you're embedding Loop or driving ctx from another host: - -```jsonc -{ - "version": "ctx.loop_adapter.v1", - "permissions": { "skills": true, "agents": true, "mcps": true, "harnesses": true }, - "use_skills": ["..."], // skill + agent names now resolvable on disk - "installed": ["..."], // freshly installed this call - "skipped": ["..."], // already present - "unavailable": [{ "name": "...", "status": "not-in-wiki" }], - "recommended": [{ "name": "...", "type": "skill", "score": 146.9 }], - "capabilities": { - "skills": [{ "name": "...", "type": "skill", "status": "installed" }], - "agents": [{ "name": "...", "type": "agent", "status": "installed" }], - "mcps": [{ "name": "...", "type": "mcp-server", "status": "available", - "install_command": "ctx-mcp-install ..." }], - "harnesses": [{ "name": "...", "type": "harness", "fit_score": 1.0, - "install_command": "ctx-harness-install ... --dry-run" }] - }, - "harness_install": "ctx-harness-install ... --dry-run", // or null - "warnings": [] -} -``` - -The contract is **additive and back-compatible**: the original -`use_skills`/`installed`/`skipped` keys are unchanged, so existing skills-only -integrations keep working untouched. - -MCP tool parameters (`ctx__loop_provision` / `ctx__loop_topup`): - -| Param | Type | Meaning | -|-------|------|---------| -| `goal` | string | What the capabilities are for. | -| `intent` | string | Optional query override. | -| `permissions` | string[] | Granted groups. Omit → `skills + agents`. | -| `own_llm` / `model_provider` / `model` | bool / string / string | Your model — unlocks harnesses. | -| `top_k` | int | Recommendations per group (≤ 5). | -| `dry_run` | bool | Recommend without installing skills/agents. | - ---- - -## 10. Safety & trust - -Designed to be safe to grant in unattended workflows: - -- **Opt-in.** No ctx attached → every ctx line is a no-op. Existing loops are unaffected. -- **Fail-closed.** Capabilities are an allow-list. Nothing outside the grant is ever returned. -- **Recommend-only for heavy capabilities.** MCP servers and harnesses are never - auto-installed — ctx hands you the command; you run it. -- **Dry-run by default for harnesses.** See exactly what would be installed first. -- **Human-gated, double-gated for harnesses.** They require both the grant *and* a declared model. -- **Degrades quietly.** A failed ctx call emits one event and the loop continues - with whatever it already names. -- **Reproducible.** Author-time names are baked into a literal `use skills:` line, - while the directive re-resolves on headless runs — so CI matches the author's box. - ---- - -## 11. FAQ - -**Do I have to use ctx?** No. It's entirely optional and opt-in. Loops without -ctx lines behave identically. - -**Will it install things I didn't approve?** Only skills and agents are installed -automatically, and only from groups you granted. MCP servers and harnesses are -never auto-installed. - -**Can I preview before anything changes?** Yes — `ctx__recommend_bundle` is a -read-only preview, and harness/MCP recommendations are always commands you choose -to run. Use `dry_run: true` to recommend skills/agents without installing them. - -**Does it work headless / in CI?** Yes. `loop run ` re-resolves the bundle -through the ctx MCP server, so an unattended run equips itself the same way an -author's session did. - -**What if my goal needs a tool ctx doesn't know?** ctx recommends from its graph; -unknown items simply don't appear. The loop still runs with whatever it names. -Re-seed or extend the graph to teach ctx new capabilities. - -**Local model or API model?** Both. Declare it with -`ctx may use my own model "/"`. That's what unlocks harness -recommendations tuned to your setup. - ---- - -## 12. Reference - -- Worked examples: `examples/ctx_skills.loop` (skills only), - `examples/ctx_capabilities.loop` (full capability set). -- Grammar in context: `AGENTS.md` → *Skill source: ctx*. -- Mechanics & contract: `docs/ctx-skill-source.md`. -- ctx itself: (`pip install claude-ctx`). - -**One line to remember:** *ctx provisions; Loop drives.* You describe the goal — -the loop arrives equipped. diff --git a/docs/ctx-skill-source.md b/docs/ctx-skill-source.md deleted file mode 100644 index d40eae0..0000000 --- a/docs/ctx-skill-source.md +++ /dev/null @@ -1,105 +0,0 @@ -# Skill source: ctx - -Loop names the skills a `.loop` needs by bare string — `use skills: a, b` and -`done when the skill "x" approves` — and assumes they already exist in -`~/.claude/skills`. **[ctx](https://github.com/stevesolun/ctx)** (`pip install -claude-ctx`) is the recommender that fills the gap: point it at a goal and it -recommends the smallest useful skill bundle and installs the bodies into -`~/.claude/skills`, so the names resolve. - -Loop stays the top, user-facing layer; ctx is the layer beneath it that loads -skills straight into the loop. The coupling is loose — ctx is optional, reached -over MCP, and if it isn't attached the loop runs exactly as it would without it. - -## Setup - -```bash -pip install claude-ctx -ctx-init --graph --model-mode skip # seed the recommendation graph -claude mcp add ctx -- ctx-mcp-server # attach ctx's MCP tools -``` - -That exposes the tools the Loop bridge uses: `ctx__recommend_bundle` (preview), -`ctx__loop_provision` (recommend + install + return names), and `ctx__loop_topup` -(add more on a failing cycle). `loop_provision`/`loop_topup` accept an optional -`permissions` array (`skills, agents, mcps, harnesses`) plus `own_llm` / -`model_provider` / `model`, and return the versioned `ctx.loop_adapter.v1` -contract (see *Capability groups* below). - -## Grammar - -Three optional forms, all no-ops when ctx isn't attached: - -```loop -recommend skills with ctx # config tier: ctx is this file's skill source - -loop "harden the stripe webhook handler": - goal: webhook retries are idempotent and signature-checked, with tests - use skills recommended by ctx # resolve a bundle for the goal - use skills recommended by ctx for "stripe webhook idempotency" # ...or an explicit query - top up skills from ctx when a step needs more # run-time: pull more on a failed cycle - done when "pnpm test api/webhooks" passes -``` - -| Form | Tier | Effect | -|------|------|--------| -| `recommend skills with ctx` | config | Declares ctx as the file's skill source. | -| `use skills recommended by ctx [for ""]` | loop body | Author-time: bake resolved names into `use skills:`. Run-time: re-resolve before the first plan. | -| `top up skills from ctx when a step needs more` | loop body | Run-time: after a cycle fails and reflects, pull additional skills before re-planning. | -| `grant ctx: skills, agents, mcps, harnesses` | config | Capability groups the file lets ctx recommend. Fails closed; default (no line) = skills+agents. | -| `ctx may use my own model "/"` | config | Declares a user-owned/local/API model — unlocks harness recommendations (dry-run only). | - -## Capability groups (beyond skills) - -ctx recommends across four entity types; a `.loop` grants which ones apply. The -model **fails closed** — with no `grant ctx:` line the grant defaults to -`skills + agents` (the original behaviour), and only listed groups are ever -returned. - -| Group | Installed? | Behaviour | -|-------|-----------|-----------| -| `skills` | yes → `~/.claude/skills` | Merged into the cycle's skill set, as before. | -| `agents` | yes → `~/.claude` | Loaded the same way Loop loads named (sub)agents. | -| `mcps` | **no — recommend-only** | Fitting MCP servers surfaced with a suggested `ctx-mcp-install `; emitted on the `ctx` event. The runtime never auto-registers one. | -| `harnesses` | **no — recommend-only, gated** | Recommended only when the loop declares a user-owned model (`ctx may use my own model …`); shipped as an explicit `ctx-harness-install --dry-run` command. Never an automatic install. | - -The provision/top-up calls return the `ctx.loop_adapter.v1` contract: -`{ version, permissions, use_skills, installed, skipped, unavailable, -recommended, capabilities{skills,agents,mcps,harnesses}, harness_install, -warnings }`. The runtime merges `use_skills` (skills + agents) into the loop and -surfaces `capabilities.mcps` / `capabilities.harnesses` / `harness_install` on -the `ctx` event for the host or a human to act on — it never installs an MCP -server or a harness on its own. - -See `examples/ctx_capabilities.loop` for the full-capability example. - -## How it works - -**Author time (`/loopflow`).** During the interview ctx recommends for the goal, -you approve, the skills are installed, and their names are written into a literal -`use skills:` line — so the `.loop` stays self-contained. The -`use skills recommended by ctx` directive is kept alongside as the regeneration -record and the run-time trigger. - -**Run time (`loop run`).** The runtime detects the directive, calls the ctx MCP -server (`ctx__loop_provision`) before the first `plan`, and merges the resolved -names into the skill set the spawned `claude` cycles see. With `top up skills -from ctx`, it calls `ctx__loop_topup` with the failing cycle's reflection and -folds in any new skills before re-planning. Both emit a `ctx` event on the -`--events` / live stream, so you can see what was loaded. - -**Degradation.** If the ctx MCP server isn't installed/attached, or a call -fails, the runtime logs one `ctx … (skipped)` event and continues with whatever -skills are already named. A loop never fails because ctx is missing. - -## Why they compose - -ctx's Claude Code integration is *passive*: it installs skills into -`~/.claude/skills` and the manifest; it never drives a model itself (its only -agent loop is the LiteLLM-based `ctx run`). Loop *is* the Claude driver — its -runtime spawns `claude -p … --allowedTools …Skill…`. Both target the **same** -`~/.claude/skills` and the same Claude Code Skill tool, so a skill ctx installs -is immediately resolvable by Loop's cycles. ctx provisions; Loop drives. - -See `examples/ctx_skills.loop` for a worked example, and `AGENTS.md` -(*Skill source: ctx*) for the grammar in context. diff --git a/docs/game.html b/docs/game.html index a55f8ca..4590da3 100644 --- a/docs/game.html +++ b/docs/game.html @@ -34,6 +34,10 @@ margin:0; color:var(--ink); font-family:var(--ui); -webkit-font-smoothing:antialiased; line-height:1.6; background:var(--bg); } +*{scrollbar-width:thin;scrollbar-color:rgba(110,110,115,.28) transparent} +*::-webkit-scrollbar{width:6px;height:6px} +*::-webkit-scrollbar-thumb{background:rgba(110,110,115,.28);border-radius:999px;border:1.5px solid transparent;background-clip:padding-box} +*::-webkit-scrollbar-track{background:transparent} /* orbital atmosphere: drifting glows + faint concentric rings, behind everything */ body::before{content:""; position:fixed; inset:0; z-index:-2; pointer-events:none; background: diff --git a/docs/index.html b/docs/index.html index b6485df..8215f7b 100644 --- a/docs/index.html +++ b/docs/index.html @@ -36,6 +36,10 @@ *{box-sizing:border-box} html{scroll-behavior:smooth} body{margin:0;background:var(--bg);color:var(--ink);font-family:var(--ui);line-height:1.5;-webkit-font-smoothing:antialiased} +*{scrollbar-width:thin;scrollbar-color:rgba(110,110,115,.28) transparent} +*::-webkit-scrollbar{width:6px;height:6px} +*::-webkit-scrollbar-thumb{background:rgba(110,110,115,.28);border-radius:999px;border:1.5px solid transparent;background-clip:padding-box} +*::-webkit-scrollbar-track{background:transparent} a{color:var(--blue);text-decoration:none} a:hover{text-decoration:underline} .shell{max-width:1180px;margin:0 auto;display:flex;align-items:flex-start} nav.toc{position:sticky;top:0;width:260px;flex:0 0 260px;height:100vh;padding:28px 22px;border-right:1px solid var(--line);overflow:auto;background:rgba(251,251,253,.82);backdrop-filter:blur(18px)} @@ -47,6 +51,7 @@ section{border-top:1px solid var(--line);margin-top:10px}details{padding:0}summary{list-style:none;cursor:pointer;padding:24px 0 18px;display:flex;align-items:center;justify-content:space-between;gap:16px}summary::-webkit-details-marker{display:none}summary h2{margin:0;font-size:30px;line-height:1.15;letter-spacing:-.035em}.chev{width:30px;height:30px;border-radius:50%;display:grid;place-items:center;border:1px solid var(--line);background:var(--panel);color:var(--muted);transition:.18s}details[open] .chev{transform:rotate(45deg);color:var(--ink)}.content{padding:0 0 28px}.content>*:first-child{margin-top:0} h3{font-size:19px;letter-spacing:-.02em;margin:26px 0 6px}p{color:#3a3a3c;margin:10px 0}ul,ol{color:#3a3a3c;padding-left:22px}li{margin:6px 0}.grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;margin:16px 0}.card{background:var(--panel);border:1px solid var(--line);border-radius:18px;padding:16px}.card h3{margin-top:0}.card p{margin-bottom:0}.mini{font-size:13px;color:var(--muted)} code.inl,td code{font-family:var(--mono);font-size:.9em;background:var(--panel2);border:1px solid var(--line);border-radius:7px;padding:1px 6px;color:var(--ink)}pre{background:#f5f5f7;border:1px solid var(--line);border-radius:18px;padding:18px;overflow:auto;font-size:13px;line-height:1.6}pre code{font-family:var(--mono);white-space:pre;color:var(--ink)}pre.hero-code{background:#111827;color:white;border-color:#111827;box-shadow:0 18px 50px rgba(0,0,0,.12)}pre.hero-code code{color:white}.caption{font-size:13px;color:var(--muted);margin-top:-6px} +.claude-run{margin:22px 0 6px;border:1px solid var(--line);background:var(--panel);border-radius:18px;padding:15px 18px}.claude-badge{display:inline-block;background:var(--ink);color:#fff;font-size:11px;font-weight:700;letter-spacing:.02em;border-radius:999px;padding:3px 10px;margin-bottom:9px}.claude-run p{margin:6px 0}pre.cmd{background:var(--panel2);border:1px solid var(--line);border-radius:12px;padding:11px 13px;margin:9px 0;font-size:13px;color:var(--ink);overflow:auto}pre.cmd code{font-family:var(--mono);color:var(--ink)}pre.cmd .p{color:var(--soft)} table{width:100%;border-collapse:separate;border-spacing:0;margin:14px 0;border:1px solid var(--line);border-radius:16px;overflow:hidden;background:var(--panel)}th,td{padding:11px 13px;border-bottom:1px solid var(--line);text-align:left;vertical-align:top}tr:last-child td{border-bottom:0}th{font-size:12px;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;background:var(--panel2)}td{color:#3a3a3c}.note{border-left:3px solid var(--blue);background:#eef6ff;border-radius:14px;padding:13px 16px;color:#21364f}.flowline{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:15px 0}.node{background:var(--panel);border:1px solid var(--line);border-radius:999px;padding:8px 12px;font-weight:700}.arrow{color:var(--muted)} footer{border-top:1px solid var(--line);padding-top:20px;margin-top:34px;color:var(--muted);font-size:13px}.tok-kw{color:#0057b8;font-weight:600}.tok-human{color:#9b2d8f;font-weight:600}.tok-str{color:#276b36}.tok-com{color:#8a8a8e;font-style:italic}.tok-num{color:#a35200} @media(max-width:900px){.shell{display:block}nav.toc{position:relative;width:auto;height:auto;border-right:0;border-bottom:1px solid var(--line)}nav.toc ol{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 8px}.grp{grid-column:1/-1}main{padding:28px 20px 70px}h1{font-size:46px}.lede{font-size:20px}.grid{grid-template-columns:1fr}summary h2{font-size:25px}} @@ -92,6 +97,12 @@

Stop babysitting the agent.

Write the loop once. The agent plans, edits, checks, reflects, and stops only when the work is verified.

LoopFlow is a small plain-English language for AI coding workflows. It keeps the important parts visible: goal, files, permissions, checks, humans, and stopping rules.

+
+ Claude Code · easiest start +

Already in Claude Code? Just type /loopflow and say what you want. Nothing to install, no syntax to memorize.

+
> /loopflow fix the checkout tax bug until pnpm test passes
+

The skill interviews you for the goal and the check, writes the loop below with you, shows its shape, then runs it right in the chat — pausing at every human gate.

+
loop "fix checkout tax":
   goal: checkout shows the right tax
   done when "pnpm test checkout" passes
@@ -109,7 +120,7 @@ 

Stop babysitting the agent.

A .loop file is a runbook for an AI agent.

Instead of a one-shot prompt

“Fix it” runs once and may stop early.

-

You write a loop

“Fix it until this real check passes, and ask before risky steps.”

+

You write a loop

/loopflow fix it until this real check passes, and ask before risky steps

The file is plain text. You can review it, commit it, share it, and run it again.

@@ -127,8 +138,13 @@

Stop babysitting the agent.

PromptLoopFlow
Hidden assumptionsExplicit goal, files, checks, gates
Stops after one attemptRetries after real failures
“Looks done”done when proves it
-

vs /loop and /goal

+
-

Built-in agent commands help in one session. LoopFlow gives you a portable file: runnable by people, agents, CI, and future you.

+

Isn’t this just /goal — or a Ralph loop?

+
+

In plain terms: LoopFlow keeps the good part of both — persistence toward a goal — and adds the parts that make it safe to trust and re-run.

+
+

vs a one-shot /goal

A goal command runs once in a session and decides it’s “done” by feel; when the chat ends, nothing is left behind. A .loop is a portable file with a real done when check, human gates, and a stop guard — reviewable, committable, and re-runnable by people, agents, CI, and future you.

+

vs a Ralph loop

“Ralph” is while true: run the same prompt — brute-force repetition with no built-in verification or stopping rule, so you babysit it and kill it by hand. LoopFlow keeps the relentlessness but adds a check it must pass, a thrash guard (after N tries: stop), reflection that feeds each failure back, and gates on risky steps.

+
+

Same idea as both — loop the agent until the goal is met — with verification, stopping conditions, and human checkpoints written down in plain text.

Install + first loop

+
@@ -236,13 +252,28 @@

Minimum useful loop

Project defaults

+
-

Put shared settings in loop.config so every loop inherits them.

-
each cycle: plan, then act, then observe
+  

Create a loop.config file at your repo root to set defaults for every .loop file in the project. Users can edit it any time; a loop file can override these settings at the top of the file, and an individual loop can override the pieces it owns.

+
# loop.config
+live=false
+
+each cycle: plan, then act, then observe
 models: fast haiku, strong opus
+rigor: structured ai-assisted
+mode: conductor
+runs as: developer
+
 git:
   work on a branch
   commit when the goal is met
-  do not push
+ do not push + +observe: + meter tokens and cost + stop and warn if cost exceeds "$5" + +sandbox: + no network access
+
SettingValues you can use
livetrue to show the in-session dashboard, false to run normally.
each cycle:plan, act, observe in the order you want, usually plan, then act, then observe or act, then observe.
models:fast <model>, strong <model>, plus phase overrides like act strong or all fast.
rigor:vibe coding, structured ai-assisted, or agentic engineering.
mode:conductor for in-session supervision, or orchestrator for async/PR-style runs.
git:work in place, work on a branch, work in a worktree; commit options include commit when the goal is met, commit each cycle, commit each story, or commit never; push options are do not push or push when done.
observe:trace every cycle, meter tokens and cost, and stop and warn if cost exceeds "$N".
sandbox:no network access, allow egress to "host" only, or resource caps such as cap cpu at … memory at … time at ….
schedule:, target:, notify:, runner:Operational defaults: when to run, which directory to target, where to report, and which runner/agent to use.

Rigor, mode, hooks, sandbox

+
@@ -282,6 +313,9 @@

Minimum useful loop

What is an AI coding loop?

A workflow where an agent plans, edits, checks, and retries until a real check passes.

How do I make an agent self-correct?

Give it a goal, a real done when check, a reflect step, and a try limit.

How is this different from prompting?

A prompt asks once. A loop keeps going until verified, with human gates for risk.

+

Is this just /goal or a one-shot agent command?

Those run once in a session and judge “done” by feel. LoopFlow is a portable file with a real done when check, a reflect step, human gates, and a stop guard — you can review it, commit it, and run it again.

+

Is it just another Ralph loop?

A Ralph loop repeats one prompt forever with no built-in check or stopping rule, so you watch it and kill it by hand. LoopFlow keeps the persistence but adds verification, a thrash guard, reflection, and human gates — so it stops when the work is actually proven.

+

How easy is the Claude Code skill?

Type /loopflow and describe what you want. It writes the loop with you, shows its shape, and runs it in the chat — no install.

What tools does it work with?

Claude Code via /loopflow, headless via loop-run, and plain text anywhere.

@@ -290,6 +324,9 @@

What tools does it work with?

Claude Code via /loop {"@type":"Question","name":"What is an AI coding loop?","acceptedAnswer":{"@type":"Answer","text":"An AI coding loop is a workflow where an agent plans, edits, checks, and retries until a real check passes."}}, {"@type":"Question","name":"How do I make an agent self-correct?","acceptedAnswer":{"@type":"Answer","text":"Give it a goal, a real done when check, a reflect step, and a try limit."}}, {"@type":"Question","name":"How is LoopFlow different from prompting?","acceptedAnswer":{"@type":"Answer","text":"A prompt asks once. A loop keeps going until verified, with human gates for risk."}}, +{"@type":"Question","name":"Is LoopFlow just /goal or a one-shot agent command?","acceptedAnswer":{"@type":"Answer","text":"A one-shot goal command runs once in a session and judges done by feel. LoopFlow is a portable file with a real done when check, a reflect step, human gates, and a stop guard, so you can review it, commit it, and run it again."}}, +{"@type":"Question","name":"Is LoopFlow just another Ralph loop?","acceptedAnswer":{"@type":"Answer","text":"A Ralph loop repeats one prompt forever with no built-in check or stopping rule. LoopFlow keeps the persistence but adds verification, a thrash guard, reflection, and human gates, so it stops when the work is actually proven."}}, +{"@type":"Question","name":"How easy is the LoopFlow Claude Code skill?","acceptedAnswer":{"@type":"Answer","text":"Type /loopflow in Claude Code and describe what you want. The skill writes the loop with you, shows its shape, and runs it in the chat, with no install."}}, {"@type":"Question","name":"What tools does LoopFlow work with?","acceptedAnswer":{"@type":"Answer","text":"Claude Code via /loopflow, headless via loop-run, and plain text anywhere."}} ]} diff --git a/docs/keywords/style.css b/docs/keywords/style.css index b88693b..b973838 100644 --- a/docs/keywords/style.css +++ b/docs/keywords/style.css @@ -9,6 +9,10 @@ *{box-sizing:border-box;} html{scroll-behavior:smooth;} body{margin:0; color:var(--ink); font-family:var(--ui); -webkit-font-smoothing:antialiased; line-height:1.6; background:var(--bg);} +*{scrollbar-width:thin;scrollbar-color:rgba(110,110,115,.28) transparent} +*::-webkit-scrollbar{width:6px;height:6px} +*::-webkit-scrollbar-thumb{background:rgba(110,110,115,.28);border-radius:999px;border:1.5px solid transparent;background-clip:padding-box} +*::-webkit-scrollbar-track{background:transparent} body::before{content:""; position:fixed; inset:0; z-index:-2; pointer-events:none; background: radial-gradient(60% 46% at 88% -6%, rgba(52,224,196,.16), transparent 60%), diff --git a/docs/keywords/use-skills.html b/docs/keywords/use-skills.html index 91707f7..af70caf 100644 --- a/docs/keywords/use-skills.html +++ b/docs/keywords/use-skills.html @@ -39,7 +39,6 @@

Syntax

What it does

Lists the skills the agent is allowed to call while it plans and acts — so the loop coordinates proven, reusable skills instead of one giant prompt. This is skill-driven development: you build and battle-test each skill on its own, then wire the loop to orchestrate them. The names must already resolve (in ~/.claude/skills); if they don't exist yet, prove the skill by hand first, then add it here.

Why it matters: a monolithic prompt is hard to debug and impossible to reuse. Naming skills keeps each capability small, independently tested, and shared across loops — the loop's job shrinks to deciding when to call each one. There are two roles a skill can play, and they're distinct. As an execution skill listed under use skills:, it's a tool the agent may invoke during a cycle. As a verifier, it decides the verdict via done when the skill "<name>" approves (or scores 8 or more, or approves by 3 judges). Pairing the two — an execution skill that does the work and a review skill that judges it — is the common shape.

-

If the skills don't exist locally, let a recommender install them: use skills recommended by ctx resolves the bundle at run time (with recommend skills with ctx at the config tier). With no ctx attached those lines are inert and the loop runs unchanged.

Example

loop "harden the upload endpoint":
   goal: no high-severity findings in the upload path
@@ -48,21 +47,11 @@ 

Example

each cycle: plan, then act, then observe when it fails: reflect, then plan again
a loop that drives two named skills -

Example — skills chosen by ctx

-
recommend skills with ctx              # config tier: ctx is the skill source
-
-loop "harden the stripe webhook handler":
-  goal: webhook retries are idempotent and signature-checked, with tests
-  use skills recommended by ctx for "stripe webhook idempotency"
-  top up skills from ctx when a step needs more
-  done when "pnpm test api/webhooks" passes
let ctx pick + install the bundle -

Common mistakes

    -
  • Naming a skill that doesn't exist. use skills: assumes the names resolve in ~/.claude/skills. Inventing a skill around capabilities you haven't built means the loop calls nothing. Prove the skill manually first, or let ctx install it.
  • +
  • Naming a skill that doesn't exist. use skills: assumes the names resolve in ~/.claude/skills. Inventing a skill around capabilities you haven't built means the loop calls nothing. Prove the skill manually first, then add it here.
  • Confusing the execution role with the verifier role. Listing a skill under use skills: only permits the agent to call it — it does not make it the finish line. To let a skill decide "done", it must appear in a done when the skill "…" approves predicate.
  • Trusting a green test alone when "done" also means "built right". An execution skill can make a test pass the wrong way. Add a trajectory eval — done when the skill "…" approves on the trajectory with a the bar: line — to judge how the work was done, not just the result.
  • -
  • Assuming ctx lines do something without ctx. use skills recommended by ctx is inert unless the ctx MCP server is attached; it won't error, but it also won't install anything. For a self-contained file, list the resolved skills under use skills:.

Related

diff --git a/docs/playground.html b/docs/playground.html index d3019db..58a5045 100644 --- a/docs/playground.html +++ b/docs/playground.html @@ -32,6 +32,10 @@ } *{box-sizing:border-box} body{margin:0;color:var(--ink);font-family:var(--ui);background:var(--bg);line-height:1.55;-webkit-font-smoothing:antialiased} + *{scrollbar-width:thin;scrollbar-color:rgba(110,110,115,.28) transparent} + *::-webkit-scrollbar{width:6px;height:6px} + *::-webkit-scrollbar-thumb{background:rgba(110,110,115,.28);border-radius:999px;border:1.5px solid transparent;background-clip:padding-box} + *::-webkit-scrollbar-track{background:transparent} header{display:flex;align-items:center;gap:14px;padding:14px 22px;border-bottom:1px solid var(--line)} header h1{font-size:16px;margin:0;font-weight:700} header .tag{font-size:12px;color:var(--muted)} @@ -47,7 +51,10 @@ .bar .status.ok{color:var(--stop)} .bar .status.err{color:var(--err)} textarea{flex:1;width:100%;resize:none;border:0;outline:0;background:#fff;color:var(--ink); font-family:var(--mono);font-size:14px;line-height:1.6;padding:16px 18px;tab-size:2;white-space:pre} - .out{flex:1;overflow:auto;padding:16px 18px} + .out{flex:1;overflow:auto;padding:16px 18px;scrollbar-width:thin;scrollbar-color:rgba(110,110,115,.28) transparent} + .out::-webkit-scrollbar{width:6px;height:6px} + .out::-webkit-scrollbar-thumb{background:rgba(110,110,115,.28);border-radius:999px;border:1.5px solid transparent;background-clip:padding-box} + .out::-webkit-scrollbar-track{background:transparent} .out h3{font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--muted);margin:18px 0 8px;font-weight:700} .out h3:first-child{margin-top:0} pre{margin:0;font-family:var(--mono);font-size:13.5px;line-height:1.65;white-space:pre-wrap} diff --git a/docs/playground/loop-lang.js b/docs/playground/loop-lang.js index 88ea8bb..08eb149 100644 --- a/docs/playground/loop-lang.js +++ b/docs/playground/loop-lang.js @@ -1,12 +1,12 @@ -var Loop=(()=>{var y=Object.defineProperty;var K=Object.getOwnPropertyDescriptor;var Y=Object.getOwnPropertyNames;var X=Object.prototype.hasOwnProperty;var J=(e,n)=>{for(var o in n)y(e,o,{get:n[o],enumerable:!0})},Q=(e,n,o,t)=>{if(n&&typeof n=="object"||typeof n=="function")for(let i of Y(n))!X.call(e,i)&&i!==o&&y(e,i,{get:()=>n[i],enumerable:!(t=K(n,i))||t.enumerable});return e};var Z=e=>Q(y({},"__esModule",{value:!0}),e);var Pe={};J(Pe,{ParseError:()=>u,explainFile:()=>_,lint:()=>U,parse:()=>x,renderFile:()=>H});var S="0.1",u=class extends Error{constructor(o,t){super(`Loop parse error (line ${t}): ${o}`);this.line=t;this.name="ParseError"}};var j=["vibe coding","structured ai-assisted","agentic engineering"],ee={skills:"skills",skill:"skills",agents:"agents",agent:"agents",mcps:"mcps",mcp:"mcps","mcp-server":"mcps","mcp-servers":"mcps",harnesses:"harnesses",harness:"harnesses"},te={"before each cycle":"before-cycle","after plan":"after-plan","after act":"after-act","after observe":"after-observe","on commit":"on-commit","on push":"on-push","on stop":"on-stop"};function ne(e){let n=e.trim().toLowerCase().replace(/[.,]$/,"");return{edit:"edit",edits:"edit",editing:"edit",migration:"migrate",migrations:"migrate",migrate:"migrate",push:"push",pushes:"push",pushing:"push",deploy:"deploy",deploys:"deploy",deployment:"deploy",deployments:"deploy",delete:"delete",deletes:"delete",deletion:"delete",deletions:"delete"}[n]??n}function C(e){return e.split(/,|\bor\b|\band\b/).map(n=>ne(n)).filter(n=>n.length>0)}function oe(e){let n=[];return e.replace(/\r\n/g,` +var Loop=(()=>{var y=Object.defineProperty;var Y=Object.getOwnPropertyDescriptor;var U=Object.getOwnPropertyNames;var J=Object.prototype.hasOwnProperty;var Q=(e,t)=>{for(var o in t)y(e,o,{get:t[o],enumerable:!0})},X=(e,t,o,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of U(t))!J.call(e,i)&&i!==o&&y(e,i,{get:()=>t[i],enumerable:!(n=Y(t,i))||n.enumerable});return e};var Z=e=>X(y({},"__esModule",{value:!0}),e);var Le={};Q(Le,{ParseError:()=>u,explainFile:()=>_,lint:()=>K,parse:()=>x,renderFile:()=>H});var S="0.1",u=class extends Error{constructor(o,n){super(`Loop parse error (line ${n}): ${o}`);this.line=n;this.name="ParseError"}};var j=["vibe coding","structured ai-assisted","agentic engineering"],ee={"before each cycle":"before-cycle","after plan":"after-plan","after act":"after-act","after observe":"after-observe","on commit":"on-commit","on push":"on-push","on stop":"on-stop"};function te(e){let t=e.trim().toLowerCase().replace(/[.,]$/,"");return{edit:"edit",edits:"edit",editing:"edit",migration:"migrate",migrations:"migrate",migrate:"migrate",push:"push",pushes:"push",pushing:"push",deploy:"deploy",deploys:"deploy",deployment:"deploy",deployments:"deploy",delete:"delete",deletes:"delete",deletion:"delete",deletions:"delete"}[t]??t}function C(e){return e.split(/,|\bor\b|\band\b/).map(t=>te(t)).filter(t=>t.length>0)}function ne(e){let t=[];return e.replace(/\r\n/g,` `).split(` -`).forEach((t,i)=>{let s=i+1,p="",l=!1;for(let h=0;ho;)t.push(e[i]),i++;return{body:t,next:i}}function b(e){let n=e.match(/"([^"]*)"/);return n?n[1]:null}function k(e,n){let o=e.trim(),t=l=>{let a=l?parseInt(l,10):1;return a>1?{runs:a}:{}},i=o.match(/^the test\s+"([^"]+)"\s+passes(?:\s+(\d+)\s+times?)?$/i);if(i)return{type:"test",target:i[1],...t(i[2])};if(i=o.match(/^"([^"]+)"\s+finds nothing(?:\s+(\d+)\s+times?)?$/i),i)return{type:"command",command:i[1],expect:"empty",...t(i[2])};if(i=o.match(/^"([^"]+)"\s+(?:passes|succeeds)(?:\s+(\d+)\s+times?)?$/i),i)return{type:"command",command:i[1],expect:"exit-zero",...t(i[2])};if(i=o.match(/^a human confirms\s+"([^"]+)"$/i),i)return{type:"human",description:i[1]};let s=l=>l?{subject:l.toLowerCase()}:{},p=l=>{let a=l?parseInt(l,10):1;return a>1?{judges:a}:{}};if(i=o.match(/^the skill\s+"([^"]+)"\s+scores\s+(\d+)(?:\s+or more)?(?:\s+on the (output|trajectory))?(?:\s+by\s+(\d+)\s+judges?)?$/i),i)return{type:"skill",skill:i[1],expect:"approve",minScore:parseInt(i[2],10),...s(i[3]),...p(i[4])};if(i=o.match(/^the skill\s+"([^"]+)"\s+approves(?:\s+on the (output|trajectory))?(?:\s+by\s+(\d+)\s+judges?)?$/i),i)return{type:"skill",skill:i[1],expect:"approve",...s(i[2]),...p(i[3])};throw new u(`could not understand "done when ${o}"`,n)}function A(e,n){let o=e.split(/,|\bthen\b/).map(i=>i.trim()).filter(i=>i.length>0),t=[];for(let i of o){let s=i.toLowerCase(),p=i.match(/^stop and warn\s+"([^"]+)"$/i);if(p){t.push({action:"stop",warn:p[1]});continue}if(s==="stop"){t.push({action:"stop"});continue}if(p=i.match(/^reflect(?:\s+on\s+(.+))?$/i),p){t.push(p[1]?{action:"reflect",focus:p[1].trim()}:{action:"reflect"});continue}if(s==="plan"||s==="plan again"||s==="replan"){t.push({action:"plan"});continue}if(s==="act"||s==="act again"){t.push({action:"act"});continue}if(s==="observe"){t.push({action:"observe"});continue}if(s==="ask a human"||s==="ask the human"||s==="ask human"){t.push({action:"ask-human"});continue}throw new u(`unknown action "${i}"`,n)}if(t.length===0)throw new u("expected at least one action",n);return t}function ie(e,n){let o=e.trim().toLowerCase();if(/^it passes and (the )?goal is met$/.test(o))return{on:"pass",requireGoalMet:!0};if(/^it passes$/.test(o))return{on:"pass"};if(/^it (fails|breaks)$/.test(o))return{on:"fail"};if(/^(it is |it gets )?(blocked|stuck)$/.test(o))return{on:"blocked"};throw new u(`unknown condition "when ${e}"`,n)}function se(e,n){let o=e.match(/^(before each cycle|after plan|after act|after observe|on commit|on push|on stop):\s*(.+)$/i);if(!o)throw new u(`unrecognized hook "${e}" (expected e.g. \`on commit: "cmd" finds nothing\`)`,n);let t=k(o[2],n);if(t.type!=="command"&&t.type!=="test")throw new u(`a hook must be a deterministic check (a command or test), not "${o[2]}"`,n);return{at:te[o[1].toLowerCase()],predicate:t}}function O(e,n){let o=e.split(/,|\bthen\b/).map(i=>i.trim().toLowerCase()).filter(i=>i.length>0),t=[];for(let i of o)if(i==="plan"||i==="act"||i==="observe")t.push(i);else throw new u(`unknown cycle step "${i}" (expected plan, act, or observe)`,n);if(t.length===0)throw new u("empty cycle",n);return t}function re(e,n,o){let t;if(/^work in place$/i.test(n)){e.isolation="in-place";return}if(t=n.match(/^work on a branch(?:\s+"([^"]+)")?$/i)){e.isolation="branch",t[1]&&(e.branch=t[1]);return}if(t=n.match(/^work in a worktree(?:\s+"([^"]+)")?$/i)){e.isolation="worktree",t[1]&&(e.branch=t[1]);return}if(/^commit when (?:the goal is met|done)$/i.test(n)){e.commit="done";return}if(/^commit each cycle$/i.test(n)){e.commit="cycle";return}if(/^commit each story$/i.test(n)){e.commit="story";return}if(/^(?:commit never|do not commit)$/i.test(n)){e.commit="never";return}if(/^(?:push when done|push)$/i.test(n)){e.push=!0;return}if(/^do not push$/i.test(n)){e.push=!1;return}if(/^open a (?:pull request|pr)$/i.test(n)){e.openPr=!0;return}throw new u(`unrecognized git line: "${n}"`,o)}function F(e,n){let o=e[n],{body:t,next:i}=w(e,n+1,o.indent);if(t.length===0)throw new u("empty git block",o.lineNo);let s={};for(let p of t)re(s,p.text,p.lineNo);return{git:s,next:i}}var E=["plan","act","reflect","also"];function T(e,n){let o={},t,i=new Set;for(let s of e.split(",")){let p=s.trim();if(!p)continue;let l=p.split(/\s+/),a=l[0].toLowerCase(),h=r=>{let c=l[r]?.toLowerCase();return c==="fast"||c==="strong"?c:void 0};if(a==="all"){let r=h(1);if(l.length!==2||!r)throw new u(`models: "all" needs a tier (fast|strong): "${p}"`,n);t=r}else if(a==="fast"||a==="strong"){if(l.length!==2)throw new u(`models: tier "${a}" needs one model: "${p}"`,n);(o.tiers??={})[a]=l[1]}else if(E.includes(a)){let r=h(1);if(l.length!==2||!r)throw new u(`models: phase "${a}" needs a tier (fast|strong): "${p}"`,n);(o.phases??={})[a]=r,i.add(a)}else{if(a==="observe")continue;throw new u(`models: unrecognized clause "${p}"`,n)}}if(t!==void 0){o.phases??={};for(let s of E)i.has(s)||(o.phases[s]=t)}return o}function M(e,n,o){let t={kind:"loop",name:e,goal:"",cycle:[]},i=null,s=!1,p=!1,l=0;for(;la.indent;)c.push(se(n[f].text,n[f].lineNo)),f++;if(c.length===0)throw new u("'hooks:' block is empty",a.lineNo);t.hooks=c,l=f;continue}if(r=h.match(/^models:\s*(.+)$/i)){t.models=T(r[1],a.lineNo),l++;continue}if(r=h.match(/^goal:\s*(.+)$/i)){t.goal=r[1].trim(),s=!0,l++;continue}if(r=h.match(/^done when\s+(.+)$/i)){let c=k(r[1],a.lineNo),f=n[l+1];if(f&&f.indent>a.indent){let d=f.text.match(/^the bar:\s*(.+)$/i);if(d){if(c.type!=="skill")throw new u(`'the bar:' applies to a skill eval, not "${r[1].trim()}"`,f.lineNo);c.bar=d[1].trim(),l++}}(t.doneWhen??=[]).push(c),l++;continue}if(r=h.match(/^(?:look at|look in|files|context|in):\s*(.+)$/i)){t.context=ae(r[1]),l++;continue}if(r=h.match(/^(?:check|verify):\s*(.+)$/i)){let c=r[1].trim(),d=/^(the test|the skill|a human)\b/i.test(c)||/^".*"\s+(passes|succeeds|finds nothing)(\s+\d+\s+times?)?$/i.test(c)?k(c,a.lineNo):{type:"command",command:c.replace(/^"|"$/g,""),expect:"exit-zero"};(t.doneWhen??=[]).push(d),l++;continue}if(/^allow\b/i.test(h)||/^ask me before\b/i.test(h)){le(t,h),l++;continue}if(r=h.match(/^(?:then\s+)?each cycle:\s*(.+)$/i)){t.cycle=O(r[1],a.lineNo),p=!0,l++;continue}if(r=h.match(/^also(?:\s+do)?:\s*(.+)$/i)){t.also=r[1].split(",").map(c=>c.trim()).filter(c=>c.length>0),l++;continue}if(r=h.match(/^use skills?:\s*(.+)$/i)){t.skills=r[1].split(/,|\band\b/).map(c=>c.trim()).filter(c=>c.length>0),l++;continue}if(r=h.match(/^use skills recommended by ctx(?:\s+for\s+"([^"]+)")?$/i)){t.skillDiscovery=r[1]?{provider:"ctx",intent:r[1].trim()}:{provider:"ctx"},l++;continue}if(/^top up skills from ctx(?:\s+when a step needs more)?$/i.test(h)){t.skillTopUp=!0,l++;continue}if(r=h.match(/^use tools from (?:the\s+)?"([^"]+)"(?:\s+server)?$/i)){(t.tools??=[]).push(r[1].trim()),l++;continue}if(r=h.match(/^examples?:\s*(.+)$/i)){(t.context??={}).examples=R(r[1]),l++;continue}if(r=h.match(/^knowledge:\s*(.+)$/i)){(t.context??={}).knowledge=R(r[1]),l++;continue}if(r=h.match(/^(?:remember|keep a memory)\s+in\s+"([^"]+)"$/i)){t.memory={file:r[1].trim()},l++;continue}if(r=h.match(/^plan from "([^"]+)"$/i)){t.planSource={type:"file",path:r[1]},l++;continue}if(/^a human approves the plan first$/i.test(h)){t.humanPlan=!0,l++;continue}if(/^a human reviews before stopping$/i.test(h)){t.humanReviewBeforeStop=!0,l++;continue}if(r=h.match(/^a human approves before\s+(.+)$/i)){i={message:`approve before ${r[1].trim()}`},l++;continue}if(r=h.match(/^when\s+(.+?):\s*(.+)$/i)){let c=ie(r[1],a.lineNo),f=A(r[2],a.lineNo);(t.transitions??=[]).push({...c,do:f}),l++;continue}if(r=h.match(/^after\s+(\d+)\s+tries:\s*(.+)$/i)){let c=A(r[2],a.lineNo);(t.transitions??=[]).push({on:"attempts",threshold:parseInt(r[1],10),do:c}),l++;continue}throw new u(`unrecognized line: "${h}"`,a.lineNo)}if(!s)throw new u(`loop "${e??"(anonymous)"}" is missing a goal`,n[0]?.lineNo??0);if(p||(t.cycle=o?.cycle?.length?[...o.cycle]:["plan","act","observe"]),o?.rigor==="structured ai-assisted"||o?.rigor==="agentic engineering"){let a=h=>(t.transitions??[]).some(r=>r.on===h);t.doneWhen?.length&&!a("fail")&&(t.transitions??=[]).push({on:"fail",do:[{action:"reflect"},{action:"plan"}]}),a("fail")&&!a("attempts")&&(t.transitions??=[]).push({on:"attempts",threshold:8,do:[{action:"stop",warn:"thrashing"}]})}return{loop:t,gate:i}}function R(e){return e.split(",").map(n=>n.replace(/^and\s+/i,"").trim()).filter(Boolean)}function ae(e){let n={},o=e.split(",").map(i=>i.trim()).filter(Boolean),t=[];for(let i of o)i=i.replace(/^and\s+/i,"").trim(),/^the last failure$/i.test(i)?n.includeLastFailure=!0:i.length>0&&t.push(i);return t.length&&(n.files=t),n}function le(e,n){let o=e.policy??{},t=n.match(/allow\s+(.+?)\s+automatically/i);t&&(o.auto=[...o.auto??[],...C(t[1])]),t=n.match(/ask me before\s+(.+?)(?:\.|$)/i),t&&(o.confirm=[...o.confirm??[],...C(t[1])]),e.policy=o}function N(e,n,o){let t=e[n],i=t.text.match(/^stage\s+(.+?):\s*$/i);if(!i)throw new u('expected "stage :"',t.lineNo);let s=i[1].trim(),p=b(s)??s,{body:l,next:a}=w(e,n+1,t.indent);if(l.length===0)throw new u(`stage "${p}" has no body`,t.lineNo);let{loop:h,gate:r}=M(null,l,o);return{stage:{name:p,gate:r,loop:h},next:a}}function ce(e,n,o){let t=e[n],i=b(t.text)??t.text.replace(/^pipeline\s+/i,"").replace(/:$/,"").trim(),{body:s,next:p}=w(e,n+1,t.indent),l=[],a=0,h=0;for(;af;){if(!/^stage\b/i.test(s[d].text))throw new u(`expected a "stage" inside the parallel group in pipeline "${i}"`,s[d].lineNo);let{stage:m,next:g}=N(s,d,o);m.parallelGroup=h,l.push(m),d=g}a=d;continue}if(!/^stage\b/i.test(s[a].text))throw new u(`expected a "stage" inside pipeline "${i}"`,s[a].lineNo);let{stage:r,next:c}=N(s,a,o);l.push(r),a=c}if(l.length===0)throw new u(`pipeline "${i}" has no stages`,t.lineNo);return{pipeline:{kind:"pipeline",name:i,stages:l},next:p}}function pe(e,n,o){let t=e[n],i=b(t.text),{body:s,next:p}=w(e,n+1,t.indent),{loop:l}=M(i,s,o);return{loop:l,next:p}}function he(e,n){let o=e[n],t=o.text.match(/^(?:then\s+)?for each\s+(\w+)\s+in\s+"([^"]+)":$/i);if(t){let r=t[1],c=t[2],{body:f,next:d}=w(e,n+1,o.indent),m=null,g=null;for(let $ of f){let L=$.text.match(/^run\s+"([^"]+)"$/i);if(L){m=L[1];continue}if(/^a human approves(?:\s+(?:the plan\s+)?first)?$/i.test($.text)){g={message:`approve before ${r}`};continue}let P=$.text.match(/^a human approves before\s+(.+)$/i);if(P){g={message:`approve before ${P[1].trim()}`};continue}throw new u(`unrecognized line in 'for each ${r}': "${$.text}"`,$.lineNo)}if(!m)throw new u(`'for each ${r}' needs a 'run "