Running the loop (operator)

August 20, 2026 · View on GitHub

Executive overview

  • For: operators who want Ralph's strongest, truest mode — reach for it whenever a headless agent invocation is available, not just for large or ambiguous builds.
  • The choice: /wgm runs in-session (Ralph-lite, the fallback); scripts/loop.sh gives the agent a fresh context every iteration (Ralph-full, the preferred default whenever invocable).
  • Fastest path: set WGM_AGENT, then ./scripts/loop.sh build 20.
  • Key knobs: --threshold (satisfaction target), --stratified (converge tier 1 → 2 → 3), --container, --devcontainer (sandbox the loop itself, disk-conscious), plus frugal ↔ powerful model escalation.
  • Safety: non-destructive by default — no commits or pushes without --commit; stop anytime with Ctrl+C or a STOP sentinel.
  • Preflight: a non-dry-run build probes the selected agent's write capability before iteration 1; repeated exit-0 iterations with no plan progress are a first-class stall.
  • Devcontainer credentials: pass --devcontainer-mount HOST[:CONTAINER] for a narrowly scoped agent auth/config directory; the loop never mounts a whole home directory automatically.
  • Next: containers.md for live-service scenarios · devcontainers.md for sandboxing the loop itself · troubleshooting.md.

wgm runs in-session when you invoke /wgm, but its strongest mode gives the agent a fresh context every iteration via scripts/loop.sh. This is the operator's guide to driving that loop.

Ralph-lite vs Ralph-full

flowchart LR
  subgraph Lite [Ralph-lite: in-session]
    L1[Iteration 1] --> L2[Iteration 2] --> L3[Iteration 3]
    L3 -. context accumulates .-> L3
  end
  subgraph Full [Ralph-full: fresh context each pass]
    F1[Iteration 1] --> X1[clear]
    X1 --> F2[Iteration 2] --> X2[clear] --> F3[Iteration 3]
  end
  • Ralph-lite — run the loop inside one agent session. The fallback: use when no headless agent invocation is available (a purely interactive host) or the work is Quick-track; compensate for accumulating context with strict persistence to IMPLEMENTATION_PLAN.md.
  • Ralph-fullloop.sh invokes your agent once per iteration with a clean context. The preferred default whenever it's invocable — not reserved for large/ambiguous builds only. The plan file is the only memory between passes. Add --devcontainer to run it sandboxed without inflating disk usage per project (one shared base image; see devcontainers.md).

See references/ralph-loop.md for the underlying mechanics.

Wiring up your agent

loop.sh is host-agnostic — tell it how to call your agent:

# A shell-evaluated command (prompt appended as the last arg):
export WGM_AGENT='copilot -p --allow-all-tools'
# …or pass argv after `--` (invoked without eval — safest):
./scripts/loop.sh build -- copilot -p --allow-all-tools

If your agent reads the prompt from stdin, set WGM_PROMPT_STDIN=1.

Run it from another project

loop.sh ships inside the installed skill and operates on your current working directory, so one installed copy drives any project — run the skill's copy from your project's root:

# from your project's root — the path depends on where wgm installed (see installation.md):
~/.agents/skills/wgm/scripts/loop.sh build -- copilot -p --allow-all-tools
# a handy alias makes it one word from anywhere:
alias wgm-loop="$HOME/.agents/skills/wgm/scripts/loop.sh"
wgm-loop build 20 --max-runtime-seconds 3600

It reads and writes IMPLEMENTATION_PLAN.md and .wgm/ in the directory you launch it from, never in the skill folder, so one install serves every project. The ./scripts/loop.sh shorthand used elsewhere in this guide just means "the loop runner" — substitute your install path when you are not inside the wgm repo.

Modes

./scripts/loop.sh plan --request "build a small CLI todo app"  # one planning pass
./scripts/loop.sh preflight        # score readiness before building
./scripts/loop.sh build 20         # up to 20 build iterations
./scripts/loop.sh build only       # exactly one iteration
./scripts/loop.sh extract --source ../exemplar   # gene transfusion
./scripts/loop.sh review           # assess the diff vs acceptance criteria
./scripts/loop.sh build --dry-run  # print the prompt/command, run nothing

Modes mirror the skill: grill | analyze | plan | preflight | build | review | extract (loop is an alias of build). build/review/preflight refuse to run without an IMPLEMENTATION_PLAN.md.

Convergence & escalation knobs

FlagDefaultEffect
--threshold N95Satisfaction target the build converges to.
--scenarios DIRscenarios/ or .wgm/scenarios/Where holdout scenarios live.
--stratifiedoffValidate scenarios by ascending tier (1→2→3).
--container auto|podman|dockerautoAuto-selects available Podman, then Docker; explicit unavailable engines fail before the run.
--frugal-agent "CMD"Cheap model for routine iterations.
--escalate-after N2No-progress iterations before escalating to --agent.
--downgrade-after N5Progressing iterations before downgrading to frugal.

Model escalation engages only when both a frugal and a main agent are set. The loop uses changes to the plan file as its progress proxy:

flowchart LR
  Fr[frugal agent] -- no progress x2 --> Esc[escalate]
  Esc --> Mn[main agent]
  Mn -- progress x5 --> Dn[downgrade]
  Dn --> Fr

The same flags double as a context-size tier, not just a cost tier: point --frugal-agent at a locally-hosted, small-context model (e.g. ~65k tokens) for routine iterations, and reserve --agent for a larger-context hosted model to escalate to on a stall or a genuinely wide-scope task. See references/local-models.md for the full token-budget playbook.

See stall-recovery.md for what the agent does inside an escalation.

Operational limits & lifecycle hooks

Guardrails for long autonomous runs — time/cost/checkpoint controls remain off by default; capability probing and the no-progress safety guard are on by default:

FlagDefaultEffect
--max-runtime-seconds N0 (off)Hard wall-clock cap; the loop stops before the iteration that would exceed it.
--idle-timeout N0 (off)Stop if the plan file makes no progress for N seconds — a time-based circuit breaker.
--agent-timeout-seconds N0 (off)Terminate an active agent process group after N seconds with GNU timeout/gtimeout; unsupported hosts use a stated cooperative fallback.
--max-no-progress-iterations N3Fail after N successful build iterations leave the plan unchanged; 0 disables it. With frugal/main escalation, the default leaves one iteration for escalation before the circuit breaker.
--checkpoint-interval N0 (off)Commit every N build iterations, so a crash never loses work; ownership manifests still apply.
--max-cost N0 (off)Stop once cumulative cost from --cost-cmd reaches N — the spend equivalent of --max-runtime-seconds. See Cost ceiling below.
--notify "CMD"Run CMD on lifecycle events with $WGM_EVENT (start/complete/error) and $WGM_ITER set.

--notify is shell-evaluated like --agent, so set it only to a command you trust; its own failure never fails the loop. Example completion ping: --notify 'notify-send "wgm $WGM_EVENT @ $WGM_ITER"'.

Resilience — retries & circuit breaker

Unlike the limits above, these default on, so a long unattended run survives a transient blip (a rate-limit, a network hiccup) instead of dying on the first non-zero agent exit.

FlagDefaultEffect
--max-retries N2Retry a failed agent invocation up to N times in the same iteration, with exponential backoff + full jitter.
--retry-base-delay N5Base seconds for the backoff (each wait is a random 0..min(base·2^k, cap)); 0 = no wait.
--retry-max-delay N60Cap for any single backoff wait, in seconds.
--max-consecutive-failures N3Circuit breaker: stop the build loop after N iterations that exhaust their retries in a row; 0 = never trip.

The breaker counts only consecutive failures — any successful iteration resets it. To fail fast on the first error (the pre-resilience behavior), set --max-retries 0 --max-consecutive-failures 1. Each retry and the breaker trip emit --notify events (retry / error).

Before a real build or plan iteration, the loop asks the selected agent to create a unique disposable marker under .wgm/ and checks that the marker exists with the expected content. A successful agent exit is not enough. --dry-run explicitly does not run this probe.

Metrics ledger (data-driven runs)

--metrics FILE appends a TSV row per iteration, so you can reason about a run's cost and behavior:

ColumnsMeaning
timestamp · iter · mode · agentwhen, which iteration, the mode, and frugal/main
duration_s · plan_changed · resultwall-clock seconds, whether the plan advanced (1/0), ok/fail/stall
costtoken/cost figure from --cost-cmd (empty if unset)

A host-agnostic loop can't read a black-box agent's token usage, so plug your own: --cost-cmd "CMD" runs after each iteration (with $WGM_ITER set) and its stdout fills the cost column — best-effort, its failure never breaks the loop. Example: --metrics .wgm/metrics.tsv --cost-cmd 'tail -1 .wgm/usage.log'.

Cost ceiling

--max-cost N stops the build loop once the cumulative total from --cost-cmd reaches N — the spend equivalent of --max-runtime-seconds, so an unattended run can't silently blow through an unbounded budget. It works independently of --metrics (you don't need a ledger file to get the ceiling) but requires --cost-cmd to have anything to sum — set both, or you'll get a startup warning that the ceiling can never trigger. The unit is whatever --cost-cmd emits (dollars, cents, tokens); wgm never interprets it, only sums it.

./scripts/loop.sh build --cost-cmd 'tail -1 .wgm/usage.log' --max-cost 20   # stop at spend >= 20

Default 0 = unlimited (unchanged behavior). This closes the "API spend/cost ceiling" follow-up noted in docs/plans/2026-06-16_PLAN.md.

Optional external tooling: dashboards and cost views

wgm ships the loop runner and its output files, not a bundled operator UI. If you want a live dashboard, build it externally against the files and hooks loop.sh already exposes — the same "runner emits durable state; a separate tool renders it" pattern noted for ralph-orchestrator and codeburn in the landscape survey.

Pattern: a ledger-backed TUI/dashboard (ralph-orchestrator style)

In the wgm vs representative projects table, ralph-orchestrator is the reference for a terminal dashboard/TUI. The equivalent wgm pattern is: leave loop.sh alone, and point an external reader at its existing artifacts.

  • Primary data source: the --metrics FILE TSV ledger (timestamp, iter, mode, agent, duration_s, plan_changed, result, cost).
  • Event stream: --notify "CMD" lifecycle events (start, retry, error, complete) plus any checkpoint commits you asked --checkpoint-interval to create.
  • Optional score input: if you want a satisfaction trend line as well as iteration timing, read the existing .wgm/scores.md trajectory (or the score notes written into the plan) alongside the ledger; the TUI is still only reading wgm's durable outputs.

That gives an operator enough to tail or poll for:

  • current iteration number, mode, and active frugal/main agent
  • per-iteration elapsed time and success/failure history
  • whether the plan advanced this pass (plan_changed)
  • checkpoint boundaries and loop lifecycle notifications
  • satisfaction trend, if you also ingest the score log

In other words: you could build a ralph-orchestrator-style terminal board on top of .wgm/metrics.tsv, .wgm/scores.md, and --notify; wgm itself does not ship that board.

Pattern: a token/cost TUI (codeburn style)

The landscape survey's Adjacent ecosystems section calls out codeburn as a token TUI dashboard. wgm's matching integration point is already here: --cost-cmd fills the ledger's per-iteration cost column, and --max-cost can stop the loop once that running total crosses a ceiling.

  • Keep your own token/spend collector outside wgm (for example, a host-specific usage log or API meter).
  • Have --cost-cmd print one numeric figure per iteration in whatever unit you care about (tokens, cents, dollars).
  • Enable --metrics FILE so the figure lands in the ledger as a durable time series.
  • Let an external TUI tail that ledger and render live per-iteration cost, cumulative spend, and "distance to --max-cost" progress.

This is intentionally host-agnostic: wgm does not know how your agent exposes usage, and it does not bundle a token dashboard. It only gives you the hook and the ledger so an operator can attach a codeburn-style live view if they want one.

Project gates (wgm.yml)

A wgm.yml (or .wgm/gates.yml) at your project root defines project-wide gates — commands the host runner executes after every build iteration. They are a quality floor independent of any single task's own check. loop.sh auto-detects the file (override with --gates FILE), runs each command, and names a failing command before the iteration can be recorded as successful.

# wgm.yml
gates:
  - npm run typecheck
  - npm test --silent
  - npm run lint
./scripts/loop.sh build --dry-run        # shows: gates=wgm.yml (3) + the injected line
./scripts/loop.sh build --gates ci/gates.yml

Gates are shell commands — use only a file you trust. A starter lives in assets/wgm.example.yml. The prompt still receives the list as context, but the host runner is the source of truth for the exit status.

Swarm — parallel worktrees

For independent slices, fan the loop out with scripts/swarm.sh: it runs several loop.sh build streams in parallel, each isolated in its own git worktree on its own branch, then you merge the branches — one thought per branch.

# one stream per line; each line is that stream's scope
mkdir -p .wgm
printf 'add the auth module\nadd the export endpoint\n' > .wgm/tasks.txt
~/.agents/skills/wgm/scripts/swarm.sh --tasks .wgm/tasks.txt -- copilot -p --allow-all-tools   # or set $WGM_AGENT
# …or N identical streams (race / diversity):
~/.agents/skills/wgm/scripts/swarm.sh -n 3 --max-iterations 20 -- copilot -p --allow-all-tools
FlagEffect
--tasks FILEone stream per non-empty, non-# line (the line is that stream's --request scope)
-n, --count NN identical streams
--max-iterations Nper-stream build cap (0 = until each self-stops)
--prefix NAMEbranch/worktree name prefix (default wgm/swarm)
--cleanupremove the worktree dirs when done — branches are kept for merging
--dry-runprint the plan; create nothing

Planning a swarm well

  • Partition file ownership, not just features. Worktree isolation only prevents live file contention — two peer streams can still independently touch the same shared module (a registry, an index/mod/use file, a shared FFI/utils file), which then surfaces only as a merge conflict at integration time, the most expensive moment to find it. Assign each stream a disjoint set of files/areas it owns, name them explicitly in its prompt, and route shared additions (helpers, constants, FFI) into the stream's own module instead of the common file. Treat any unavoidably-shared declaration file as a known merge point and have each stream append in a stable, non-adjacent location so a 3-way merge stays trivial.
  • A feasibility spike is a legitimate stream too. Not every stream has to ship code — dispatch an open "is this even possible?" question as a peer stream whose deliverable is a go/no-go writeup with provenance, not a diff. Fold the verdict back into IMPLEMENTATION_PLAN.md: drop any task the spike proves is a duplicate, split out a smaller patchable sub-win it surfaces, and record the question so it's never re-attempted. A well-supported NO-GO is a PASS for a spike, not a stall — it only fails if it produces no decision (see stall-recovery.md).

Provenance: [learn] issues #29 (file-ownership partitioning) and #30 (feasibility spikes as a parallel stream).

Each stream runs with --commit, so its branch carries the work. Worktrees live under .wgm/worktrees/ (gitignored). Merge a finished stream with git merge wgm/swarm/N; an existing branch is skipped rather than clobbered. Partition the work yourself — the swarm is the sheepdog spawning the dogs, not an auto-splitter.

Every run also feeds the Hive Growth Loop. After all streams finish, swarm.sh unconditionally folds each stream's .wgm/memories.md into the invoking worktree's own .wgm/memories.md (tagged by origin branch), then hands it to scripts/harvest-hive.sh — safe to run every time: with no .github/wgm-hive.yml yet it just previews and skips, never blocking the swarm or asking on your behalf unattended (references/self-improvement.md).

Stopping the loop

  • Ctrl+C at any time.
  • Create a STOP (or .wgm/STOP) sentinel to end after the current iteration.
  • Cap the run up front with --max-runtime-seconds, --idle-timeout, or --max-no-progress-iterations (see above).
  • In build mode the agent drops that sentinel itself when no must-have task remains, so the loop self-terminates.

Commits

loop.sh is non-destructive by default (no commits, no pushes). Pass --commit to commit after each build iteration. Commit mode requires a clean baseline, takes exclusive ownership of the worktree, and stages only repository-relative paths declared in the iteration ownership manifest. Do not edit the worktree while the loop runs; use another worktree for concurrent human changes.

See also: containers.md · troubleshooting.md.