Back to skills
SKILL.md
Factory Ralph Loop
ASecurityIterative task execution using the Ralph Loop pattern (named after Ralph Wiggum). Use when you need to repeatedly run an agent until a condition is met—fixing all lint errors, passing all tests, or exhausting PRD tasks. The filesystem serves as memory between iterations.
- 57 stars
- 0 votes
- 0 copies
- 0 views
- Added October 17, 2026
Works with
Security analysis
100/100npx -y skills add diegosouzapw/awesome-omni-skill --skill factory-ralph-loop --agent claude-codeAre you the author of Factory Ralph Loop?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/diegosouzapw-factory-ralph-loop)---
name: factory-ralph-loop
description: Iterative task execution using the Ralph Loop pattern (named after Ralph Wiggum). Use when you need to repeatedly run an agent until a condition is met—fixing all lint errors, passing all tests, or exhausting PRD tasks. The filesystem serves as memory between iterations.
---
# Ralph Loop Pattern
The Ralph Loop (named after Ralph Wiggum) is an agentic pattern where you run an AI agent in a continuous loop until a task is complete. Each iteration starts relatively fresh, with the filesystem serving as persistent memory.
## Core Characteristics
1. **Same systemPrompt repeated** — The agent receives consistent instructions each iteration
2. **Filesystem as memory** — Code changes persist on disk between iterations
3. **Fresh context** — Each iteration reduces context pollution vs. single long conversation
4. **Exit condition** — Loop ends when tests pass, lint is clean, or work is exhausted
5. **Simple orchestrator** — Just `while (!done) { run agent }`
## Basic Structure
```typescript
const maxIterations = 10;
let iteration = 0;
let done = false;
while (!done && iteration < maxIterations) {
iteration++;
factory.observe.log("info", `Iteration ${iteration}`, { maxIterations });
const result = await factory.spawn({
agent: "worker",
systemPrompt: "You are fixing issues iteratively",
prompt: "Fix the next issue",
model: "anthropic/claude-sonnet-4-6",
step: iteration,
});
// Check exit condition
done = result.exitCode === 0 && result.text.includes("all clean");
if (result.exitCode !== 0) {
factory.observe.log("error", "Agent failed", { iteration, error: result.errorMessage });
break;
}
}
```
## Pattern 1: Fix All Lint Errors
Repeatedly run an agent until lint is clean:
```typescript
import { spawnSync } from "node:child_process";
const maxIterations = 20;
let iteration = 0;
while (iteration < maxIterations) {
iteration++;
const lintResult = spawnSync("npm", ["run", "lint"], {
cwd: process.cwd(),
encoding: "utf-8",
});
if (lintResult.status === 0) {
factory.observe.log("info", "Lint clean!", { iterations: iteration });
break;
}
factory.observe.log("info", `Iteration ${iteration}`, {
exitCode: lintResult.status,
errorCount: (lintResult.stdout.match(/error/gi) || []).length,
});
const result = await factory.spawn({
agent: "linter",
systemPrompt: `You fix lint errors iteratively.
Run 'npm run lint' to see current errors.
Fix one or more errors, focusing on the most common patterns.
Make minimal, focused changes.`,
prompt: `Fix lint errors. Current output:\n\n${lintResult.stdout}\n${lintResult.stderr}`,
model: "mistral/devstral-2512",
step: iteration,
});
if (result.exitCode !== 0) {
factory.observe.log("error", "Agent failed", { iteration });
break;
}
}
```
## Pattern 2: With Progress Tracking
Accumulate state across iterations to show progress:
```typescript
import { spawnSync } from "node:child_process";
interface ProgressState {
fixedIssues: string[];
lastErrorCount: number;
stagnantIterations: number;
}
const maxIterations = 20;
let iteration = 0;
const progress: ProgressState = {
fixedIssues: [],
lastErrorCount: Infinity,
stagnantIterations: 0,
};
while (iteration < maxIterations) {
iteration++;
const lintResult = spawnSync("npm", ["run", "lint"], {
cwd: process.cwd(),
encoding: "utf-8",
});
const errorCount = (lintResult.stdout.match(/error/gi) || []).length;
if (lintResult.status === 0) {
factory.observe.log("info", "All issues fixed!", {
iterations: iteration,
fixedIssues: progress.fixedIssues,
});
break;
}
// Track progress
if (errorCount >= progress.lastErrorCount) {
progress.stagnantIterations++;
} else {
progress.stagnantIterations = 0;
}
// Exit if stagnant
if (progress.stagnantIterations >= 3) {
factory.observe.log("warning", "No progress for 3 iterations", { errorCount });
break;
}
factory.observe.log("info", `Iteration ${iteration}`, {
errorCount,
lastErrorCount: progress.lastErrorCount,
fixed: progress.fixedIssues.length,
});
progress.lastErrorCount = errorCount;
const result = await factory.spawn({
agent: "fixer",
systemPrompt: `You fix lint errors iteratively.
Track your progress and avoid repeating unsuccessful approaches.
Previous fixes: ${progress.fixedIssues.join(", ") || "none yet"}
Error count: ${errorCount} (was ${progress.lastErrorCount === Infinity ? "unknown" : progress.lastErrorCount})`,
prompt: `Fix lint errors:\n\n${lintResult.stdout}\n${lintResult.stderr}`,
model: "anthropic/claude-sonnet-4-6",
step: iteration,
});
if (result.exitCode === 0) {
const fixMatch = result.text.match(/fixed?:?\s*(.+)/i);
if (fixMatch) {
progress.fixedIssues.push(fixMatch[1]);
}
}
}
```
## Pattern 3: Loop Until Tests Pass
Run agent repeatedly until test suite passes:
```typescript
import { spawnSync } from "node:child_process";
const testCommand = "npm test";
const [cmd, ...args] = testCommand.split(" ");
const maxIterations = 10;
let iteration = 0;
while (iteration < maxIterations) {
iteration++;
const testResult = spawnSync(cmd, args, {
cwd: process.cwd(),
encoding: "utf-8",
timeout: 60000,
});
if (testResult.status === 0) {
factory.observe.log("info", "Tests passing!", { iterations: iteration });
break;
}
factory.observe.log("info", `Iteration ${iteration}`, {
exitCode: testResult.status,
timeout: testResult.signal === "SIGTERM",
});
const failureOutput = [testResult.stdout, testResult.stderr]
.filter(Boolean)
.join("\n")
.slice(-5000); // Last 5KB to avoid huge prompt payloads
const result = await factory.spawn({
agent: "test-fixer",
systemPrompt: `You fix failing tests iteratively.
Analyze test output, identify the root cause, and make minimal fixes.
Run the tests again to verify your changes.
Focus on one failure at a time if there are multiple.`,
prompt: `Fix failing tests. Output from '${testCommand}':\n\n${failureOutput}`,
model: "anthropic/claude-opus-4-6",
step: iteration,
});
if (result.exitCode !== 0) {
factory.observe.log("error", "Agent failed", { iteration });
break;
}
}
```
## Pattern 4: Exhaustive PRD Implementation
Work through Product Requirements Document tasks until all are complete:
```typescript
import fs from "node:fs";
interface PRDTask {
id: string;
description: string;
completed: boolean;
}
const prdPath = "./PRD.md";
const tasksPath = "./tasks.json";
const maxIterations = 50;
// Load or initialize tasks
let tasks: PRDTask[];
if (fs.existsSync(tasksPath)) {
tasks = JSON.parse(fs.readFileSync(tasksPath, "utf-8"));
} else {
const prdContent = fs.readFileSync(prdPath, "utf-8");
tasks = parsePRD(prdContent);
fs.writeFileSync(tasksPath, JSON.stringify(tasks, null, 2));
}
let iteration = 0;
while (iteration < maxIterations) {
const nextTask = tasks.find(t => !t.completed);
if (!nextTask) {
factory.observe.log("info", "All tasks completed!", { iterations: iteration });
break;
}
iteration++;
factory.observe.log("info", `Iteration ${iteration}: ${nextTask.id}`, {
remaining: tasks.filter(t => !t.completed).length,
});
const result = await factory.spawn({
agent: "implementer",
systemPrompt: `You implement PRD tasks iteratively.
Read the PRD at ${prdPath}.
Complete tasks one at a time.
Mark tasks complete by updating ${tasksPath}.`,
prompt: `Implement: ${nextTask.id} - ${nextTask.description}\n\nCompleted so far:\n${
tasks.filter(t => t.completed).map(t => `+ ${t.id}`).join("\n")
}`,
model: "openai-codex/gpt-5.3-codex",
step: iteration,
});
if (result.exitCode !== 0) {
factory.observe.log("error", "Agent failed", { iteration, task: nextTask.id });
break;
}
// Reload tasks (agent may have updated them)
if (fs.existsSync(tasksPath)) {
tasks = JSON.parse(fs.readFileSync(tasksPath, "utf-8"));
}
}
function parsePRD(content: string): PRDTask[] {
const matches = content.matchAll(/^[-*]\s*\[\s*\]\s*(.+)$/gm);
const tasks: PRDTask[] = [];
let id = 1;
for (const match of matches) {
tasks.push({
id: `TASK-${id++}`,
description: match[1].trim(),
completed: false,
});
}
return tasks;
}
```
## Pattern 5: Combined Safety Checks
Comprehensive safety and exit logic:
```typescript
import { spawnSync } from "node:child_process";
const maxIterations = 20;
const maxStagnantIterations = 3;
const maxFailedIterations = 2;
const checkCommand = "npm run lint";
let iteration = 0;
let stagnantCount = 0;
let failedCount = 0;
let lastCheckOutput = "";
while (iteration < maxIterations) {
iteration++;
// Periodic check
const [cmd, ...args] = checkCommand.split(" ");
const checkResult = spawnSync(cmd, args, {
cwd: process.cwd(),
encoding: "utf-8",
});
if (checkResult.status === 0) {
factory.observe.log("info", "Check passed!", { iterations: iteration });
break;
}
// Track stagnation
const currentOutput = checkResult.stdout + checkResult.stderr;
if (currentOutput === lastCheckOutput) {
stagnantCount++;
factory.observe.log("warning", "No change detected", { stagnantCount });
} else {
stagnantCount = 0;
}
lastCheckOutput = currentOutput;
if (stagnantCount >= maxStagnantIterations) {
factory.observe.log("error", "Stagnant iterations exceeded", { stagnantCount });
break;
}
factory.observe.log("info", `Iteration ${iteration}`, {
stagnantCount,
failedCount,
max: maxIterations,
});
const result = await factory.spawn({
agent: "worker",
systemPrompt: "You are fixing issues iteratively",
prompt: "Continue fixing issues",
model: "anthropic/claude-sonnet-4-6",
step: iteration,
});
if (result.exitCode !== 0) {
failedCount++;
factory.observe.log("error", "Agent failed", { iteration, failedCount });
if (failedCount >= maxFailedIterations) {
factory.observe.log("error", "Failed iterations exceeded", { failedCount });
break;
}
} else {
failedCount = 0;
}
}
```
## Best Practices
### 1. **Set max iterations**
Always have an upper bound to prevent infinite loops:
```typescript
const maxIterations = 20; // Sensible default
```
### 2. **Detect stagnation**
Track if the agent is making progress:
```typescript
if (currentState === lastState) {
stagnantCount++;
if (stagnantCount >= 3) break;
}
```
### 3. **Use bash exit conditions**
Shell out to authoritative checks (tests, lint, build):
```typescript
const result = spawnSync("npm", ["test"], { encoding: "utf-8" });
if (result.status === 0) break;
```
### 4. **Provide context to agent**
Include iteration number, progress, previous attempts:
```typescript
prompt: `Iteration ${iteration}/${maxIterations}
Fixed so far: ${fixed.join(", ")}
Current errors: ${errorCount}
...`
```
### 5. **Log everything**
Observability is critical for debugging loops:
```typescript
factory.observe.log("info", "Loop state", {
iteration,
errorCount,
stagnantCount,
lastChange
});
```
### 6. **Limit context size**
Truncate large outputs to avoid prompt bloat:
```typescript
const recentOutput = fullOutput.slice(-5000); // Last 5KB
```
### 7. **Allow early exit**
If the goal is achieved, return immediately:
```typescript
if (testsPassing) break;
```
## When to Use Ralph Loop
Good for:
- Fixing lint/type errors iteratively
- Making tests pass one by one
- Implementing PRD tasks sequentially
- Refactoring with incremental validation
- Code generation with iterative refinement
Not ideal for:
- Tasks requiring deep context across iterations
- Complex multi-step reasoning within a single problem
- When the agent needs to remember detailed discussions
- Parallel work (use `Promise.all` with `factory.spawn` instead)
## Advanced: Nested Loops
You can nest Ralph Loops for hierarchical work:
```typescript
const modules = ["src/auth", "src/api", "src/db"];
for (const module of modules) {
factory.observe.log("info", `Processing module: ${module}`);
let iteration = 0;
while (iteration < 10) {
iteration++;
const result = await factory.spawn({
agent: "module-fixer",
systemPrompt: `Fix issues in ${module}`,
prompt: "Run checks and fix issues",
model: "mistral/devstral-2512",
step: iteration,
});
const check = spawnSync("npm", ["run", "lint", module], {
cwd: process.cwd(),
encoding: "utf-8",
});
if (check.status === 0) break;
}
}
```
## Summary
The Ralph Loop is a simple but powerful pattern:
- **While loop** around `await factory.spawn()`
- **Filesystem persistence** between iterations
- **Bash exit conditions** for authoritative checks
- **Progress tracking** to detect stagnation
- **Max iterations** for safety
It works because the agent sees fresh context each iteration, making progress incrementally while the filesystem accumulates changes. Perfect for iterative tasks where "run it again" is a valid strategy.
Attribution
Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.