The Ralph Wiggum Technique: Autonomous loops with OpenCode
AI coding agents have a tendency to declare victory prematurely. The agent makes changes, runs into an issue, makes a reasonable attempt to fix it, then stops and says "I've completed the task"—even when the tests are still failing or the feature doesn't work.
The Ralph Wiggum technique addresses this by creating autonomous loops: the agent keeps iterating until the task is actually done, not just attempted.
Why "Ralph Wiggum"?
The name comes from The Simpsons character known for persistent (sometimes oblivious) optimism in the face of setbacks. The technique embodies that spirit: keep trying, learn from each attempt, don't give up after the first obstacle.
The key insight is that each iteration isn't starting fresh. The agent sees what it built in the last round—the code changes, the error messages, the test output. It can diagnose why the previous attempt failed and try a different approach.
How it works
The basic mechanism:
- Provide a prompt with clear success criteria (e.g., "all tests pass")
- The agent attempts the task and tries to exit
- A hook intercepts the exit and checks if success criteria are met
- If not met, the original prompt reinjects with context about what was tried
- The agent reviews its previous work, identifies issues, and iterates
- Process repeats until success criteria are satisfied
The hook acts as a gatekeeper. The agent can't declare the task complete until verifiable conditions are met.
Implementing with OpenCode
OpenCode's plugin system provides hooks for intercepting session events. The session.idle hook fires when the agent finishes working—this is where the loop logic lives.
Create a plugin at .opencode/plugins/ralph.ts:
import type { Plugin } from "opencode";
const PROMISE_TAG = "<promise>";
const MAX_ITERATIONS = 20;
let iterationCount = 0;
let originalPrompt = "";
export default function ralphPlugin(): Plugin {
return {
name: "ralph-wiggum",
hooks: {
"session.idle": async (ctx) => {
const lastMessage = ctx.session.messages.at(-1);
const content = lastMessage?.content || "";
// Check if the promise tag is present (success criteria met)
if (content.includes(PROMISE_TAG)) {
console.log(`[Ralph] Success after ${iterationCount} iterations`);
iterationCount = 0;
return; // Allow session to end
}
// Check iteration limit
if (iterationCount >= MAX_ITERATIONS) {
console.log(`[Ralph] Max iterations reached`);
iterationCount = 0;
return;
}
iterationCount++;
console.log(`[Ralph] Iteration ${iterationCount}, continuing...`);
// Reinject the prompt with iteration context
await ctx.session.send({
role: "user",
content: `The task is not complete yet. This is iteration ${iterationCount}.
Previous attempt did not satisfy the success criteria. Review what was tried, identify what went wrong, and try again.
Original task:
${originalPrompt}
Remember: Include ${PROMISE_TAG} in your response only when the task is fully complete and verified.`
});
},
"message.created": async (ctx) => {
// Capture the original prompt on first user message
if (ctx.message.role === "user" && iterationCount === 0) {
originalPrompt = ctx.message.content;
}
}
}
};
}Using the ralph CLI tool
If you don't want to write your own plugin, there's a dedicated CLI tool that implements this pattern: opencode-ralph-wiggum.
Install it globally:
npm install -g @th0rgal/ralph-wiggumThen run tasks with automatic iteration:
ralph "Refactor auth.ts to use JWT tokens. Run tests after each change. \
Output <promise>COMPLETE</promise> when all tests pass." --max-iterations 20The CLI handles the loop logic, state management, and provides useful features like:
--max-iterationsto limit iterations--completion-promiseto customize the success markerralph --statusto monitor progress from another terminalralph --add-context "hint"to inject guidance mid-loop
State is stored in .opencode/ including iteration history and pending context hints. This makes it easy to track what the agent tried across iterations.
Structuring prompts for loops
The prompt needs clear, verifiable success criteria. The agent should know exactly what "done" looks like.
Refactor the authentication module to use JWT tokens instead of sessions.
Success criteria:
- All existing auth tests pass
- New tests cover JWT token generation and validation
- The login endpoint returns a JWT token
- Protected routes validate the JWT token
Run the test suite after each change. Only include <promise> in your
response when ALL tests pass and the criteria above are met.The <promise> tag acts as a gate. The agent includes it only when it believes the task is complete. The hook verifies this by checking for the tag.
When this works well
The technique is effective for:
- Test-driven development – Iterate until all tests pass. The test suite is the verifiable success criterion.
- Large refactors – Framework migrations, API updates across many files. The agent can work through errors incrementally.
- Batch operations – Adding documentation to every function, standardizing code style. Run linters/checks after each iteration.
- Build fixes – Keep iterating until the build succeeds. Each error message guides the next attempt.
When to avoid it
Autonomous loops are not suitable for:
- Ambiguous requirements – If success can't be verified automatically, the loop has no exit condition.
- Architectural decisions – Design choices need human judgment, not iteration.
- Security-critical code – Autonomous changes to auth, encryption, or access control should be reviewed by humans.
- Exploratory work – Research, prototyping, and "figure out how this works" tasks don't have clear completion criteria.
Cost considerations
Each iteration consumes tokens. A 20-iteration loop on a medium-sized codebase can cost $50-100+ in API usage depending on the model.
Mitigations:
- Set a reasonable
MAX_ITERATIONSlimit - Use faster/cheaper models for iterative work
- Make success criteria specific so the agent doesn't thrash on vague goals
- Start with smaller tasks to calibrate iteration counts
Running autonomous loops with codecloud
The local plugin approach works for development, but for production workflows—CI/CD pipelines, automated ticket processing, scheduled tasks—codecloud provides a simpler path.
codecloud runs OpenCode agents in the cloud. While the plugin-based loop runs locally, you can achieve similar iterative behavior through the API by:
- Making an initial request with your task and success criteria
- Using a webhook to receive completion notification
- Checking the result (run tests, verify output) in your webhook handler
- If criteria aren't met, triggering a new run with context from the previous attempt
// Webhook handler with iteration logic
app.post('/webhook/agent-complete', async (req, res) => {
const { id, status, result, pr_url } = req.body;
const taskContext = await getTaskContext(id);
if (status !== 'completed') {
await handleFailure(taskContext, result);
return res.status(200).send('OK');
}
// Run verification (e.g., trigger CI on the PR)
const testsPass = await runVerification(pr_url);
if (testsPass) {
await markTaskComplete(taskContext);
return res.status(200).send('OK');
}
// Tests failed - iterate if under limit
if (taskContext.iterations < MAX_ITERATIONS) {
await triggerNextIteration(taskContext, result);
} else {
await markTaskFailed(taskContext, 'Max iterations reached');
}
res.status(200).send('OK');
});
async function triggerNextIteration(ctx, previousResult) {
await fetch('https://codecloud.dev/api/v1/agents', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
repo: ctx.repo,
prompt: `Previous attempt failed verification. Iteration ${ctx.iterations + 1}.
Previous result:
${previousResult}
Original task:
${ctx.originalPrompt}
Review what was tried, identify the issue, and try again.`,
auto_create_pr: true,
webhook_url: 'https://your-server.com/webhook/agent-complete'
})
});
await incrementIteration(ctx.id);
}This approach moves the loop logic to your server rather than relying on local hooks. It works with any CI system and integrates with existing workflows.
You can also use codecloud's plan mode first to verify the approach, then execute with auto-PR. If CI fails, trigger another run with the failure context.
For questions or feedback, use the support portal. For API details, see the documentation.