Files
uroma 932529d37f Replace placeholder files with originals from system
Found and copied original files from ~/.claude installation:

- hooks/ - Original Qwen and Ralph hook scripts with full functionality
- commands/ - Original command definitions (brainstorm, write-plan, execute-plan)
- bin/ralphloop - Original 223-line Python wrapper (6,290 bytes)
- scripts/sync-agents.sh - Original sync script with GitHub/Gitea backup
- templates/ - Original config templates from working installation
- plugins/ - Original comprehensive plugin README

Files sourced from:
- ~/.claude/skills/skills/hooks/
- ~/.claude/skills/skills/commands/
- ~/.claude/skills/skills/templates/
- /home/uroma/obsidian-web-interface/bin/ralphloop
- ~/.claude/agents/sync-agents.sh

These are the production files from the working Claude Code
installation, replacing the placeholder files I created earlier.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-23 18:23:28 +00:00

224 lines
6.1 KiB
Python
Executable File

#!/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<!-- Ralph will continue iterating until task is complete -->\n\n## Success Criteria\n\nThe task is complete when:\n- All requirements are implemented\n- Tests pass\n- Code is documented\n\n<!-- When complete, add <!-- COMPLETE --> 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)