Search

Sign in to launch Copilot/Codex from the palette.

RALPH WIGGUM TECHNIQUE

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

AI CODING PATTERN 2026.01.05
Ralph Wiggum Technique illustration

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: false flags)
  • 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

Ralph Wiggum Technique illustration

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 
done

Pattern 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
done

Pattern 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.

Ralph Wiggum Technique illustration