#!/usr/bin/env python3 """ RalphLoop - Ralph Orchestrator Wrapper for Claude Code This wrapper integrates Ralph Orchestrator with Claude Code skills, providing autonomous "Tackle Until Solved" capabilities. Environment Variables: RALPH_AGENT Agent to use (claude|gemini|kiro|q|auto) RALPH_MAX_ITERATIONS Maximum iterations (default: 100) RALPH_MAX_RUNTIME Maximum runtime in seconds (default: 14400) RALPH_VERBOSE Enable verbose output (default: false) Usage: ralphloop "Design a microservices architecture" ralphloop --agent claude --max-iterations 50 "Implement auth" """ import os import sys import json import argparse import subprocess from pathlib import Path from datetime import datetime, timedelta # Configuration DEFAULT_AGENT = os.getenv("RALPH_AGENT", "claude") DEFAULT_MAX_ITERATIONS = int(os.getenv("RALPH_MAX_ITERATIONS", 100)) DEFAULT_MAX_RUNTIME = int(os.getenv("RALPH_MAX_RUNTIME", 14400)) VERBOSE = os.getenv("RALPH_VERBOSE", "false").lower() == "true" # Ralph directory RALPH_DIR = Path(".ralph") STATE_FILE = RALPH_DIR / "state.json" PROMPT_FILE = RALPH_DIR / "PROMPT.md" CONFIG_FILE = RALPH_DIR / "ralph.yml" ITERATIONS_DIR = RALPH_DIR / "iterations" def setup_ralph_directory(): """Create .ralph directory structure.""" RALPH_DIR.mkdir(exist_ok=True) ITERATIONS_DIR.mkdir(exist_ok=True) def load_state(): """Load current Ralph state.""" if STATE_FILE.exists(): with open(STATE_FILE, "r") as f: return json.load(f) return { "iteration": 0, "status": "not_started", "started_at": None, "completed_at": None, "last_error": None } def save_state(state): """Save Ralph state.""" with open(STATE_FILE, "w") as f: json.dump(state, f, indent=2) def create_prompt(task, agent, max_iterations, max_runtime): """Create PROMPT.md with task and success criteria.""" prompt = f"""# RalphLoop Task ## Task {task} ## Success Criteria - Task fully analyzed and understood - All requirements addressed - Implementation/design complete - Quality standards met - No critical issues remaining ## Configuration - Agent: {agent} - Max Iterations: {max_iterations} - Max Runtime: {max_runtime} seconds ({timedelta(seconds=max_runtime)}) - Started: {datetime.now().isoformat()} ## Instructions Run autonomous iterations until all success criteria are met. Update state.json after each iteration. Save final result to iterations/final.md when complete. """ with open(PROMPT_FILE, "w") as f: f.write(prompt) def create_config(agent, max_iterations, max_runtime): """Create ralph.yml configuration.""" config = f"""# RalphLoop Configuration agent: {agent} max_iterations: {max_iterations} max_runtime: {max_runtime} verbose: {VERBOSE} # Output iterations_dir: iterations state_file: state.json final_output: iterations/final.md # Claude Code Integration skill_path: ~/.claude/skills/ralph brainstorming_skill: ~/.claude/skills/brainstorming """ with open(CONFIG_FILE, "w") as f: f.write(config) def run_ralph_iteration(task, iteration, agent): """Run a single Ralph iteration.""" iteration_file = ITERATIONS_DIR / f"{iteration:03d}.md" # Check if ralph-orchestrator is installed try: result = subprocess.run( ["ralph", "--agent", agent, "--prompt", task], capture_output=True, text=True, timeout=300 ) output = result.stdout or result.stderr with open(iteration_file, "w") as f: f.write(f"""# Iteration {iteration} ## Agent: {agent} ## Time: {datetime.now().isoformat()} ## Output {output} ## Status {'COMPLETE' if result.returncode == 0 else 'IN_PROGRESS'} """) return result.returncode == 0 except FileNotFoundError: # Ralph not installed, use placeholder with open(iteration_file, "w") as f: f.write(f"""# Iteration {iteration} ## Agent: {agent} ## Time: {datetime.now().isoformat()} ## Note Ralph Orchestrator not installed. Install with: pip install ralph-orchestrator ## Task Analysis {task} ## Next Steps 1. Install Ralph Orchestrator 2. Re-run ralphloop command """) return False except subprocess.TimeoutExpired: with open(iteration_file, "w") as f: f.write(f"""# Iteration {iteration} ## Agent: {agent} ## Time: {datetime.now().isoformat()} ## Status: TIMEOUT Iteration exceeded 300 second timeout. """) return False def run_ralph(task, agent, max_iterations, max_runtime): """Run Ralph autonomous loop.""" setup_ralph_directory() state = load_state() if state["status"] == "completed": print(f"Task already completed at {state['completed_at']}") print(f"See {ITERATIONS_DIR / 'final.md'} for results") return # Initialize state["status"] = "running" state["started_at"] = datetime.now().isoformat() save_state(state) create_prompt(task, agent, max_iterations, max_runtime) create_config(agent, max_iterations, max_runtime) start_time = datetime.now() completed = False for iteration in range(1, max_iterations + 1): # Check runtime elapsed = (datetime.now() - start_time).total_seconds() if elapsed > max_runtime: print(f"Max runtime ({max_runtime}s) exceeded") break if VERBOSE: print(f"[Ralph] Iteration {iteration}/{max_iterations}") state["iteration"] = iteration save_state(state) # Run iteration if run_ralph_iteration(task, iteration, agent): completed = True break # Finalize state["status"] = "completed" if completed else "max_iterations_reached" state["completed_at"] = datetime.now().isoformat() save_state(state) # Create final output final_file = ITERATIONS_DIR / "final.md" with open(final_file, "w") as f: f.write(f"""# RalphLoop Final Result ## Task {task} ## Agent: {agent} ## Iterations: {state['iteration']} ## Status: {state['status']} ## Started: {state['started_at']} ## Completed: {state['completed_at']} ## Result {'Task completed successfully!' if completed else 'Max iterations reached. Manual review needed.'} ## Output Files - iterations/001.md through iterations/{iteration:03d}.md - state.json - Progress tracking - ralph.yml - Configuration ## Next Steps {'Implement the solution from the final iteration.' if completed else 'Review iterations and continue manually or increase max_iterations.'} """) print(f"\n[Ralph] {state['status'].upper()}") print(f"[Ralph] Iterations: {state['iteration']}/{max_iterations}") print(f"[Ralph] Output: {final_file}") def main(): parser = argparse.ArgumentParser( description="RalphLoop - Ralph Orchestrator Wrapper", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=__doc__ ) parser.add_argument("task", help="Task description") parser.add_argument("--agent", default=DEFAULT_AGENT, help="Agent to use") parser.add_argument("--max-iterations", type=int, default=DEFAULT_MAX_ITERATIONS) parser.add_argument("--max-runtime", type=int, default=DEFAULT_MAX_RUNTIME) parser.add_argument("--verbose", action="store_true") args = parser.parse_args() if args.verbose: global VERBOSE VERBOSE = True run_ralph( task=args.task, agent=args.agent, max_iterations=args.max_iterations, max_runtime=args.max_runtime ) if __name__ == "__main__": main()