#!/usr/bin/env python3 """ RalphLoop - "Tackle Until Solved" Autonomous Agent Loop Integration of Ralph Orchestrator with Claude Code CLI. This script runs an autonomous agent loop that continues until the task is complete. Usage: ./ralphloop "Your task description here" ./ralphloop -i task.md ./ralphloop --agent claude --max-iterations 50 Environment Variables: ANTHROPIC_API_KEY Required for Claude agent RALPH_AGENT Override agent selection (claude, gemini, etc.) RALPH_MAX_ITERATIONS Override max iterations (default: 100) """ import os import sys import subprocess import argparse import json from pathlib import Path from datetime import datetime # Configuration DEFAULT_AGENT = "claude" DEFAULT_MAX_ITERATIONS = 100 DEFAULT_MAX_RUNTIME = 14400 # 4 hours # Path to Ralph in venv SCRIPT_DIR = Path(__file__).parent.parent VENV_BIN = SCRIPT_DIR / ".venv" / "bin" RALPH_CMD = str(VENV_BIN / "ralph") def check_dependencies(): """Check if Ralph Orchestrator is available.""" try: result = subprocess.run( [RALPH_CMD, "run", "-h"], capture_output=True, text=True, timeout=5 ) if result.returncode == 0 or "usage:" in result.stdout: return True except (FileNotFoundError, subprocess.TimeoutExpired): pass # Fallback: check if pip package is installed try: import ralph_orchestrator return True except ImportError: return False def create_ralph_project(task_description=None, task_file=None): """Create a Ralph project in the current directory.""" ralph_dir = Path(".ralph") ralph_dir.mkdir(exist_ok=True) # Create prompt file prompt_file = Path("PROMPT.md") if task_file: # Read from file content = Path(task_file).read_text() prompt_file.write_text(content) elif task_description: # Use inline task prompt_file.write_text(f"# Task: {task_description}\n\n\n\n## Success Criteria\n\nThe task is complete when:\n- All requirements are implemented\n- Tests pass\n- Code is documented\n\n marker to this file -->") else: print("Error: Either provide task description or task file") sys.exit(1) # Create config file config_file = Path("ralph.yml") config = { "agent": os.getenv("RALPH_AGENT", DEFAULT_AGENT), "prompt_file": "PROMPT.md", "max_iterations": int(os.getenv("RALPH_MAX_ITERATIONS", DEFAULT_MAX_ITERATIONS)), "max_runtime": int(os.getenv("RALPH_MAX_RUNTIME", DEFAULT_MAX_RUNTIME)), "verbose": True, "adapters": { "claude": { "enabled": True, "timeout": 300 } } } import yaml with open(config_file, "w") as f: yaml.dump(config, f, default_flow_style=False) print(f"āœ… Ralph project initialized") print(f" Prompt: {prompt_file}") print(f" Config: {config_file}") def run_ralph_loop(task=None, task_file=None, agent=None, max_iterations=None, max_runtime=None): """Run the Ralph autonomous loop.""" print("šŸ”„ RalphLoop: 'Tackle Until Solved' Autonomous Agent Loop") print("=" * 60) # Initialize project create_ralph_project(task, task_file) # Build command cmd = [RALPH_CMD, "run"] if agent: cmd.extend(["-a", agent]) if max_iterations: cmd.extend(["-i", str(max_iterations)]) if max_runtime: cmd.extend(["-t", str(max_runtime)]) cmd.append("-v") # Verbose output print(f"Command: {' '.join(cmd)}") print("=" * 60) # Run Ralph try: process = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1 ) # Stream output for line in process.stdout: print(line, end='', flush=True) process.wait() return process.returncode except KeyboardInterrupt: print("\n\nāš ļø Interrupted by user") return 130 except Exception as e: print(f"āŒ Error: {e}") return 1 def main(): parser = argparse.ArgumentParser( description="RalphLoop - Autonomous agent loop for Claude Code CLI", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=__doc__ ) parser.add_argument( "task", nargs="?", help="Task description (inline)" ) parser.add_argument( "-i", "--input", dest="task_file", help="Read task from file" ) parser.add_argument( "-a", "--agent", choices=["claude", "kiro", "q", "gemini", "acp", "auto"], default=os.getenv("RALPH_AGENT", DEFAULT_AGENT), help="AI agent to use" ) parser.add_argument( "--max-iterations", type=int, default=int(os.getenv("RALPH_MAX_ITERATIONS", DEFAULT_MAX_ITERATIONS)), help="Maximum iterations" ) parser.add_argument( "--max-runtime", type=int, default=int(os.getenv("RALPH_MAX_RUNTIME", DEFAULT_MAX_RUNTIME)), help="Maximum runtime in seconds" ) parser.add_argument( "--init-only", action="store_true", help="Only initialize project, don't run" ) args = parser.parse_args() # Check dependencies if not check_dependencies(): print("āš ļø Ralph Orchestrator not found at:", RALPH_CMD) print("\nTo install:") print(f" .venv/bin/pip install ralph-orchestrator") print("\nFor now, creating project files only...") args.init_only = True # Initialize only mode if args.init_only: create_ralph_project(args.task, args.task_file) print("\nšŸ’” To run the loop later:") print(f" {RALPH_CMD} run") return 0 # Run the loop return run_ralph_loop( task=args.task, task_file=args.task_file, agent=args.agent, max_iterations=args.max_iterations, max_runtime=args.max_runtime ) if __name__ == "__main__": sys.exit(main() or 0)