RALPH WIGGUM TECHNIQUE
Run AI agents in a loop until they're done. Deterministically bad beats unpredictably good.

THE AI AGENT FAILURE PROBLEM
Problem: A one-shot coding run can exhaust its context while the task is still open. The next run then needs the changed files, completed checks, and remaining work without replaying the whole session.
Implementation: The Ralph loop gives each run a scoped task, persists work in git or a progress file, runs checks, and stops on an explicit completion signal or iteration limit.
Loop Starts → Agent Works on Task ↓ Check Stop Condition (<promise>COMPLETE</promise>) ↓ If Not Complete → Commit Work → Append Progress → Run Tests ↓ Next Iteration (Agent Sees Previous Work) ↓ Repeat Until Complete or Max Iterations
The Philosophy
"Deterministically Bad Beats Unpredictably Good"
Named after Ralph Wiggum—keeps trying until it works. As Geoffrey Huntley puts it: "That's Ralph - the technique is deterministically bad in an undeterministic world."
Failures are data. The loop refines work. Prompt engineering becomes the skill, not perfect one-shot prompts. LLMs mirror operator skill—experienced engineers can be unskilled at AI. The loop makes skill development explicit.
- Agents see mistakes in git history
- Each iteration is fresh with more context
- Small tasks fit context windows
- Feedback loops catch errors immediately
Core Components
- Loop:
while :; do cat PROMPT.md | npx --yes @sourcegraph/amp ; done - Stop condition: Agent outputs
<promise>COMPLETE</promise>or max iterations - Persistent state: Git history, modified files,
progress.txt - Scoped tasks: PRD with user stories (
passes: falseflags) - Feedback loops: Tests/typechecks on each iteration
Minimal Implementation
Absolute minimal: DO + Worker in one file. ~60 lines. Demonstrates the core loop pattern.
// ralph.ts - Everything in one file (DO + Worker)
export class RalphDO extends DurableObject {
private iteration = 0;
private code = '';
private status = 'pending';
async fetch(req: Request): Promise<Response> {
const url = new URL(req.url);
if (url.pathname === '/start' && req.method === 'POST') {
const { task } = await req.json();
this.run(task); // Fire and forget
return Response.json({ started: true });
}
return Response.json({
iteration: this.iteration,
code: this.code,
status: this.status
});
}
async run(task: string) {
while (this.iteration < 10) {
this.iteration++;
this.status = 'working';
// Call AI with previous code
const prompt = `Task: ${task}
Previous code:
${this.code}
Continue or output <promise>COMPLETE</promise> if done.`;
const response = await this.env.AI.run('@cf/meta/llama-3.1-8b-instruct', {
messages: [{ role: 'user', content: prompt }]
});
const text = response.response;
this.code = text.includes('```') ? this.extractCode(text) : text;
if (text.includes('<promise>COMPLETE</promise>')) {
this.status = 'complete';
break;
}
await new Promise(r => setTimeout(r, 2000));
}
}
extractCode(text: string): string {
const match = text.match(/```[\w]*\n([\s\S]*?)```/);
return match ? match[1] : text;
}
}
// Worker
export default {
async fetch(req: Request, env: Env): Promise<Response> {
const url = new URL(req.url);
if (url.pathname === '/ralph/start' && req.method === 'POST') {
const { task } = await req.json();
const stub = env.RALPH.getByName('demo');
await stub.fetch('http://do/start', {
method: 'POST',
body: JSON.stringify({ task })
});
return Response.json({ sessionId: 'demo' });
}
if (url.pathname === '/ralph/status') {
const stub = env.RALPH.getByName('demo');
return stub.fetch('http://do/');
}
return new Response('Not found', { status: 404 });
}
};DO runs the loop, calls Workers AI each iteration, checks for <promise>COMPLETE</promise>, stores state. Worker routes requests. Frontend polls for status.
Implementation Patterns

The Tuning Metaphor
Geoffrey Huntley describes Ralph like tuning a guitar. When Ralph takes a wrong direction, you don't blame the tools—you look inside. You tune Ralph by adding "signs" (better prompts, clearer instructions). Eventually, Ralph only thinks about the signs, and you get a new Ralph that doesn't feel defective at all.
Pattern 1: Pure Bash Loop
Geoffrey Huntley's purest form:
# Purest form - infinite loop until agent signals completion
while :; do
cat PROMPT.md | npx --yes @sourcegraph/amp
donePattern 2: Bash with Safety
#!/bin/bash
MAX_ITERATIONS=10
for i in $(seq 1 $MAX_ITERATIONS); do
echo "Iteration $i"
# Run agent, capture output
OUTPUT=$(cat PROMPT.md | npx --yes @sourcegraph/amp)
# Check for completion signal
if echo "$OUTPUT" | grep -q "<promise>COMPLETE</promise>"; then
echo "Task complete!"
break
fi
# Commit work
git add -A
git commit -m "Iteration $i: $(echo $OUTPUT | head -c 50)"
# Run tests
npm test || echo "Tests failed, continuing..."
sleep 2
donePattern 3: PRD-Based Scoping
Matt Pocock's method—JSON PRD with user stories:
// prd.json
{
"userStories": [
{
"id": "auth-login",
"title": "User can log in with email/password",
"priority": 1,
"passes": false
},
{
"id": "auth-logout",
"title": "User can log out",
"priority": 2,
"passes": false
}
]
}// Agent prompt includes PRD
const prompt = `
You are working on: ${prd.userStories.find(s => !s.passes)?.title}
PRD:
${JSON.stringify(prd, null, 2)}
Previous work (git log):
${gitLog}
Current progress:
${readFile('progress.txt')}
Work on ONLY the highest priority story that doesn't pass.
When done, update the PRD: set passes: true for that story.
Output <promise>COMPLETE</promise> when all stories pass.
`;The PRD Approach
Matt Pocock's method: JSON PRD with user stories. Prevents context overflow, clear stop condition, formalizes work into chunks.
// prd.json
{
"userStories": [
{
"id": "auth-login",
"title": "User can log in with email/password",
"priority": 1,
"passes": false
},
{
"id": "auth-logout",
"title": "User can log out",
"priority": 2,
"passes": false
}
]
}Agent picks highest priority story with passes: false, works on it, updates PRD when done, outputs <promise>COMPLETE</promise> when all pass.
When to Use
Good For:
- Large features needing multiple iterations
- Tasks where agent needs to see mistakes
- Work benefiting from incremental commits
- Greenfield projects (can replace outsourcing)
Not Good For:
- Simple one-shot tasks
- Tasks needing human review between iterations
- Time-sensitive tasks (loop adds latency)
Real-World Results
- $50k → $297: Contract delivered as MVP using @ampcode
- YC Hackathon: "6 Repos Overnight"
- CURSED language: Built and programmed in a language not in training data
So, I think the takeaway is: Treat AI agents like iterative processes, not one-shot tools. The loop is the pattern. The agent is the worker. The feedback is the teacher.
