A development pattern where an AI agent runs in a bash loop, iterating on tasks until a machine-verifiable exit condition is met. State persists in files (PLAN.md) and git, not in the LLM's context window.
while tasks_remain; do
claude -p "Read PLAN.md. Do the next task. Validate. Mark done."
doneNamed after the Simpsons character. Each iteration starts with fresh context — the agent reads the plan from scratch, picks up where the previous iteration left off, and works on one task.
| Signal | Single-Shot | Ralph Loop |
|---|---|---|
| Output needs human review | Yes | No |
| Clear pass/fail criteria | Either | Yes |
| Might need multiple attempts | No | Yes |
| Requires judgment or taste | Yes | No |
| Tightly bounded scope | Either | Yes |
| Architectural decisions | Yes | No |
Use Ralph for: Bug fixes, boilerplate, test writing, "make the build pass" work, migration scripts, refactoring with clear patterns.
Use single-shot for: Documentation, UX copy, API design, architectural decisions, anything where "done" requires human judgment.
The plan file is the central state artifact. The agent reads it fresh every iteration.
# Plan: [Feature/Fix Name]
## Validation
Run after every task. All must pass before marking [x].
- `npm run build`
- `npm test`
## Tasks
- [ ] First task — `file.tsx:line` — what to change and why
- [ ] Second task — `file.tsx:line` — specific, measurable outcome
- [ ] **HARD STOP** — Risky task — review before continuing
- [ ] Fourth task — `file.tsx:line` — depends on third task
## Completed
(agent moves checked items here to keep the task list focused)- One task per checkbox — specific, measurable, with
file:linereferences - HARD STOP markers — agent completes the task, commits, and stops. The loop restarts with fresh context, giving you a chance to review
- Completed section — agent moves
[x]items here to keep the active list short as context grows - Validation at top — agent runs these after every task
- Be specific: "Remove the
!isMobileguard attodo-card.tsx:83" not "Fix mobile checkbox" - Be measurable: Include success criteria the agent can verify
- Wrong file paths — verify every
file:linereference before starting the loop. Wrong paths waste iterations. - Tasks too large — each task should be completable in one iteration (5-15 minutes). Split large tasks.
- Tasks too vague — "improve the component" is not a task. "Add error boundary to
UserProfile.tsxthat catches fetch failures and shows a retry button" is. - Missing dependencies — if task 3 depends on task 2, add a note. Better yet, use HARD STOP if the dependency is critical.
A short file (~30-60 lines) with validation commands and code constraints. Lives alongside PLAN.md in the worktree.
# Agent Instructions
## Validation Commands
Run these after every task. All must pass before marking [x].
- `npm run build`
- `npm test`
## Code Guidelines
- Follow existing patterns in the codebase
- Keep changes minimal — fix the specific issue, don't refactor surroundings
- Do not add new dependencies without justification
## Stuck Protocol
If tests fail 3 times on the same issue:
1. Add a STUCK note to the task in PLAN.md
2. Move to the next task
3. Do NOT disable tests or loosen typesloop.sh handles the full lifecycle:
- Pre-flight checks — PLAN.md exists, AGENTS.md exists
- Dependency install —
npm cifor Node projects (worktrees don't havenode_modules) - Env file setup — symlink
.env.localfrom main repo - Pre-flight build — verify the baseline compiles before starting
- Main loop — invoke
claude -pwith scoped permissions, check for completion - Safety net — auto-commit staged changes if agent didn't commit
The --allowedTools flag pre-approves specific tools so the agent runs unattended:
--allowedTools "Edit" "Write" "Read" "Glob" "Grep" \
"Bash(npm run build)" "Bash(npm test)" \
"Bash(git add *)" "Bash(git commit *)" "Bash(git diff *)" "Bash(git status)"Critical: Do NOT use Bash(*). Scoped permissions prevent the agent from running unexpected commands.
In production, the agent commits on its own about 1 in 9 times. It reliably stages changes (git add) but rarely follows through with git commit. The safety net catches this:
if ! git diff --cached --quiet; then
git add PLAN.md
git commit -m "ralph: auto-commit from iteration $ITERATION"
fiThis is essential infrastructure, not optional.
Ralph loops use worktrees for isolation — each agent gets its own working directory and branch:
# Create
git worktree add -b fix/feature-a ../project-wt-feature-a
# Copy plan
cp .ralph/plans/feature-a.md ../project-wt-feature-a/PLAN.md
# Run loop
cd ../project-wt-feature-a
bash ../project/.ralph/loop.sh 15
# When done, merge and clean up
cd ../project
bash .ralph/cleanup.sh feature-a| Issue | Solution |
|---|---|
No node_modules |
loop.sh runs npm ci automatically |
No .env.local |
loop.sh symlinks from main repo |
Need -b flag |
git worktree add -b branch path for new branches |
| Stale symlinks | loop.sh detects and removes them |
Cleanup needs --staged |
git restore --staged --worktree . for full reset |
Run multiple Ralph loops simultaneously in separate tmux windows:
# Launch (from main repo)
bash .ralph/launch.sh teammate-a .ralph/plans/teammate-a.md 15
bash .ralph/launch.sh teammate-b .ralph/plans/teammate-b.md 20
# Monitor from main window
while true; do
clear
for wt in teammate-a teammate-b; do
echo "=== $wt ==="
grep '^\- \[' ../project-wt-$wt/PLAN.md
echo ""
done
sleep 10
done
# Clean up when done
bash .ralph/cleanup.sh teammate-a
bash .ralph/cleanup.sh teammate-bApproximate per-iteration cost with Claude Sonnet: ~$1-2. A 10-task feature across 3 worktrees typically costs $15-25 total.
Always set max_iterations to cap cost. If the agent can't solve it in N iterations, it's better to escalate to a human than burn tokens.