From 99cb591bf76daa6589ad2ca4b88b3168cf4351c1 Mon Sep 17 00:00:00 2001 From: uroma Date: Fri, 16 Jan 2026 10:03:31 +0000 Subject: [PATCH] Enhance Ralph hook with background task spawning BREAKING CHANGE: Ralph now runs as background process (non-blocking) Changes: - Enhanced ralph-auto-trigger.sh to spawn Ralph in background - Hook immediately returns to Claude Code (NON-BLOCKING) - Ralph runs via nohup (survives terminal close) - Added PID tracking: ~/.claude/ralph.pid - Added lock file: ~/.claude/ralph.lock - Added log file: ~/.claude/ralph-output.log - Added trigger log: ~/.claude/ralph-trigger.log - Added RALPH_MAX_ITERATIONS environment variable (default: 50) Features: - Background execution: Claude Code continues immediately - Process tracking: PID saved for monitoring/stopping - Lock mechanism: Prevents duplicate Ralph instances - Real-time monitoring: tail -f ~/.claude/ralph-output.log - Graceful cleanup: Auto-cleanup of dead process locks Updated files: - MASTER-PROMPT.md: Enhanced hook script + usage instructions - interactive-install-claude.sh: Enhanced install_ralph() function - ~/.claude/hooks/ralph-auto-trigger.sh: Updated local installation Usage: - Ralph auto-starts in background when agents are requested - Monitor: tail -f ~/.claude/ralph-output.log - Stop: kill $(cat ~/.claude/ralph.pid) - Configure: export RALPH_AUTO_MODE=agents|always|off - Configure: export RALPH_MAX_ITERATIONS=100 Co-Authored-By: Claude Sonnet 4.5 --- MASTER-PROMPT.md | 279 ++++++++++++++++++++++++++-------- interactive-install-claude.sh | 178 +++++++++++++++++----- 2 files changed, 353 insertions(+), 104 deletions(-) diff --git a/MASTER-PROMPT.md b/MASTER-PROMPT.md index c30ae64..3571f9f 100644 --- a/MASTER-PROMPT.md +++ b/MASTER-PROMPT.md @@ -504,80 +504,198 @@ ralph --version mkdir -p ~/.claude/hooks cat > ~/.claude/hooks/ralph-auto-trigger.sh << 'EOF' #!/bin/bash - -# Ralph Auto-Trigger Hook -# Automatically starts Ralph loop when user requests something -# Set RALPH_AUTO_MODE environment variable to control behavior: -# "always" - Always start Ralph for every request -# "agents" - Only start Ralph for agent requests (default) +# Ralph Auto-Trigger Hook - Enhanced with Background Task Spawning +# Automatically starts Ralph CLI in background when needed +# +# Modes (via RALPH_AUTO_MODE environment variable): +# "always" - Start Ralph for every request +# "agents" - Only for agent requests (default) # "off" - Disable auto-trigger +# +# Background Execution: +# - Ralph runs as background process (non-blocking) +# - Claude Code continues immediately +# - Ralph output logged to: ~/.claude/ralph-output.log +# - Ralph PID tracked in: ~/.claude/ralph.pid set -euo pipefail +# Configuration +CLAUDE_DIR="$HOME/.claude" +RALPH_STATE_FILE="$CLAUDE_DIR/ralph-loop.local.md" +RALPH_PID_FILE="$CLAUDE_DIR/ralph.pid" +RALPH_LOG_FILE="$CLAUDE_DIR/ralph-output.log" +RALPH_LOCK_FILE="$CLAUDE_DIR/ralph.lock" + # Read hook input from stdin HOOK_INPUT=$(cat) +USER_PROMPT=$(echo "$HOOK_INPUT" | jq -r '.prompt // empty' 2>/dev/null || echo "") -# Get the user prompt from hook input -USER_PROMPT=$(echo "$HOOK_INPUT" | jq -r '.prompt // empty') +# Fallback: if no JSON input, use first argument +if [[ -z "$USER_PROMPT" && $# -gt 0 ]]; then + USER_PROMPT="$1" +fi -# Check if auto-trigger is enabled (default: agents) +# Get Ralph mode (default: agents) RALPH_AUTO_MODE="${RALPH_AUTO_MODE:-agents}" +RALPH_MAX_ITERATIONS="${RALPH_MAX_ITERATIONS:-50}" # Exit if auto-trigger is disabled if [[ "$RALPH_AUTO_MODE" == "off" ]]; then - exit 0 + exit 0 fi -# Exit if Ralph is already active -RALPH_STATE_FILE=".claude/ralph-loop.local.md" -if [[ -f "$RALPH_STATE_FILE" ]]; then - exit 0 +# Check if Ralph is already running (via lock file) +if [[ -f "$RALPH_LOCK_FILE" ]]; then + # Check if process is still alive + LOCK_PID=$(cat "$RALPH_LOCK_FILE" 2>/dev/null || echo "") + if [[ -n "$LOCK_PID" ]] && kill -0 "$LOCK_PID" 2>/dev/null; then + # Ralph is already running, don't start another instance + exit 0 + else + # Lock file exists but process is dead, clean up + rm -f "$RALPH_LOCK_FILE" "$RALPH_PID_FILE" + fi fi -# Check if this is an agent request (list of known agents) -KNOWN_AGENTS="frontend-developer|backend-architect|mobile-app-builder|ai-engineer|devops-automator|rapid-prototyper|test-writer-fixer|studio-coach|studio-producer|sprint-prioritizer|experiment-tracker|whimsy-injector|joker|trend-researcher|tiktok-strategist|feedback-synthesizer|ui-ux-pro-max" +# Agent detection list (lowercase for matching) +AGENTS=( + "ai-engineer" "backend-architect" "devops-automator" "frontend-developer" + "mobile-app-builder" "rapid-prototyper" "test-writer-fixer" + "tiktok-strategist" "growth-hacker" "content-creator" "instagram-curator" + "reddit-builder" "twitter-engager" "app-store-optimizer" + "brand-guardian" "ui-designer" "ux-researcher" "visual-storyteller" + "whimsy-injector" "ui-ux-pro-max" + "feedback-synthesizer" "sprint-prioritizer" "trend-researcher" + "experiment-tracker" "project-shipper" "studio-producer" "studio-coach" + "analytics-reporter" "finance-tracker" "infrastructure-maintainer" + "legal-compliance-checker" "support-responder" + "api-tester" "performance-benchmarker" "test-results-analyzer" + "tool-evaluator" "workflow-optimizer" + "joker" "agent-updater" + "explore" "plan" "general-purpose" +) + +# Detect agent request (case-insensitive) +agent_detected=false +detected_agent="" + +for agent in "${AGENTS[@]}"; do + if echo "$USER_PROMPT" | grep -iq "$agent"; then + agent_detected=true + detected_agent="$agent" + break + fi +done # Determine if we should start Ralph -START_RALPH=false +should_trigger=false case "$RALPH_AUTO_MODE" in - "always") - # Start Ralph for everything - START_RALPH=true - ;; - "agents") - # Check if prompt mentions an agent or uses agent-like language - if echo "$USER_PROMPT" | grep -qiE "use (the |a )?($KNOWN_AGENTS) agent|launch ($KNOWN_AGENTS)|agent:|delegate to|run (the |a )?($KNOWN_AGENTS)" || \ - echo "$USER_PROMPT" | grep -qiE "build|create|implement|develop|fix|add|refactor|optimize"; then - START_RALPH=true - fi - ;; + "always") + # Trigger on all prompts + should_trigger=true + ;; + "agents") + # Only trigger on agent requests OR development keywords + if [[ "$agent_detected" == true ]]; then + should_trigger=true + elif echo "$USER_PROMPT" | grep -qiE "build|create|implement|develop|fix|add|refactor|optimize|write|generate|delegate|autonomous"; then + should_trigger=true + detected_agent="general-development" + fi + ;; esac -if [[ "$START_RALPH" == "true" ]]; then - # Create Ralph state file with default settings - mkdir -p .claude +if [[ "$should_trigger" == true ]]; then + # Create Ralph state file + mkdir -p "$CLAUDE_DIR" - cat > "$RALPH_STATE_FILE" < "$RALPH_STATE_FILE" << EOF +# Ralph Loop State - Auto-Triggered +# Generated: $(date -u +"%Y-%m-%d %H:%M:%S UTC") +**User Request:** $USER_PROMPT + +**Detected Agent:** $detected_agent +**Mode:** $RALPH_AUTO_MODE +**Max Iterations:** $RALPH_MAX_ITERATIONS +**Timestamp:** $(date -Iseconds) + +## Context + +This state file was automatically generated by the Ralph auto-trigger hook. +Ralph CLI will read this file and autonomously execute the request. + +## Auto-Trigger Details + +- Triggered by: Claude Code UserPromptSubmit hook +- Trigger mode: $RALPH_AUTO_MODE +- Background execution: Yes (non-blocking) +- Log file: $RALPH_LOG_FILE + +## Usage + +Ralph is running autonomously in the background. Monitor progress: + +bash +# View Ralph output in real-time +tail -f ~/.claude/ralph-output.log + +# Check if Ralph is still running +ps aux | grep ralph + +# Stop Ralph manually +kill $(cat ~/.claude/ralph.pid) +rm ~/.claude/ralph.lock + + EOF - # Notify that Ralph was auto-triggered - echo "🔄 Ralph auto-triggered!" >&2 - echo " Mode: $RALPH_AUTO_MODE" >&2 - echo " Ralph will loop this request indefinitely." >&2 - echo " Set RALPH_AUTO_MODE=off to disable, or use /cancel-ralph to stop." >&2 + # Spawn Ralph in background (NON-BLOCKING) + if command -v ralph &> /dev/null; then + # Create log file + touch "$RALPH_LOG_FILE" + + # Start Ralph in background with nohup (survives terminal close) + echo "[$(date -u +"%Y-%m-%d %H:%M:%S UTC")] Starting Ralph in background..." >> "$RALPH_LOG_FILE" + echo "Mode: $RALPH_AUTO_MODE" >> "$RALPH_LOG_FILE" + echo "Agent: $detected_agent" >> "$RALPH_LOG_FILE" + echo "Max iterations: $RALPH_MAX_ITERATIONS" >> "$RALPH_LOG_FILE" + echo "---" >> "$RALPH_LOG_FILE" + + # Start Ralph in background + nohup ralph build "$RALPH_MAX_ITERATIONS" >> "$RALPH_LOG_FILE" 2>&1 & + RALPH_PID=$! + + # Save PID for tracking + echo "$RALPH_PID" > "$RALPH_PID_FILE" + echo "$RALPH_PID" > "$RALPH_LOCK_FILE" + + # Log the trigger + { + echo "[$(date -u +"%Y-%m-%d %H:%M:%S UTC")] Ralph auto-triggered" + echo " Mode: $RALPH_AUTO_MODE" + echo " Agent: $detected_agent" + echo " PID: $RALPH_PID" + echo " Log: $RALPH_LOG_FILE" + } >> "$CLAUDE_DIR/ralph-trigger.log" 2>/dev/null || true + + # Notify user via stderr (visible in Claude Code) + echo "🔄 Ralph CLI auto-started in background" >&2 + echo " PID: $RALPH_PID" >&2 + echo " Agent: $detected_agent" >&2 + echo " Monitor: tail -f ~/.claude/ralph-output.log" >&2 + echo " Stop: kill \$(cat ~/.claude/ralph.pid)" >&2 + else + # Ralph not installed, just create state file + echo "⚠️ Ralph CLI not installed. State file created for manual use." >&2 + echo " Install: npm install -g @iannuttall/ralph" >&2 + fi fi +# Exit immediately (NON-BLOCKING - Claude Code continues) exit 0 EOF @@ -609,8 +727,11 @@ Note: Replace "/home/uroma" with your actual home directory if different. ### What This Provides: -**Autonomous Looping:** -- Ralph keeps iterating until task completion +**Background Autonomous Looping:** +- Ralph runs as background process (NON-BLOCKING) +- Claude Code continues immediately +- Ralph output logged to: ~/.claude/ralph-output.log +- Ralph PID tracked in: ~/.claude/ralph.pid - State preserved in `.claude/ralph-loop.local.md` - Can handle complex multi-step workflows @@ -625,46 +746,70 @@ export RALPH_AUTO_MODE="always" # Mode 3: Disable auto-loop export RALPH_AUTO_MODE="off" -### How It Works: +# Set max iterations (default: 50) +export RALPH_MAX_ITERATIONS="100" -1. You request: "Build a TikTok app in 2 weeks" +### How It Works (Background Execution): + +1. You request in Claude Code: "Build a TikTok app in 2 weeks" 2. Hook detects agent language -3. Ralph creates state file and starts loop -4. Ralph iterates through agents: +3. Hook spawns Ralph in BACKGROUND (non-blocking) +4. Claude Code immediately continues (you can keep working!) +5. Ralph autonomously iterates in background: - Iteration 1: rapid-prototyper builds MVP - Iteration 2: tiktok-strategist plans viral features - Iteration 3: frontend-developer builds UI - - Continues until Ralph is satisfied -5. Ralph updates `.claude/ralph-loop.local.md` each iteration -6. Stop with: /cancel-ralph command or delete state file + - Continues until Ralph completes max iterations +6. Monitor Ralph: tail -f ~/.claude/ralph-output.log +7. Stop Ralph anytime: kill $(cat ~/.claude/ralph.pid) + +### Monitoring Ralph: + +# View Ralph output in real-time +tail -f ~/.claude/ralph-output.log + +# Check if Ralph is still running +ps aux | grep ralph +# Or check the PID file +cat ~/.claude/ralph.pid + +# View Ralph trigger log +cat ~/.claude/ralph-trigger.log ### Canceling Ralph: -If Ralph is looping and you want to stop: +If Ralph is running in background and you want to stop: -# Method 1: Use cancel command -/cancel-ralph +# Method 1: Kill by PID (recommended) +kill $(cat ~/.claude/ralph.pid) +rm ~/.claude/ralph.lock -# Method 2: Delete state file -rm .claude/ralph-loop.local.md +# Method 2: Kill by process name +pkill -f "ralph build" -# Method 3: Disable auto-mode +# Method 3: Disable auto-mode (prevents future triggers) export RALPH_AUTO_MODE="off" +# Method 4: Delete state file (Ralph will stop on next iteration) +rm ~/.claude/ralph-loop.local.md + ### Ralph vs PROACTIVELY Agents: -| Feature | Ralph CLI | PROACTIVELY Agents | -|---------|-----------|-------------------| -| **Execution** | Multi-loop iterations | Single interaction | -| **State** | Persisted in file | Context only | -| **Control** | Manual stop required | Automatic | +| Feature | Ralph CLI (Background) | PROACTIVELY Agents | +|---------|------------------------|-------------------| +| **Execution** | Multi-loop, background process | Single interaction | +| **Blocking** | NON-BLOCKING (runs in bg) | Blocking (waits for response) | +| **State** | Persisted in file + log | Context only | +| **Control** | Manual stop via PID | Automatic completion | | **Complexity** | Handles complex workflows | Quick coordination | | **Setup** | Requires hook + script | Built-in to agents | +| **Best For** | Large projects, overnight coding | Quick tasks, human collaboration | **You can use BOTH together:** -- Use Ralph for complex, multi-step projects -- Use PROACTIVELY agents for quick tasks +- Use Ralph for complex, multi-step projects (autonomous) +- Use PROACTIVELY agents for quick tasks (interactive) - Set RALPH_AUTO_MODE="agents" for hybrid approach +- Ralph runs in background while you continue working in Claude Code --- diff --git a/interactive-install-claude.sh b/interactive-install-claude.sh index 0acea8f..f90df2f 100755 --- a/interactive-install-claude.sh +++ b/interactive-install-claude.sh @@ -1151,15 +1151,63 @@ install_ralph() { # Create hooks directory mkdir -p "$CLAUDE_DIR/hooks" - # Create ralph-auto-trigger.sh script - log_info "Creating Ralph auto-trigger hook..." + # Create ralph-auto-trigger.sh script (Enhanced with Background Spawning) + log_info "Creating Ralph auto-trigger hook with background spawning..." cat > "$CLAUDE_DIR/hooks/ralph-auto-trigger.sh" << 'RALPH_HOOK_EOF' #!/bin/bash -# Ralph Auto-Trigger Hook -# Detects agent requests and manages Ralph state for autonomous looping +# Ralph Auto-Trigger Hook - Enhanced with Background Task Spawning +# Automatically starts Ralph CLI in background when needed +# +# Modes (via RALPH_AUTO_MODE environment variable): +# "always" - Start Ralph for every request +# "agents" - Only for agent requests (default) +# "off" - Disable auto-trigger +# +# Background Execution: +# - Ralph runs as background process (non-blocking) +# - Claude Code continues immediately +# - Ralph output logged to: ~/.claude/ralph-output.log +# - Ralph PID tracked in: ~/.claude/ralph.pid -STATE_FILE="$HOME/.claude/ralph-loop.local.md" -RALPH_MODE="${RALPH_AUTO_MODE:-agents}" # Options: always, agents, off +set -euo pipefail + +# Configuration +CLAUDE_DIR="$HOME/.claude" +RALPH_STATE_FILE="$CLAUDE_DIR/ralph-loop.local.md" +RALPH_PID_FILE="$CLAUDE_DIR/ralph.pid" +RALPH_LOG_FILE="$CLAUDE_DIR/ralph-output.log" +RALPH_LOCK_FILE="$CLAUDE_DIR/ralph.lock" + +# Read hook input from stdin +HOOK_INPUT=$(cat) +USER_PROMPT=$(echo "$HOOK_INPUT" | jq -r '.prompt // empty' 2>/dev/null || echo "") + +# Fallback: if no JSON input, use first argument +if [[ -z "$USER_PROMPT" && $# -gt 0 ]]; then + USER_PROMPT="$1" +fi + +# Get Ralph mode (default: agents) +RALPH_AUTO_MODE="${RALPH_AUTO_MODE:-agents}" +RALPH_MAX_ITERATIONS="${RALPH_MAX_ITERATIONS:-50}" + +# Exit if auto-trigger is disabled +if [[ "$RALPH_AUTO_MODE" == "off" ]]; then + exit 0 +fi + +# Check if Ralph is already running (via lock file) +if [[ -f "$RALPH_LOCK_FILE" ]]; then + # Check if process is still alive + LOCK_PID=$(cat "$RALPH_LOCK_FILE" 2>/dev/null || echo "") + if [[ -n "$LOCK_PID" ]] && kill -0 "$LOCK_PID" 2>/dev/null; then + # Ralph is already running, don't start another instance + exit 0 + else + # Lock file exists but process is dead, clean up + rm -f "$RALPH_LOCK_FILE" "$RALPH_PID_FILE" + fi +fi # Agent detection list (lowercase for matching) AGENTS=( @@ -1176,16 +1224,9 @@ AGENTS=( "api-tester" "performance-benchmarker" "test-results-analyzer" "tool-evaluator" "workflow-optimizer" "joker" "agent-updater" + "explore" "plan" "general-purpose" ) -# Check if mode is off -if [ "$RALPH_MODE" = "off" ]; then - exit 0 -fi - -# Read the user prompt -USER_PROMPT="$1" - # Detect agent request (case-insensitive) agent_detected=false detected_agent="" @@ -1198,51 +1239,114 @@ for agent in "${AGENTS[@]}"; do fi done -# Check if we should trigger +# Determine if we should start Ralph should_trigger=false -if [ "$RALPH_MODE" = "always" ]; then - # Trigger on all prompts - should_trigger=true -elif [ "$RALPH_MODE" = "agents" ]; then - # Only trigger on agent requests - if [ "$agent_detected" = true ]; then +case "$RALPH_AUTO_MODE" in + "always") + # Trigger on all prompts should_trigger=true - fi -fi + ;; + "agents") + # Only trigger on agent requests OR development keywords + if [[ "$agent_detected" == true ]]; then + should_trigger=true + elif echo "$USER_PROMPT" | grep -qiE "build|create|implement|develop|fix|add|refactor|optimize|write|generate|delegate|autonomous"; then + should_trigger=true + detected_agent="general-development" + fi + ;; +esac -if [ "$should_trigger" = true ]; then - # Create/update state file - cat > "$STATE_FILE" << EOF -# Ralph Loop State -# Generated: $(date) +if [[ "$should_trigger" == true ]]; then + # Create Ralph state file + mkdir -p "$CLAUDE_DIR" + + cat > "$RALPH_STATE_FILE" << EOF +# Ralph Loop State - Auto-Triggered +# Generated: $(date -u +"%Y-%m-%d %H:%M:%S UTC") **User Request:** $USER_PROMPT **Detected Agent:** $detected_agent -**Mode:** $RALPH_MODE +**Mode:** $RALPH_AUTO_MODE +**Max Iterations:** $RALPH_MAX_ITERATIONS **Timestamp:** $(date -Iseconds) ## Context This state file was automatically generated by the Ralph auto-trigger hook. -Ralph CLI can read this file to continue autonomous looping based on the user's request. +Ralph CLI will read this file and autonomously execute the request. + +## Auto-Trigger Details + +- Triggered by: Claude Code UserPromptSubmit hook +- Trigger mode: $RALPH_AUTO_MODE +- Background execution: Yes (non-blocking) +- Log file: $RALPH_LOG_FILE ## Usage -To run Ralph autonomously: -\`\`\`bash -ralph build 50 # Run 50 iterations -\`\`\` +Ralph is running autonomously in the background. Monitor progress: + +bash +# View Ralph output in real-time +tail -f ~/.claude/ralph-output.log + +# Check if Ralph is still running +ps aux | grep ralph + +# Stop Ralph manually +kill $(cat ~/.claude/ralph.pid) +rm ~/.claude/ralph.lock + -Ralph will read this state file and continue working until the task is complete. EOF - # Log the trigger (silent, no output) - echo "[$(date '+%Y-%m-%d %H:%M:%S')] Ralph auto-triggered: mode=$RALPH_MODE, agent=$detected_agent" >> "$HOME/.claude/ralph-trigger.log" 2>/dev/null || true + # Spawn Ralph in background (NON-BLOCKING) + if command -v ralph &> /dev/null; then + # Create log file + touch "$RALPH_LOG_FILE" + + # Start Ralph in background with nohup (survives terminal close) + echo "[$(date -u +"%Y-%m-%d %H:%M:%S UTC")] Starting Ralph in background..." >> "$RALPH_LOG_FILE" + echo "Mode: $RALPH_AUTO_MODE" >> "$RALPH_LOG_FILE" + echo "Agent: $detected_agent" >> "$RALPH_LOG_FILE" + echo "Max iterations: $RALPH_MAX_ITERATIONS" >> "$RALPH_LOG_FILE" + echo "---" >> "$RALPH_LOG_FILE" + + # Start Ralph in background + nohup ralph build "$RALPH_MAX_ITERATIONS" >> "$RALPH_LOG_FILE" 2>&1 & + RALPH_PID=$! + + # Save PID for tracking + echo "$RALPH_PID" > "$RALPH_PID_FILE" + echo "$RALPH_PID" > "$RALPH_LOCK_FILE" + + # Log the trigger + { + echo "[$(date -u +"%Y-%m-%d %H:%M:%S UTC")] Ralph auto-triggered" + echo " Mode: $RALPH_AUTO_MODE" + echo " Agent: $detected_agent" + echo " PID: $RALPH_PID" + echo " Log: $RALPH_LOG_FILE" + } >> "$CLAUDE_DIR/ralph-trigger.log" 2>/dev/null || true + + # Notify user via stderr (visible in Claude Code) + echo "🔄 Ralph CLI auto-started in background" >&2 + echo " PID: $RALPH_PID" >&2 + echo " Agent: $detected_agent" >&2 + echo " Monitor: tail -f ~/.claude/ralph-output.log" >&2 + echo " Stop: kill \$(cat ~/.claude/ralph.pid)" >&2 + else + # Ralph not installed, just create state file + echo "⚠️ Ralph CLI not installed. State file created for manual use." >&2 + echo " Install: npm install -g @iannuttall/ralph" >&2 + fi fi +# Exit immediately (NON-BLOCKING - Claude Code continues) exit 0 RALPH_HOOK_EOF