The Ralph loop
August 24, 2026 · View on GitHub
Ralph is a powerful, dumb little idea: keep restarting an agent with the same prompt and a persistent plan on disk, let it pick the most important task, do it, validate, record, and repeat. The plan file is the memory; each iteration is otherwise disposable. wgm adapts this for an agent.
Core principles
- Context is everything. Tight task + one task per iteration = the model spends its smart-zone context on the work, not on archaeology. Bloated context = degraded output.
- One task per loop. Pick the single most important
pendingtask. Finish it. Stop. - Patterns + backpressure steer the agent. Two forces keep iterations on track:
- Patterns/signs: existing utilities, conventions,
AGENTS.md, specs — the agent discovers and follows them. When the agent drifts, add a sign. - Backpressure: a deterministic pass/fail signal (test, type-check, build, lint, HTTP probe) that rejects bad work. No signal → no real loop.
- Patterns/signs: existing utilities, conventions,
- Let Ralph Ralph. The agent chooses which task is most important and how to implement it. Don't micro-script; provide signs and signals and let it work.
- Move outside the loop. The human sits on the loop, not in it: observe failure patterns and add signs (a prompt note, a utility, a spec clarification) rather than hand-holding each step.
Ralph-lite vs Ralph-full — prefer Ralph-full whenever it's invocable
Geoffrey Huntley's original definition is blunt: "Ralph is a technique. In its purest form, Ralph is
a Bash loop" — while :; do cat PROMPT.md | agent; done — a single monolithic process, one task per
loop, with progress living on disk (not the model's context) between passes
(ghuntley/how-to-ralph-wiggum, ghuntley.com/loop). Staying in-session for convenience is the
weaker, compromise form of the technique, not the default to reach for out of habit.
- Ralph-full (preferred whenever it's invocable) — genuinely fresh context per iteration via
scripts/loop.sh, or by restarting with a clean context. Reach for it whenever a non-interactive agent invocation is available: an operator-setWGM_AGENT, or a known CLI (copilot,claude,codex,aider) onPATHwith a print/non-interactive mode. Detecting capability: if you (the in-session agent) can shell out to invoke a CLI agent non-interactively — the same checkscripts/loop.sh's wiring already assumes (docs/operator/running-the-loop.md"Wiring up your agent") — prefer launching the loop over continuing in-session, even for small/medium work. Run it sandboxed and disk-conscious with--devcontainer(references/devcontainers.md) when you want the loop isolated from the host without a new multi-GB image per project. - Ralph-lite (fallback) — run the loop in-session when no headless invocation exists (a purely
interactive host) or the work is Quick-track (a whole subprocess loop is overkill for one short
prompt once clarification lands). Compensate for context accumulation with strict persistence:
after every iteration, write the next state into
IMPLEMENTATION_PLAN.mdso a fresh agent could continue. - Multi-layer builds — when
references/gene-transfusion.md's genes artifact reveals a clear component dependency DAG, neither mode has to converge the whole system in one monolithic pass: see that reference's "Composed convergence" section for topologically-sorted per-component mini-loops that finish with an integration-validation pass.
The per-iteration algorithm
Analyze → Implement → Validate → Review → Record
- Analyze — read only
IMPLEMENTATION_PLAN.md, the relevant spec, and this task's files. If the host exposes an optional MCP code-intelligence layer (for example,oraios/serena), use it to narrow symbol / call-graph / cross-reference lookup before falling back to grep; wgm itself does not bundle or require that retrieval layer. If the requested outcome crosses an intermediary owned by neither the caller nor the target (CDN, managed proxy, gateway, mesh, ingress, or platform service), read that component's documented eligibility and transformation rules before proposing a config change. Check request-side ineligibility, existing response transformations, and whether the named artifacts are actually active in production; a correct origin policy can be inert or a second compression tier can regress output. - Implement — smallest change that completes one task; prefer a working vertical slice.
- Validate — run the task's backpressure command. Green or it isn't done.
- Review — diff check: scope creep, acceptance met, signal actually proves the task. Record
the iteration's ruggedness verdict (below) — no verdict, no
done. - Record — update the plan: status, results, follow-ups. Once the task's validation
command exits 0, commit that iteration with a Conventional Commits message (
type: imperative description, ≤72 chars;fix/feat/refactor/docs/test/chore/build/ci) so history mirrors the one-task loop; if git is unavailable, the plan-file record is still required. Adapted fromAider-AI/aider's Conventional-Commits-style auto-commit habit. Make it fresh-agent-resumable.
Backpressure in depth
- PR-level CI green is not proof of a real deploy. When a project has a deploy pipeline distinct
from its per-PR checks — a common shape: PR-triggered checks run tests/lint/build-smoke, while a
separate workflow, triggered only on push to the default branch after merge, does the real
build-push-migrate-deploy — treat
gh pr checksgoing green as proof the change is safe to merge, never as proof it shipped. Whenever a task's acceptance criteria implies reaching a running deployment (not just a merged commit), explicitly check that separate deploy workflow's own most-recent run status too (e.g.gh run list --workflow=<deploy-workflow-file> --limit 1). A real dogfood run merged 8 PRs across a multi-hour session, each only after its own PR-level CI was green, while a separate post-merge deploy workflow's database-migration step had been silently failing since the very first merge — 7 consecutive failed deploys, invisible because nothing ever checked that second workflow's run history, discovered only when the human asked why "complete" work wasn't visible on the live site. Treat a currently-red deploy workflow as a standing blocker surfaced immediately (mid-loop, at Record — not deferred to a Ship/Handoff afterthought), since every later iteration otherwise silently builds on an environment that never received the earlier "done" work ([learn]issue #60). - Map every acceptance criterion to a runnable check. If the project has none, the first task is to build one (a failing test, a curl probe, a type-check command).
- Check the toolchain is present before checking it's compatible. Before reasoning about whether
a runtime is newer/odder than a tool's tested baseline (below), do a trivial presence check first
—
which <tool>or a version probe — that the required runtime/toolchain is installed at all. A real dogfood run's first validation attempt failed immediately because the sandbox had nojava/JAVA_HOME, forcing a mid-task detour before any real work could start; a one-line presence check at T1 is strictly cheaper than the end-to-end proof below and catches a more basic failure mode ([learn]issue #57). - De-risk an unusual runtime at T1. If the build's host runtime is newer, older, or odder than a key tool's tested baseline (a bleeding-edge language major vs. a framework's LTS target), make the first task prove the whole toolchain end-to-end — install it, get a trivial hello-world through the real build/run path, assert the real output exists — before any feature work, and pre-commit a fallback (pin a known-good version) in case it misbehaves. This extends "the first task is to build a validation signal" to the runtime/environment axis, not just the test harness.
- Prefer fast, deterministic signals. A 2-second deterministic check beats a 30-second flaky one.
- A green isolated retry does not clear a failed full-suite gate. When a test fails in the suite
and passes when run alone, the isolated pass is not the answer — it is a second data point, and
the difference between the two runs is the bug. Look at lifecycle ordering first: an operating
system can discard buffered output that was never read when a producer tears down, so a consumer
that "sees nothing" under suite conditions is a real race, not flakiness. Fix it by synchronizing
producer teardown to consumer acknowledgement, stress the exact test repeatedly to prove the race
is gone, and then rerun the complete gate — the full suite is the gate, and only a green full
suite clears it (
[learn]issue #80). - Take test-filter evidence from the runner, not from source scans. When acceptance or a holdout
says "the N tests matching
<filter>pass," get N from the runner's own reported selected/passed counts and preserve the exact command. Do not infer it by grepping source for functions whose names start with the filter: runners commonly match a filter anywhere in a fully qualified test name, so a static count and the real count legitimately differ. When they disagree, treat it as a measurement defect in your counting method until authoritative runner output says otherwise — concluding "tests are missing" from a source-prefix scan sends you chasing a phantom gap ([learn]issue #81). - Check whether CI is even defined on the branch you target. A workflow can live only on an
unmerged sibling branch, so tightly-scoped local gates go green while the workspace-wide CI that
will eventually gate the base branch fails — on pre-existing debt in code your change never
touched. Before reporting "validated," enumerate workflow files across all branches, not just
base/HEAD (
git ls-tree <branch> .github/workflows, or the host API's per-branch contents). Then distinguish in the handoff between "this change's own files pass the workspace gate" (verifiable now) and "the whole workspace passes it" (may be blocked by pre-existing debt). Record that debt as a dedicated chore task — do not fold unrelated fmt/lint cleanup into a feature PR to chase a not-yet-active CI, and do not let it silently sink the first PR that lands after the CI does ([learn]issue #77). - Exit codes are still only one signal.
scripts/loop.shnow probes write capability before a build and treats repeatedplan_changed=0build iterations as a first-class stall, but it does not semantically judge an agent's prose response. A future enhancement pattern is semantic exit / response analysis — inspect response content as well as exit code, asralph-claude-codedoes. - Spec-drift pre-check (optional, cheap). Before running the task's full validation command,
diff the files actually touched against the task's declared files/areas in
IMPLEMENTATION_PLAN.md. A mismatch (e.g. a task scoped toschema/touching UI files) is a cheap, early signal of scope or spec drift — worth flagging before spending the validation budget on a run that was never going to prove the right thing. Adapted fromsaitarrun/devforge-ai'sralph-loopskill (its "Sentinel" health check, which also flags "context drift" — a new log entry conspicuously shorter than prior ones). - Include at least one end-to-end demo check that exercises the spec's smallest demo path — narrow unit/build checks can pass while the actual user flow is broken.
- Only when no deterministic check can exist (UX feel, copy, aesthetics) fall back to an LLM-as-judge with a binary pass/fail; record its prompt and verdict, and accept that it varies run to run.
- For holistic/holdout judgment, an LLM-as-judge satisfaction score (0–100) against holdout
scenarios can augment binary checks; converge to a threshold (default 95). Deterministic checks
still gate "done." See
scoring.mdandscenarios.md. - "Important: when authoring code and docs, capture the why — and the test that proves it."
- Project-wide gates (a floor): an optional
wgm.yml(or.wgm/gates.yml) lists commands every build iteration must keep green — a backpressure floor independent of each task's own check.loop.shexecutes them after the agent returns and also injects the list into the prompt (--gates FILEto override).
The ruggedness gate in the loop
Backpressure proves the change works; the ruggedness gate asks whether it holds up for the
operators and environment that actually run it. It is part of the loop's Review step on every
track, not an optional extra (SKILL.md, "The ruggedness gate"; references/artifacts.md,
.wgm/rugged/*).
- Every Review records exactly one verdict. Run
/rugged review(or/rugged stresswhen only the field evidence is in question) over the iteration's diff. RUGGED passes; FRAGILE blocks the task and adds a remediation task; UNKNOWN blocks the task and adds a validation-signal task that creates the missing deterministic field/stress evidence. UNKNOWN is a blocking gap, never a soft pass. - No companion, no bypass. If
/ruggedis not discoverable, run the five-step rubric inline — actual operator/environment · intrinsic-design / user-capacity / operational-stress decomposition · simplification · the exact runnable check with its expected observation and recovery criterion · one verdict — and record both the verdict and the missing capability. An absent companion is a recorded fallback, not a pass, and a serial single-agent host runs the identical rubric (references/subagents.md). - Review evidence is real, Plan-exit evidence is a design. In the loop the implementation exists, so a promised future check is not evidence: execute or inspect the actual field/stress result. The weaker plan-readiness standard (an exact runnable check design) belongs to Plan-exit only.
- It is not a second deterministic gate. The task's own validation command still has to exit 0;
the verdict sits beside it, and
doneneeds both.
Standing guardrails
Inject these into every iteration — they prevent recurring loop failures:
- Search before you build. Before adding code, grep the codebase for an existing implementation
(parallel searches help). The classic Ralph failure is re-implementing something that already
exists because one quick search came up empty. Don't assume a feature is missing — prove it is.
Extract the key identifiers the task actually names (function / class / variable names), not just
conceptual keywords, and search those exact names first. For each hit, also inspect what imports
or calls it; the defining file is only half the surface area. Deprioritize identifiers that show
up everywhere (generic helpers, framework builtins) — they are usually noise, not signal.
Adapted from
Aider-AI/aider'saider/repomap.py. Widen the search past the local repo: also check declared dependencies, mandated companion tools, and the platform/runtime for the capability — a project's "additive to X, never duplicate X" rule is only enforceable if Analyze actually inspects X. If a dependency or companion already ships it, drop the build task, wire/enable the existing provider instead, and record why. - Document why each test exists. When you add a test, record in a comment what behavior it proves and why it matters. A fresh context that can't see the rationale may delete the test as an orphan, silently dropping coverage.
- Format only what you touched. Never run a project- or crate-wide auto-formatter mid-iteration — it rewrites files this task never touched, leaving stray churn that can block a branch switch and buries the one-task diff in reformatting noise. Hand-format to the house style, or format only the exact files this task changed; run a full reformat (if ever needed) as its own separate, reviewed change.
- Corrected facts require a corpus sweep. When a path, prefix, default, version, permission, or other fact changes, search both the old and new values across the full documentation corpus, including historical/runbook copies and copy-paste commands. If the claim depends on a measurement, record the tool, input, date, and regeneration command so a reviewer can reproduce it.
- Commit ownership is explicit.
loop.sh --committakes exclusive ownership of the worktree. Start clean, declare the files intentionally changed in the iteration manifest, and refuse to stage undeclared paths. A concurrent human edit belongs in another worktree or after the loop stops.
Memory (cross-iteration learning)
Fresh context per iteration is Ralph's strength, but it also forgets. A small, token-budgeted
.wgm/memories.md closes that gap without re-polluting context:
- Recall in Analyze: read it first so you don't re-hit a known gotcha or re-walk a dead end.
- Append in Record: add the one-line lesson from this iteration (a fix that worked, a gotcha, a stall's cause). Keep it within ~2000 tokens — trim the oldest when over.
- Outgrown the flat log? For optional named alternatives for long builds, see
references/memory-patterns.md. - Cross-check prior commitments at Ship/Handoff: if a previous session's memories include an
explicit "resolve to..." / "next time..." commitment and this build reused the same
.wgm/memories.md, add a one-line ✅/❌ note on whether it actually happened. This is a deliberately lighter-weight adaptation ofBMAD-METHOD'ssrc/bmm-skills/4-implementation/bmad-retrospective/SKILL.md, not BMAD's full ceremony — wgm has no epic/story structure to retro against. - Promote at handoff: a lesson that is durable and cross-project can graduate upstream — a
sanitized
[learn]report that lands in the shared skill's ledger (references/self-improvement.md). - It is not
AGENTS.md(curated how-to) orIMPLEMENTATION_PLAN.md(task state); it is the raw lessons log. Seereferences/artifacts.md.
Context-hygiene gate (every iteration)
- Read the minimum set, not the whole repo.
- Advance exactly one task.
- End by writing handoff-quality state into the plan.
- If context feels bloated, stop and hand off rather than push on.
Context rotation (summarize, then refresh)
Long in-session runs degrade as the window fills — the ecosystem's fix is to rotate context at a token threshold (e.g. cursor rotates ~80k tokens) and summarize forward (Vercel's loop) so no progress is lost. wgm's rule:
- Set a budget. Pick a threshold well below the model's window — a practical default is ~50% of it, or a host-configured token cap. Treat crossing it as a stop signal, not a soft suggestion.
- Summarize before you rotate. Write handoff-quality state into
IMPLEMENTATION_PLAN.md(task statuses + the exact next step) and append durable lessons to.wgm/memories.md. The summary — not the transcript — is what survives the rotation. - Rotate to fresh context. Start the next iteration clean, reloading only the lean plan, the
relevant spec,
.wgm/memories.md, andspecs/CONTEXT.md— never the old transcript. - Ralph-full already rotates every iteration (a fresh process per loop); Ralph-lite rotates
on the threshold. Either way the persistent files are the memory — keep them lean (
artifacts.md). - Small/local-context models (≲65–100k tokens): the generic ~50% default under-rotates — the
fixed skill+plan+spec overhead is a bigger slice of a small window. Rotate earlier (~35–40% of the
window) and read narrower; see
references/local-models.mdfor concrete budgets.
Stop / regenerate conditions
- All must-have tasks are
done→ ship/handoff. - The same task fails ~3 times, or the satisfaction score stalls → first run a wonder/reflect
recovery and consider model escalation (
stall-recovery.md); if still stuck, record the blocker, stop, ask or regenerate the plan. Regenerating the plan is cheap; a loop going in circles is not. - If
[INVALIDATES: ...]flags (the plan-invalidating-discovery tag fromreferences/self-improvement.md's Harvest step) keep piling up across still-pending tasks, a stall pattern keeps recurring on different tasks, or the build's actual path no longer matches the spec's magic moment / demo path, treat it as plan drift, not just one task needing a better Record step. - When the plan is structurally stale, regenerate
IMPLEMENTATION_PLAN.mdfrom the current spec, the completed work, and the accumulated memories instead of incrementally patching a plan whose assumptions no longer hold. - The trajectory is clearly wrong (building the wrong thing, duplicating work) → stop and re-plan.
- Autonomous + manual-merge: cap concurrent open PRs (~3-5). In an autopilot loop that ships one PR per iteration into a repo a human merges by hand, an open-ended stream of net-new PRs floods the maintainer and deepens the shared-file conflict surface while nothing actually lands. Past the cap, the next iteration's task becomes consolidation — help land, rebase, or merge existing PRs — instead of opening another one; merged value beats more net-new work.
Keep AGENTS.md lean
AGENTS.md is operational only: how to build, run, and validate, plus durable codebase patterns.
Status updates and progress notes belong in IMPLEMENTATION_PLAN.md. A bloated AGENTS.md
pollutes every future iteration's context.