Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Multi-step autonomous plan execution with bounded iteration (max 10), state persistence, dependency checking, error retry with exponential backoff, and human escalation on failure. The "Ralph Wiggum loop" enables AI agents to execute complex multi-step plans autonomously while maintaining safety bounds and progress tracking. Includes MCP action invocation, step dependency validation, graceful error recovery, and comprehensive audit logging. Use when: (1) implementing autonomous multi-step task e
.claude/skills/majiayu000-ralph-wiggum-loop/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-05 | ✗→✓ | ▲ Improved | 133% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 308% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 143% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 190% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 205% | 0% |
> "I'm helping! I'm helping! I'm helping!" — Ralph Wiggum
The Ralph Wiggum loop is a bounded autonomous execution pattern that allows AI agents to execute multi-step plans with:
Named after Ralph Wiggum because the agent enthusiastically attempts tasks repeatedly but has a hard limit to prevent runaway behavior.
Plan.md (approved) → Plan Executor (plan_executor.py)
↓
Initialize ExecutionState
(iterations_remaining=10)
↓
┌─────────────┴─────────────┐
↓ ↓
Check Dependencies Execute Step
↓ ↓
All met? Success? ───→ Mark [x], decrement iteration
│ │
No No (retry 3x)
↓ ↓
Mark blocked Mark [!], escalate
↓ ↓
Escalate to vault/Needs_Action/
vault/Needs_Action/
↓
iterations_remaining == 0? ───→ Escalate (max iterations)
│
No
↓
Next Step ──→ Loop backmarkdown<!-- vault/Plans/PLAN_client_onboarding_001.md --> --- plan_id: plan_client_onboarding_001 objective: "Onboard new client - draft intro email, create calendar invite, post LinkedIn announcement" total_steps: 3 completed_steps: 0 status: awaiting_approval approval_required: true iterations_used: 0 max_iterations: 10 --- # Plan: Client Onboarding - Acme Corp **Objective:** Complete new client onboarding for Acme Corp ## Steps - [ ] **Step 1:** Draft intro email to client (action_type: mcp_email) - Dependencies: none - MCP Server: email-mcp - Tool: send_email - Params: {to: "client@acme.com", subject: "Welcome!", body: "..."} - [ ] **Step 2:** Create calendar invite for kickoff meeting (action_type: create_file) - Dependencies: step_1 - File: vault/Pending_Approval/Calendar/kickoff_invite.md - [ ] **Step 3:** Post LinkedIn announcement (action_type: mcp_linkedin) - Dependencies: step_1, step_2 - MCP Server: linkedin-mcp - Tool: create_post - Params: {text: "Excited to welcome Acme Corp..."}
python# agent_skills/plan_executor.py from dataclasses import dataclass from typing import List, Optional import time @dataclass class ExecutionState: plan_id: str current_step: int iterations_remaining: int last_action: str last_action_timestamp: str loop_start_time: str status: str # executing | completed | blocked | escalated class RalphWiggumLoop: """Autonomous multi-step plan executor with bounded iterations""" def __init__(self, plan_path: str, max_iterations: int = 10): self.plan_path = plan_path self.plan = self.load_plan(plan_path) self.max_iterations = max_iterations # Initialize or load execution state state_path = f"vault/In_Progress/{self.plan['plan_id']}/state.md" if os.path.exists(state_path): self.state = self.load_state(state_path) else: self.state = ExecutionState( plan_id=self.plan['plan_id'], current_step=1, iterations_remaining=max_iterations, last_action="initialized", last_action_timestamp=datetime.utcnow().isoformat(), loop_start_time=datetime.utcnow().isoformat(), status="executing" ) def execute(self): """ Main execution loop Returns: bool: True if plan completed, False if blocked/escalated """ print(f"🔄 Starting Ralph Wiggum loop for {self.plan['objective']}") print(f" Max iterations: {self.max_iterations}") while self.state.iterations_remaining > 0: print(f"\n--- Iteration {self.max_iterations - self.state.iterations_remaining + 1}/{self.max_iterations} ---") # Get current step step = self.plan['steps'][self.state.current_step - 1] # Check dependencies if not self.check_dependencies(step): print(f"⏸️ Step {self.state.current_step} blocked - dependencies not met") self.handle_blocked_step(step) return False # Execute step try: self.execute_step(step) # Mark step complete self.mark_step_complete(step) # Update state self.state.current_step += 1 self.state.iterations_remaining -= 1 self.state.last_action = f"completed step {step['step_num']}" self.save_state() # Check if all steps complete if self.state.current_step > self.plan['total_steps']: print("✅ All steps complete!") self.handle_plan_complete() return True except Exception as e: print(f"❌ Step {self.state.current_step} failed: {e}") # Retry with exponential backoff if not self.retry_step(step): # All retries failed - escalate self.handle_step_failure(step, e) return False # Max iterations reached - escalate print(f"⚠️ Max iterations ({self.max_iterations}) reached - escalating") self.handle_max_iterations_escalation() return False def check_dependencies(self, step: dict) -> bool: """Check if step dependencies are met""" dependencies = step.get('dependencies', []) if not dependencies or dependencies == ['none']: return True # Check each dependency step is completed for dep in dependencies: dep_num = int(dep.replace('step_', '')) dep_step = self.plan['steps'][dep_num - 1] if not dep_step.get('completed', False): return False return True def execute_step(self, step: dict): """Execute a single plan step""" action_type = step['action_type'] if action_type.startswith('mcp_'): # Invoke MCP server self.execute_mcp_action(step) elif action_type == 'create_file': # Create file in vault self.execute_file_creation(step) elif action_type == 'notify_human': # Create notification for human self.execute_human_notification(step) else: raise ValueError(f"Unknown action_type: {action_type}") def execute_mcp_action(self, step: dict): """Invoke MCP server for step action""" from agent_skills.mcp_client import call_mcp_tool # Extract MCP details server = step['mcp_server'] tool = step['tool'] params = step['params'] # Invoke MCP result = call_mcp_tool(server, tool, params) # Log MCP action log_mcp_action( mcp_server=server, action=tool, outcome="success", plan_id=self.plan['plan_id'], step_num=step['step_num'] ) # Store result in step step['mcp_result'] = result def execute_file_creation(self, step: dict): """Create vault file as step action""" file_path = step['file_path'] content = step.get('content', '') os.makedirs(os.path.dirname(file_path), exist_ok=True) with open(file_path, 'w') as f: f.write(content) print(f"📄 Created file: {file_path}") def execute_human_notification(self, step: dict): """Create notification for human action""" notification_path = f"vault/Needs_Action/plan_notification_{self.plan['plan_id']}_step_{step['step_num']}.md" content = f"""--- plan_id: {self.plan['plan_id']} step_num: {step['step_num']} action_required: true --- # Human Action Required **Plan:** {self.plan['objective']} **Step:** {step['description']} {step.get('notification_message', 'Manual action required - see plan for details')} """ with open(notification_path, 'w') as f: f.write(content) print(f"📢 Created notification: {notification_path}") def mark_step_complete(self, step: dict): """Mark step as [x] in Plan.md""" step['completed'] = True # Update Plan.md file plan_content = open(self.plan_path).read() # Replace checkbox: - [ ] → - [x] step_pattern = f"- \\[ \\] \\*\\*Step {step['step_num']}:\\*\\*" step_replacement = f"- [x] **Step {step['step_num']}:**" updated_content = re.sub(step_pattern, step_replacement, plan_content) with open(self.plan_path, 'w') as f: f.write(updated_content) # Update plan completion count self.plan['completed_steps'] += 1 def retry_step(self, step: dict, max_retries: int = 3) -> bool: """Retry failed step with exponential backoff""" for attempt in range(max_retries): try: # Wait with exponential backoff: 5s, 10s, 20s if attempt > 0: wait_time = 5 * (2 ** attempt) print(f"⏳ Retry {attempt + 1}/{max_retries} in {wait_time}s...") time.sleep(wait_time) # Retry execution self.execute_step(step) print(f"✅ Step succeeded on retry {attempt + 1}") return True except Exception as e: print(f"❌ Retry {attempt + 1} failed: {e}") return False def handle_step_failure(self, step: dict, error: Exception): """Handle permanent step failure - escalate to human""" # Mark step as [!] in Plan.md step['status'] = 'failed' plan_content = open(self.plan_path).read() step_pattern = f"- \\[ \\] \\*\\*Step {step['step_num']}:\\*\\*" step_replacement = f"- [!] **Step {step['step_num']}:** (FAILED)" updated_content = re.sub(step_pattern, step_replacement, plan_content) with open(self.plan_path, 'w') as f: f.write(updated_content) # Create escalation file escalation_path = f"vault/Needs_Action/plan_blocked_{self.plan['plan_id']}.md" content = f"""--- plan_id: {self.plan['plan_id']} step_num: {step['step_num']} severity: high action_required: true --- # Plan Blocked - Step Failed **Plan:** {self.plan['objective']} **Blocked Step:** {step['description']} **Error:** {str(error)} **Recovery Instructions:** 1. Review step details in [[{os.path.basename(self.plan_path)}]] 2. Fix the underlying issue 3. Restart plan execution **Full Error Details:**
{traceback.format_exc()}
"""
with open(escalation_path, 'w') as f:
f.write(content)
# Update state
self.state.status = "blocked"
self.save_state()
print(f"⚠️ Plan blocked - escalated to: {escalation_path}")
def handle_max_iterations_escalation(self):
"""Handle max iterations reached - escalate"""
escalation_path = f"vault/Needs_Action/plan_escalated_{self.plan['plan_id']}.md"
content = f"""---
plan_id: {self.plan['plan_id']}
severity: medium
action_required: true
max_iterations_reached: true
---
# Plan Escalated - Max Iterations Reached
**Plan:** {self.plan['objective']}
**Iterations Used:** {self.max_iterations}
The plan has reached the maximum iteration limit ({self.max_iterations}) without completing all steps.
**Current Progress:**
- Completed: {self.plan['completed_steps']}/{self.plan['total_steps']} steps
- Current Step: {self.state.current_step}
**Next Actions:**
1. Review plan progress in [[{os.path.basename(self.plan_path)}]]
2. Check for circular dependencies or stuck steps
3. Manually complete remaining steps OR restart with higher iteration limit
**Full Iteration History:**
See vault/Logs/Plan_Execution/{self.plan['plan_id']}.md
"""
with open(escalation_path, 'w') as f:
f.write(content)
# Update state
self.state.status = "escalated"
self.save_state()
print(f"⚠️ Plan escalated - max iterations: {escalation_path}")
def handle_plan_complete(self):
"""Handle successful plan completion"""
# Update Plan.md status
self.update_plan_status("completed")
# Move to Done
done_path = f"vault/Done/{os.path.basename(self.plan_path)}"
shutil.move(self.plan_path, done_path)
# Clean up execution state
state_dir = f"vault/In_Progress/{self.plan['plan_id']}/"
if os.path.exists(state_dir):
shutil.rmtree(state_dir)
# Update dashboard
from agent_skills.dashboard_updater import update_dashboard
update_dashboard()
print(f"✅ Plan completed: {self.plan['objective']}")
print(f" Iterations used: {self.max_iterations - self.state.iterations_remaining}")
def save_state(self):
"""Persist execution state to vault/In_Progress/"""
state_dir = f"vault/In_Progress/{self.plan['plan_id']}/"
os.makedirs(state_dir, exist_ok=True)
state_path = os.path.join(state_dir, 'state.md')
content = f"""---
plan_id: {self.state.plan_id}
current_step: {self.state.current_step}
iterations_remaining: {self.state.iterations_remaining}
last_action: {self.state.last_action}
last_action_timestamp: {self.state.last_action_timestamp}
loop_start_time: {self.state.loop_start_time}
status: {self.state.status}
---
# Execution State: {self.plan['objective']}
**Current Step:** {self.state.current_step}/{self.plan['total_steps']}
**Iterations Remaining:** {self.state.iterations_remaining}/{self.max_iterations}
**Status:** {self.state.status}
**Last Action:** {self.state.last_action}
**Timestamp:** {self.state.last_action_timestamp}
"""
with open(state_path, 'w') as f:
f.write(content)python# scripts/plan_watcher.py from agent_skills.plan_executor import RalphWiggumLoop from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler class PlanApprovalHandler(FileSystemEventHandler): def on_moved(self, event): """Detect approved plans""" if not event.is_directory and 'Approved/Plans' in event.dest_path: # Parse plan plan_path = event.dest_path # Start Ralph Wiggum loop executor = RalphWiggumLoop(plan_path) executor.execute() def watch_approved_plans(): """Monitor vault/Approved/Plans/ for approved plans""" observer = Observer() observer.schedule( PlanApprovalHandler(), path="vault/Pending_Approval/", recursive=True ) observer.start() print("📋 Plan watcher active") try: while True: time.sleep(1) except KeyboardInterrupt: observer.stop() observer.join()
python# Prevent infinite loops MAX_ITERATIONS = 10 # Hard limit # Escalate when limit reached if iterations_remaining == 0: escalate_to_human()
python# Restart-safe execution state_path = f"vault/In_Progress/{plan_id}/state.md" # On crash, resume from last saved state if os.path.exists(state_path): state = load_state(state_path) resume_from_step(state.current_step)
python# Never execute steps out of order def check_dependencies(step): for dep in step['dependencies']: if not is_step_complete(dep): return False # Block execution return True
python# tests/integration/test_plan_execution.py def test_ralph_wiggum_loop(): """Test multi-step plan execution""" # Create test plan plan = create_test_plan(steps=[ {"step_num": 1, "action_type": "mcp_email", "dependencies": []}, {"step_num": 2, "action_type": "create_file", "dependencies": ["step_1"]}, {"step_num": 3, "action_type": "mcp_linkedin", "dependencies": ["step_1", "step_2"]} ]) # Mock MCP calls with patch('agent_skills.mcp_client.call_mcp_tool') as mock_mcp: mock_mcp.return_value = {"success": True} # Execute plan executor = RalphWiggumLoop(plan_path='test_plan.md') result = executor.execute() # Verify success assert result == True assert executor.state.current_step > executor.plan['total_steps'] assert mock_mcp.call_count == 2 # email + linkedin (file doesn't use MCP) def test_max_iterations_escalation(): """Test escalation when max iterations reached""" # Create circular dependency plan (infinite loop) plan = create_circular_plan() executor = RalphWiggumLoop(plan_path='circular_plan.md', max_iterations=5) result = executor.execute() # Verify escalation assert result == False assert executor.state.status == "escalated" assert os.path.exists(f"vault/Needs_Action/plan_escalated_{executor.plan['plan_id']}.md")
bash# .env MAX_PLAN_ITERATIONS=10 # Default: 10 PLAN_RETRY_ATTEMPTS=3 # Default: 3
| Issue | Solution | |-------|----------| | Plan stuck in loop | Check vault/In_Progress/{plan_id}/state.md | | Max iterations reached | Review plan for circular dependencies | | Step always failing | Check vault/Needs_Action/plan_blocked_.md | | State corrupted | Delete vault/In_Progress/{plan_id}/ and restart |
agent_skills/plan_executor.py - Ralph Wiggum loop implementationscripts/plan_watcher.py - Monitor approved plansvault/Plans/ - Plan definitionsvault/In_Progress/ - Active execution statevault/Needs_Action/plan_*.md - EscalationsProduction Ready: Bounded iterations, state persistence, dependency validation, retry logic, comprehensive error handling, human escalation patterns.
Remember: Like Ralph Wiggum, the agent is enthusiastic and helpful, but has firm limits to prevent chaos. Max 10 iterations ensures autonomous execution stays safe and bounded.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-05 | fail→pass | 22,702 | 17,745 | -22% | 1 | 1 | 0% | 3,234 | 7,530 | +133% | 0 | 0 | — |
case-01 | fail→fail | 54,059 | 12,673 | -77% | 1 | 1 | 0% | 8,480 | 5,481 | -35% | 0 | 0 | — |
case-02 | fail→fail | 10,833 | 8,477 | -22% | 1 | 1 | 0% | 751 | 5,568 | +641% | 0 | 0 | — |
case-03 | fail→pass | 16,022 | 19,148 | +20% | 1 | 1 | 0% | 1,962 | 8,012 | +308% | 0 | 0 | — |
case-04 | fail→fail | 25,442 | 25,267 | -1% | 1 | 1 | 0% | 2,950 | 8,527 | +189% | 0 | 0 | — |
case-06 | fail→pass | 16,697 | 8,169 | -51% | 1 | 1 | 0% | 2,323 | 5,654 | +143% | 0 | 0 | — |
case-07 | fail→pass | 12,803 | 12,644 | -1% | 1 | 1 | 0% | 2,178 | 6,318 | +190% | 0 | 0 | — |
case-08 | pass→pass | 13,976 | 12,555 | -10% | 1 | 1 | 0% | 2,233 | 6,504 | +191% | 0 | 0 | — |
case-09 | fail→pass | 17,076 | 8,445 | -51% | 1 | 1 | 0% | 1,874 | 5,713 | +205% | 0 | 0 | — |
case-10 | fail→pass | 9,924 | 5,848 | -41% | 1 | 1 | 0% | 1,808 | 6,301 | +249% | 0 | 0 | — |
case-11 | fail→pass | 10,709 | 3,714 | -65% | 1 | 1 | 0% | 872 | 5,782 | +563% | 0 | 0 | — |
case-12 | fail→pass | 15,220 | 5,707 | -63% | 1 | 1 | 0% | 1,763 | 6,224 | +253% | 0 | 0 | — |
case-13 | fail→pass | 10,966 | 8,374 | -24% | 1 | 1 | 0% | 1,812 | 5,721 | +216% | 0 | 0 | — |
case-14 | fail→pass | 31,149 | 5,925 | -81% | 1 | 1 | 0% | 897 | 6,199 | +591% | 0 | 0 | — |
case-15 | pass→pass | 11,367 | 12,625 | +11% | 1 | 1 | 0% | 1,081 | 6,646 | +515% | 0 | 0 | — |
case-16 | fail→pass | 12,231 | 7,507 | -39% | 1 | 1 | 0% | 1,965 | 6,394 | +225% | 0 | 0 | — |
case-17 | fail→fail | 18,992 | 15,998 | -16% | 1 | 1 | 0% | 2,425 | 7,214 | +197% | 0 | 0 | — |
case-18 | pass→pass | 7,035 | 11,557 | +64% | 1 | 1 | 0% | 1,264 | 6,285 | +397% | 0 | 0 | — |
case-19 | fail→pass | 15,857 | 19,545 | +23% | 1 | 1 | 0% | 2,319 | 7,602 | +228% | 0 | 0 | — |
case-20 | fail→pass | 18,281 | 10,479 | -43% | 1 | 1 | 0% | 1,960 | 6,108 | +212% | 0 | 0 | — |
case-21 | pass→pass | 12,034 | 17,324 | +44% | 1 | 1 | 0% | 2,126 | 7,341 | +245% | 0 | 0 | — |
case-22 | pass→pass | 17,581 | 9,986 | -43% | 1 | 1 | 0% | 2,128 | 6,861 | +222% | 0 | 0 | — |
case-23 | pass→pass | 30,362 | 15,946 | -47% | 1 | 1 | 0% | 5,706 | 7,468 | +31% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted, and 21 counted toward the lift figure. The other 2 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +57 percentage points is the difference between those two pass rates over the 21 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.