Skip to content

Latest commit

 

History

History
806 lines (631 loc) · 42.3 KB

File metadata and controls

806 lines (631 loc) · 42.3 KB

LoopFlow — User Manual

The complete LoopFlow reference: install, CLI, language, run semantics, the IR. New here? Start with the tutorial. Authoring .loop with an AI agent? Give it AGENTS.md.


The shape of a loop

Every loop has one structure with two faces. You author five decisions (the knobs); the runtime executes five phases (the cycle). The knobs configure the phases.

        what you DECIDE                     what RUNs
        (the .loop you write)               (each iteration)

        objective   →  goal:                ┌─────────────────────────────┐
        context     →  look at:             │   plan → act → observe      │
        actions     →  allow / ask me…      │     ▲              │        │
        verification → done when            │     └── reflect ◄──┘ (fail) │
        stopping    →  when… / after N      └──────────── │ ──────────────┘
                                                     (pass) ▼
                                                          stop
Knob The line Configures the phase
Objective goal: what stop is aiming at
Context look at: what plan may read
Actions allow … / ask me before … what act may do
Verification done when … what observe checks
Stopping when … / after N tries stop + the reflect back-edge

Defaults cover the simple case (cycle plan → act → observe; edits auto). The VSCode extension warns — never errors — when a loop is missing something load-bearing.

Anatomy — the authoring order

The parser doesn't care what order the lines come in; you should: the finish line first, the safety net last — four zones, top to bottom. (Rationale and the taught version: tutorial → Anatomy.)

Anatomy of a healthy loop: 1 the contract (goal + done when), 2 the boundaries (look at, allow/ask, human gates), 3 the engine (each cycle), 4 the safety net (when it fails / when blocked / after N tries)

The four zones, in writing order

# Zone Lines Why it comes here
1 The contract goal: → done when The check before any behavior — loop engineering's TDD.
2 The boundaries look at: → allow …, ask me before … → a human approves/reviews … Scope before power; decide gates while thinking about risk.
3 The engine each cycle: The repeated movement.
4 The safety net when it fails: → when blocked: → after N tries: Recovery → escape hatch → floor; the last line is the hard stop.

The canonical skeleton (placeholders — replace every <…> before running; it does not parse as-is):

loop "<name>":
  goal: <the outcome, one sentence>                    # 1 ── contract
  done when <a check a machine can run>                #      the finish line — write it FIRST

  look at: <the few files that matter>, and the last failure   # 2 ── boundaries
  allow edits automatically, but ask me before <the risky class>
  a human approves the plan first                      #      (only when the work warrants it)

  each cycle: plan, then act, then observe             # 3 ── engine

  when it fails: reflect on <what to examine>, then plan again  # 4 ── safety net
  when blocked:  ask a human
  after 6 tries: stop and warn "<how you'll find it stuck>"     #      the floor, always last

Where the optional lines go — each joins the zone it belongs to:

Line Zone
use skills: / remember in / knowledge: / examples: 2 · boundaries (capabilities & context)
plan from "<file>" 2 · boundaries (the plan is an input)
also: (finishing passes) 3 · engine (extra movement after the goal)
hooks: 4 · safety net (deterministic checkpoints)
git: block, models: config tier — above all definitions, or file-top inside the loop

Smells, for the review pass: no done when · look at: missing or unbounded · a reflect back-edge with no try ceiling · a warn message that won't tell future-you what got stuck · five gates on a rename. The same order applies inside every stage of a pipeline — each stage is a loop and reads as one.

1. Requirements

  • Node.js 18+
  • The Claude Code CLI (claude) installed and authenticated — loop-run run drives it. Other commands (parse, viz) do not need it.

2. Install

The published packages:

npx @loop-lang/loop init         # scaffold Loop into a repo: /loopflow skill + AGENTS.md + examples + templates
                                 # + the gated loop-first default in CLAUDE.md (--cursor / --copilot for other agents; --no-claude-md to skip)
npm i -g @loop-lang/runtime      # the loop-run CLI used throughout this manual

Building from source (contributors):

git clone <repo-url> loop-lang && cd loop-lang
npm install && npm run build
npm link --workspace @loop-lang/runtime   # the same loop-run command, from your build

3. Quickstart

Create fix.loop:

loop "fix add":
  goal: the add function returns the sum of its two arguments
  done when "npm test" passes
  look at: src/add.js
  allow edits automatically
  each cycle: plan, then act, then observe
  when it fails: reflect on why it failed, then plan again
  after 5 tries: stop and warn "could not fix add"

Don't start from a blank file: templates/ ships verified starter loops (bugfix, feature, security, greenfield A-to-Z) — copy one and fill in its # TODO lines.

Check it parses, see it, then run it:

loop-run show fix.loop       # print the loop's shape as ASCII (explain = plain English)
loop-run parse fix.loop      # prints the loop-spec JSON (validates syntax)
loop-run viz fix.loop        # writes fix.html — open it in a browser
loop-run run fix.loop        # drives Claude Code: plan -> act -> observe -> done

Make show (or explain) a habit before run — ten seconds of reading the shape catches a missing thrash guard before it costs tokens.

loop-run run works in the directory the .loop file lives in: it plans, edits files, runs your done when check, reflects on failure, and stops when the goal is met (or at the thrash guard).

4. The CLI

loop-run <run|parse|viz|live|show|explain|ls|emit> <file.loop> [--model <alias>] [--live] [--events] [--log <path>] [--resume <log>] [--out <path>]
Command What it does
loop-run run <file> Execute the flow on Claude Code (plan/act/observe, reflect, verify, human gates).
loop-run parse <file> Parse to the loop-spec IR and print it as JSON (use to validate syntax).
loop-run show <file> Print the loop's flow as compact ASCII (the "see the shape" view).
loop-run explain <file> Describe the loop in plain English — handy for non-experts to check what a .loop will actually do.
loop-run viz <file> Write a self-contained HTML schematic of the flow.
loop-run ls [dir] List every .loop under a directory with its one-line shape.
loop-run live <file> Start the live dashboard server (no engine) and stay up; for driving the dashboard from an in-session run.
loop-run emit <port> '<json>' Push one event to a running live server (used by the /loopflow skill).

Flags

  • --model <alias> — model for run (e.g. opus, sonnet, haiku); passed to Claude Code. Omit to use the CLI default.
  • --live — for run, open a live browser dashboard and stream every step to it as the loop executes (see below).
  • --events — for run, emit the machine-readable NDJSON event protocol on stdout for a UI host (what the VSCode ▶ Run "output" mode consumes).
  • --log <path> — for run, append the full event stream to a local NDJSON log (see Event log & telemetry below). Overrides LOOP_LOG_FILE.
  • --resume <log> — for run, skip everything a prior run's event log proves already satisfied and pick up at the first incomplete unit (see Resuming an interrupted run).
  • --out <path> — for viz, the HTML output file (default: the .loop name with an .html extension).
  • --json — print the parsed loop-spec JSON before the command's normal work (handy with run; also works with viz). For parse, the JSON is the entire output.

Config files. A .loop file containing only a config block with use (and no definitions) resolves the named preset and runs it — e.g. a project.loop with use the BMAD method.

Live dashboard

A real-time browser view of a run: the loop's actual structure as a turn-by-turn route (pipeline stages / flow steps / for-each items), with a "you are here" marker, the steps ahead, human gates flagged, and for-each sprints listed by item title with N/total progress.

Two ways to drive it:

  • Headless — loop-run run <file> --live opens a browser tab and the engine streams every event to it. The terminal still prints the normal text trace.
  • In-session — opt-in via loop.config: loop init writes live=false; only live=true makes the /loopflow skill open and drive the dashboard. Independent of the headless --live flag. (The emit protocol the skill uses is specified in the skill's own cheat-sheet.)

The page connects over Server-Sent Events; each event carries an id and the server replays on connect (and dedupes on reconnect via Last-Event-ID), so events fired before the browser connects are not lost and a transient drop doesn't double-deliver. The dashboard is self-contained (no external assets) and binds to 127.0.0.1 only.

Event log & telemetry

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, 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 thrashing loop, or metering cost.

Off by default. With no flag and no env var, a run persists nothing and behaves exactly as before. Persistence is best-effort: a failing log or an unreachable collector never throws and never blocks a run — telemetry can't break a loop.

Local log — --log / LOOP_LOG_FILE

loop-run run test.loop --log run.log          # one-off (overrides LOOP_LOG_FILE)
LOOP_LOG_FILE=run.log loop-run run test.loop  # env — same effect

The log is NDJSON: one JSON object per line, so it streams and greps cleanly. The first line is a loop.log.v1 header (the run id + metadata); every line after is one event with a monotonic seq and an ISO timestamp:

{"v":"loop.log.v1","runId":"…","ts":"2026-07-01T20:04:43.443Z","meta":{"loop_path":"test.loop","principal":"idan"}}
{"seq":0,"ts":"…","event":{"type":"loop-start","name":"test loop"}}
{"seq":1,"ts":"…","event":{"type":"node-enter","node":"plan","attempt":1}}
{"seq":2,"ts":"…","event":{"type":"observe","passed":false,"output":"1 failing test"}}
{"seq":3,"ts":"…","event":{"type":"reflect","focus":"which layer broke","text":"the API returned 500"}}
{"seq":4,"ts":"…","event":{"type":"stop","reason":"done"}}

Each event is written with a synchronous append, so it's on disk the instant it fires — the log survives a Ctrl-C or a crash with no lost tail. The parent directory is created if missing. It works the same across all run modes (default, --live, --events).

Secrets are scrubbed before anything is persisted. Command output can echo credentials (a failing git push printing a token, a dumped env). Every event passes through a redactor before it reaches the file log or the HTTP collector: values of env vars whose names look secret-bearing (*_TOKEN, *_SECRET, *_PASSWORD, *_KEY, …) are replaced with [redacted:<VARNAME>], and well-known credential shapes (GitHub / Slack / AWS / sk-… API keys, JWTs, PEM private keys, Bearer … headers, password=… assignments) are masked by pattern. Best-effort by design — treat it as a seatbelt, not a licence to log freely. Disable with LOOP_REDACT=off (e.g. when debugging the redactor itself).

Read it back with any NDJSON tool — e.g. with jq:

jq 'select(.event.type == "observe")' run.log            # every verification result
jq -r 'select(.event.type=="reflect") | .event.text' run.log   # what each failure taught it
jq 'select(.event.type == "stop") | .event.reason' run.log     # how it ended

Remote collector — LOOP_EVENTS_URL

To stream the same events to a control plane over HTTP, set LOOP_EVENTS_URL (and, if the collector needs it, LOOP_EVENTS_TOKEN). Events POST to <url>/api/v1/runs/<runId>/events, each carrying the monotonic seq so the server is idempotent on retries and out-of-order delivery.

Env var Meaning
LOOP_LOG_FILE Local NDJSON log path (the --log flag overrides it).
LOOP_EVENTS_URL Control-plane collector base URL — enables the HTTP sink.
LOOP_EVENTS_TOKEN Shared API token, sent as x-api-token (optional).
LOOP_RUN_ID Correlate a run's events across sinks (optional; a UUID is generated if unset).

The file log and the HTTP collector can run together — the same event fans out to both, sharing one runId, so a local trace and the control-plane record line up. Set LOOP_RUN_ID yourself when you want a run's id to match something you already track (a CI job, a ticket).

Resuming an interrupted run — --resume

The event log is also a journal. A long pipeline that dies at stage 4 — crash, Ctrl-C, laptop lid — doesn't have to start over:

loop-run run epic.loop --log run.log          # …dies at stage 4 of 6
loop-run run epic.loop --resume run.log --log run.log   # stages 1–3 skip, stage 4 picks up

What resume does:

  • Skips what's proven done. A unit whose end event says satisfied: true in the log — a whole definition, a pipeline stage, a flow step, a for each item — is skipped, with a ⏩ resumed line in the trace. Everything else (failed, or interrupted mid-flight with no end event) re-runs from scratch.
  • Restores flow context. A flow step's handoff summary is recorded on its end event, so a resumed flow hands the next step the same carry-forward text the original run produced.
  • Detects drift. The log header carries a hash of the .loop source; if the file changed since the logged run you get a warning (units are matched by name/position). Editing the file to fix the failing stage and then resuming is the normal workflow — the warning is informational, not an error.
  • Composes with --log. Point --log at the same file to keep one continuous journal (headers separate the runs), or at a new file for a clean second record. Nested work needs no bookkeeping: a sub-file a flow step runs is summarised by that step's own end event.

The unit of resume is deliberately the stage / step / item, not the mid-loop cycle — a half-finished loop re-verifies from its own done when, which is exactly what makes skipping safe: nothing is trusted that a check didn't prove.

5. Language reference

A .loop file is indentation-structured. loop / pipeline sit at column 0; their body is indented two spaces; a stage's body is indented under the stage. Comments start with #.

Definitions

  • loop "<name>": — a single self-correcting loop. Requires a goal.
  • pipeline "<name>": — a sequence of stages. Requires at least one stage. Stages run in order; a failing stage halts the rest. (An epic → a pipeline.)
  • stage "<name>": — one stage of a pipeline; its body is a loop. (A story → a stage.)
  • flow "<name>": — a chain of .loop files. Each step runs a whole file and passes its text result forward. Fail-fast: a step that ends unsatisfied stops the rest.

Chaining loops across files (flow)

A flow lets you sequence independent .loop files into a single pipeline where the output of one step becomes the context for the next. This is useful when each phase lives in its own file and should be reusable independently:

flow "ship":
  run "build.loop"
  then run "test.loop"
  then run "deploy.loop":
    a human approves first
Element Meaning
run "<file>" First step. Runs the whole file (plan→act→observe); text result is carried forward.
then run "<file>" Subsequent step. Automatically receives the previous step's text summary as upstream context.
a human approves first Optional per-step human gate — blocks until approved before the step runs.
with the result of <name> Reference a specific named step's output instead of the automatic carry.

Text handoff. Only the text summary (the last observe output or final plan) is passed between steps — not file state. File edits made by one step are visible to the next through the working directory as normal; the explicit channel is the text summary.

Fail-fast. If any step ends unsatisfied, the remaining steps are skipped and the flow ends unsatisfied.

Iterating over a plan (for each)

Inside a flow, for each reads a list from a YAML or Markdown file and runs a template once per entry. Each entry's text becomes the template's context — what to build.

flow "deliver":
  for each item in "plan.yaml":
    run "item-template.loop"
Element Meaning
for each <var> in "<file>": Read items from the file; run the body once per item.
run "<template>" Template to run per item; the item text arrives as context.
Source .yaml A flat list or a single-key list (e.g. items:).
Source .md Splits on ## headings — each section is one item.
Failed item The flow pauses and asks whether to continue with the next item or stop.

A-to-Z with any method. for each is method-neutral. The same syntax works whether the template is a BMAD story checklist, a security scan, a documentation pass, or anything else. See examples/foreach/ for a generic bundle (implement → security → lint, per work item) and examples/bmad/atoz/ for a BMAD flow (discover → design → for each story) as one example method.

Inside a loop / stage body

Line Meaning
goal: <text> The objective, in plain language. Required.
done when <predicate> How the loop verifies itself (see Predicates). You can list several — all must pass (a conjunction of tests and evals). Omit only if a human gate decides completion.
look at: <a>, <b>, and the last failure Context the agent reads before acting. Items are file paths or plain-language descriptions (e.g. the billing form) — the agent locates the actual files from descriptions before planning. and the last failure feeds the previous failure forward.
allow edits automatically, but ask me before <classes> Action policy. Auto classes run unattended; confirm classes pause for you. Classes: edit, migrate, push, deploy, delete.
each cycle: plan, then act, then observe The repeated steps — any subset of plan / act / observe, in order.
also: <pass>, <pass> Extra finishing passes run after the goal is met (skipped on failure).
use skills: <a>, <b> Named skills the loop may invoke during plan/act — coordinate proven skills instead of one mega-prompt.
remember in "<file.md>" Cross-run memory: read the file's lessons into the first plan, append a dated outcome entry on stop. (keep a memory in "<file>" also works.)
when it passes and the goal is met: stop Success transition.
when it fails: reflect on <focus>, then plan again The feedback edge — reflect, then re-enter the cycle.
when blocked: ask a human Pause for a person when stuck.
after <N> tries: stop and warn "<msg>" Thrash guard — stop after N attempts.
a human approves the plan first A person approves the plan before the agent acts.
a human reviews before stopping A person judges the result before the loop may stop.
plan from "<file>" Read the loop's plan from a file you control (e.g. a hand-written .md) instead of having the agent generate it. Omit it and the agent writes the plan (the default).

Friendly shorthands

Plain-English synonyms for the lines above — write whichever reads naturally to you. They desugar to the exact same loop-spec, so you never lose any power:

Shorthand Same as
check: pnpm test done when "pnpm test" passes (a bare value is a shell command)
check: the skill "review" approves done when the skill "review" approves (a predicate phrase is parsed as-is)
verify: … same as check:
in: src/cart, src/tax · files: … · look in: … · context: … look at: …
when it breaks: … when it fails: …
when it gets stuck: … when blocked: …

A complete, friendly loop can be as short as:

loop "fix the cart total":
  goal: the cart total is correct with tax
  in: src/cart, src/tax
  check: pnpm test cart
  when it breaks: reflect, then plan again
  after 6 tries: stop and warn "stuck"

Run loop-run explain <file> to read any loop back in plain English and confirm it does what you intend.

In a stage only

Line Meaning
a human approves before <action> A blocking gate before the stage runs (e.g. before deploy). Parses in any loop body, but only a stage acts on it — in a standalone loop it is silently ignored.

Predicates (done when …)

Every form, in one (deliberately over-checked) loop body:

loop "predicate forms":
  goal: one example of every done when form
  done when the test "billing.spec.ts::apostrophe" passes   # a named test
  done when "pnpm test" passes                               # shell command, exit 0 (`succeeds` also works)
  done when "semgrep --severity=high" finds nothing          # shell command, empty stdout
  done when "pnpm test flaky" passes 3 times                 # flake guard: re-run, every run must pass
  done when a human confirms "looks right at 375px"          # a human check
  done when the skill "email-review" approves                # an eval: approved / not
  done when the skill "email-review" scores 8 or more        # an eval: numeric threshold
  done when the skill "code-review" approves by 3 judges     # judge panel: N verdicts, majority wins
  done when the skill "api-review" scores 8 or more on the output       # an eval of WHAT was produced
  done when the skill "path-review" approves on the trajectory          # an eval of HOW it got there
    the bar: didn't weaken a test to go green; no writes outside api/   # the rubric the judge scores against

The command runs in your shell with your privileges (like an npm script). Keep it fast and deterministic.

Flake guard — passes N times. Append N times to a test or command predicate (passes / succeeds / finds nothing) to re-run the check N times and require every run to pass. The first failing run short-circuits (the rest don't execute). Use it when a green can hold by luck — a timing-dependent or order-dependent test — so "done" means "passes reliably", not "passed once". show renders it as ×N; a plain check (or 1 time) is the usual single run.

Judge panel — by N judges. Append by N judges to a skill predicate to collect N independent verdicts and take the majority. A single LM judge wobbles run-to-run near the bar; independent samples average the noise out. The panel early-exits once the vote is mathematically decided (2 approvals out of 3 → the third judge never runs), each verdict is emitted as its own skill-verify event (judge 1/3: …), and the observe output reports the tally (judges: 2/2 approved (majority of 3 reached)). Composes with everything else: scores 8 or more on the trajectory by 5 judges. Costs N× the eval, so reserve it for checks where a wrong "done" is expensive.

Tests vs evals

A loop can list several done when lines, and all must pass. Use this to combine the two kinds of verification:

  • Tests — test / command predicates. Deterministic, checked by code.
  • Evals — skill predicates. A rubric / LM judge for the non-deterministic parts. An eval names its subject: on the output (the default — judges what was produced) or on the trajectory (judges how the agent got there — its path and tool calls). An indented the bar: line states the conditions the judge must score against.

A trajectory eval catches what a green test can't — e.g. an agent that made a test pass by weakening it. Pair a test with an eval when "done" means both it works and it was built the right way. Build the review skill manually first and confirm it judges well, then wire it in. See examples/skills_memory.loop and examples/email_review.loop.

How verification works — what "done" actually depends on

When the loop says "done", what exactly decided that? End to end:

When it runs. Verification happens at the observe node of every cycle — after act, before any transition. Nothing is verified mid-act; a cycle without observe in its each cycle: never checks at all (it relies on a human gate to stop).

The conjunction. Every done when line must pass, in the order written. The first failing check short-circuits — later predicates don't run that cycle. Their combined output becomes the observe result; on failure that text is "the last failure" your look at: context refers to, and it feeds the reflect step. Verification isn't just a gate — it's the loop's sensory input.

Where a command runs. A test / command predicate runs as a real shell command:

Factor Effect on the verdict
Working directory The loop's effective baseDir — your repo dir by default, the target: dir if the config sets one, and the worktree when the git policy is work in a worktree. A check that passes in-repo can fail in a fresh worktree (untracked files, unbuilt artifacts).
Your shell + your env The command runs with your privileges and environment, like an npm script. A PATH, NODE_ENV, or missing env var difference between your machine and CI changes the verdict.
Exit code passes / succeeds = exit 0. Nothing else is inspected. A test runner configured to exit 0 on failures will make the loop lie to you — verify your runner's exit-code behavior first.
Output emptiness finds nothing = exit 0 and empty stdout+stderr. A scanner that prints a benign banner never "finds nothing" — silence it or wrap it.
The test shorthand the test "x::y" passes desugars to npm test -- x::y by default. If your runner isn't npm-style, use an explicit command predicate instead.
Flakiness One lucky green = done, unless you add passes N times (every run must pass, first failure short-circuits).
Output size Captured output is truncated (~4 kB into the trace) — the verdict uses the exit code, not the text, so truncation never changes pass/fail.

How an eval decides. A skill predicate never touches the shell — it's routed to the runner, which invokes the named review skill as a judge. What the judge sees is the subject: the act summary (on the output, default) or the captured path and tool calls (on the trajectory). It scores against the the bar: rubric if present; scores N or more compares its numeric score to the threshold; by N judges repeats the judgment independently and takes the majority. An eval's verdict is an LM's judgment — it can wobble; the bar and the panel are the two tools that stabilize it.

What never auto-passes. a human confirms "…" always waits for you. A loop with no done when at all can never be machine-satisfied — it needs a human reviews before stopping, or it runs until the after N tries guard / the hard cap (25 cycles) stops it unsatisfied.

What the verdict triggers. Pass + goal met → the loop stops (then also: finishing passes, then a human review if declared — and an on stop hook can still veto the stop). Fail → the when it fails: transition (reflect → plan again). So the checks you write are literally the loop's definition of reality: a wrong predicate doesn't make the loop fail — it makes the loop stop caring about the right thing.

Rules of thumb. Fast, deterministic, loud-on-failure commands; finds nothing for scanners; passes N times for anything with timing in it; a test and an eval when "done" has a quality dimension; a trajectory eval when you're worried the agent will game the test.

Config tier (top of file)

use the BMAD method      # pull in a preset (a .loop in the stdlib, or ./local.loop)
run with claude code     # runner / provider (or: `runner claude code`)
schedule: nightly        # manual · nightly · on push · cron (parsed; run is manual via the CLI)
target: ./src            # working directory the loop operates in
notify: slack            # parsed and stored in the IR; NO runtime behavior yet (reserved)
each cycle: plan, then act, then observe   # the DEFAULT cycle for every loop in the file

Anything in the config tier is a default for every loop in the file, so you write it once instead of repeating it per loop. A each cycle: here is inherited by every loop; a loop with its own each cycle: overrides it. The same lowest-wins cascade governs git: and models:.

Project defaults — loop.config

To avoid repeating config across files, drop a loop.config (or .looprc) at your project root. It uses the same config-tier syntax (each cycle:, models:, a git: block) and the runner reads it before every run, walking up from the .loop file to find it. It is the lowest tier of the cascade, so a file's own config and a per-loop directive both override it.

# loop.config — applies to every .loop in the repo
each cycle: plan, then act, then observe
models: fast haiku, strong opus
git:
  work on a branch
  commit when the goal is met

Cascade (lowest wins): loop.config → a file's config tier → a per-loop directive.

Agentic-engineering features

These add agentic-engineering discipline to Loop. All are optional; a simple loop ignores them.

Construct Where What it does
rigor: vibe coding | structured ai-assisted | agentic engineering config tier The spectrum dial. Under structured/agentic, every loop is born with a reflect-on-fail back-edge and a thrash guard unless it sets its own — sensible defaults, no boilerplate. Also enables the "without both [tests and evals]" lint.
mode: conductor | orchestrator config tier Supervision posture — conductor (in-session, gates inline) vs orchestrator (async, opens a PR).
hooks: block loop body Deterministic checks at lifecycle points — before each cycle / after act / on commit / on push / on stop: "<cmd>" passes or finds nothing. A failing hook blocks.
observe: block config tier trace every cycle / meter tokens and cost / stop and warn if cost exceeds "$N". The CLI prints a stop-time OpEx report (cycles, reflects, first-pass success).
sandbox: block config tier Run isolation: no network access / allow egress to "host" only / cap cpu at … memory at … time at ….
runs as: <identity> config tier An auditable principal for unattended runs.
examples: / knowledge: loop body examples: = reference patterns to imitate; knowledge: = read-only reference the agent must not edit. With look at:/remember in:/use skills:/allow…ask, a loop can declare all six context-engineering parts.
use tools from the "<server>" server loop body Name MCP servers whose tools the loop may use.
stages in parallel: inside a pipeline The indented stages run concurrently (barrier-join, fail-fast). File-safe parallel edits need a worktree per branch.

See examples/agentic/ for one file per feature; run loop-run explain <file> on any of them to read it back in plain English.

Git strategy

A git: block sets the version-control strategy for the run. It can appear at the top of the file (config tier, applying to all loops) or inside a single loop body (refining the commit cadence for that loop only).

Built-in default (no git: block): LoopFlow works on a branch and commits when the goal is met. No push happens. This default applies whenever no git block is present.

Line forms:

Line Meaning
work in place Edit the current branch as-is (no new branch).
work on a branch Create / switch to a feature branch (the default).
work on a branch "name" Use a specific branch name.
work in a worktree Run in an isolated git worktree.
work in a worktree "name" Named worktree.
commit when the goal is met One commit when the loop succeeds (the default).
commit each cycle Commit after every plan→act→observe cycle.
commit each story Commit after each stage in a pipeline.
commit never / do not commit No automatic commits.
push when done Push the branch when the loop finishes.
do not push No push (the default).
open a pull request Open a PR after pushing (requires push when done).

Cascade — settings resolve in three layers:

  1. Built-in default — branch + commit-when-done, no push.
  2. File-level git: block — applies to all loops in the file.
  3. Per-loop git: block — placed inside the loop body; refines the commit cadence for that loop only.

A use the <method> preset may carry a git: block at file level; the file's own block overrides it.

Always-on safety — unconditional, not configurable:

  • Never push to main or master. If the current branch is protected and push when done is set, the error surfaces before the loop runs.
  • work in place + push when done on a protected branch is likewise rejected up front.

Example:

git:
  work on a branch
  commit when the goal is met
  push when done
  open a pull request

loop "add a healthcheck endpoint":
  goal: GET /healthz returns 200 with a JSON status
  done when "pnpm test health" passes
  look at: the http server and the routes module, and the last failure
  each cycle: plan, then act, then observe
  when it fails: reflect, then plan again
  after 6 tries: stop and warn "healthcheck stuck"

See examples/git_policy.loop for the full working file.

Model policy

models: names two tiers; the engine routes the cheap-thinking phases (plan / reflect / also) to the fast tier and act to the strong tier.

Grammar (config tier, before any definition, or inside a loop body):

models: fast <model>, strong <model>

Built-in default mapping:

Phase Tier
plan fast
reflect fast
also passes fast
act strong
observe shell check — no model

Override a single phase or a whole scope, and it cascades file→loop→stage the same way the git: block does:

models: fast haiku, strong opus, plan strong   # flip one phase to strong
models: all strong                              # whole scope on one tier

Kill switch: --model X (CLI flag) or loop.model (VS Code setting) forces every phase onto one model, overriding any models: block.

Cost summary: at the end of a run, LoopFlow prints a per-tier call count so the spend is measurable, not a guess.

6. How a run works

loop-run run maps each node to a Claude Code invocation:

Node What happens
plan Claude Code in plan mode, read-only, scoped to your look at context.
act Claude Code headless; edits gated by your policy (auto classes allowed; confirm classes prompt you).
observe Runs the done when predicate and captures pass/fail + output.
reflect Feeds the failure back as context for the next plan (the back-edge).
a human … Pauses on the terminal for your y/N (plan approval, review, gate, confirm).
done when The runtime checks the predicate — you cannot fake "done".

Notes:

  • The cycle repeats until done when passes, a thrash guard fires, or a hard safety cap (25 iterations) is reached.
  • The test shorthand's default command: see the verification table.
  • In a pipeline, stages run sequentially; a stage that ends unsatisfied halts the rest.
  • Confirm-class actions are asked once per loop, then remembered for that run.

7. VSCode extension

The extension (packages/vscode) gives .loop files real editor tooling:

  • Syntax highlighting
  • Ctrl+Space autocomplete — context-aware (offers only what's valid where the cursor is: top-level vs loop body vs pipeline vs stage body)
  • Hover docs on the vocabulary
  • Live error squiggles — parses as you type and underlines the offending line
  • Formatter (Format Document; runs on save only if you enable editor.formatOnSave)
  • ▶ Run loop CodeLens above each loop/pipeline/flow

Inline AI prediction is intentionally left to Copilot/your editor's AI — write a # comment describing intent and let it draft the .loop.

Install (development): open packages/vscode in VSCode and press F5 to launch an Extension Development Host. Package a VSIX: npm run build --workspace @loop-lang/vscode produces a bundled dist/extension.js; package with vsce package.

Settings: loop.cliPath (path to the CLI for the Run button) and loop.model.

8. Authoring with an AI agent

Drop AGENTS.md in the repo; any agent authors .loop from a plain-English request.

loop init also installs a gated default into the agent's memory file (CLAUDE.md by default; add --cursor / --copilot or --all-agents for the others, --no-claude-md to skip): for repeatable, verifiable work the agent reaches for a .loop on its own — gated by AGENTS.md's four-condition test — while one-off questions and trivial edits are done directly, no loop ceremony. /loopflow always works as the explicit override in both directions.

The Claude Code skill (/loopflow)

The repo ships a Claude Code skill at .claude/skills/loopflow/. Copy it to ~/.claude/skills/ to use it in any project (it's already active inside the loop-lang repo). Then:

/loopflow fix the failing checkout tax test, gate any migration   # creates a .loop
/loopflow run examples/bmad-auth.loop                             # runs one natively

The skill both creates .loop files from a description and runs them inside your Claude Code session — walking plan → act → observe → reflect, honoring each done when and thrash guard, and pausing for human gates by asking you in the chat. Because Claude runs the loop itself, you see every step and answer gates inline, instead of a headless subprocess. (The loop-run run CLI and the VSCode ▶ button remain the headless ways to run the same files.)

9. Presets (methods)

A method is just a .loop file in the standard library. use the BMAD method pulls in BMAD.loop (an analyze → architect → build → qa pipeline). Fork it, or point use at your own ./method.loop. The core is method-agnostic.

A bare name (use the BMAD method) resolves in the stdlib (@loop-lang/stdlib — check packages/stdlib for the current contents; today BMAD.loop); a path (use ./method.loop) resolves relative to the file.

10. Troubleshooting

  • loop-run run does nothing / errors immediately — ensure claude is installed and authenticated (claude --version). run shells out to it.
  • "parse error (line N)" — the line doesn't match the grammar; check indentation (two spaces) and that the done when predicate is one of the supported forms.
  • The loop never stops — add after N tries: stop and warn "…"; otherwise a hard cap of 25 iterations applies.
  • A done when test never passes — if your runner isn't npm-style (see the verification table for the test shorthand's default), use a command predicate instead: done when "pnpm test -- x" passes.
  • A migration/push happened that you didn't want — add it to the policy: ask me before migrations or pushes. Only auto classes run unattended.
  • plan from "<file>" doesn't load — check the path is relative to the .loop file and the file exists; the loop reads its plan from that file verbatim.
  • The loop is thrashing (repeating the same failed fix) — read what each cycle learned: jq -r 'select(.event.type=="reflect") | .event.text' run.log. Identical reflections mean the context is wrong, not the attempt count: add the failing file to look at: and make sure it ends with and the last failure.
  • Runs cost too much — three dials: models: fast haiku, strong opus (cheap plan/reflect, strong act), an observe: block with stop and warn if cost exceeds "$5" (hard budget), and a lower after N tries. The end-of-run per-tier call count shows where the spend went.

11. The loop-spec IR

Every .loop parses to a JSON loop-spec — the open contract defined in spec/loop-spec.schema.json. Parser, runtime, and visualizer all read it. Build your own tooling against it:

loop-run parse file.loop      # prints the loop-spec for a file

The language and the IR are an open standard (Apache-2.0) — implement against them freely.