diff --git a/ralph-loop.sh b/ralph-loop.sh index 3ac1de7..7eb9931 100755 --- a/ralph-loop.sh +++ b/ralph-loop.sh @@ -7,7 +7,10 @@ # - Agent reads the plan, picks ONE task, implements, tests, commits, exits # - Loop restarts until all tasks are done # -# No context compaction. No stale reasoning. Just fresh starts. +# Session limit handling: +# - Detects Claude Pro usage limit messages in agent output +# - Polls every SESSION_POLL_INTERVAL seconds until the session resets +# - Resumes the same iteration automatically — no manual intervention needed # # Usage: # ./ralph-loop.sh # Build mode (default) @@ -19,22 +22,6 @@ # ./ralph-loop.sh --agent gemini # Use Gemini CLI # ./ralph-loop.sh --agent custom # Use custom agent (see below) # -# Extensibility: -# To add support for other AI coding agents (aider, cursor, windsurf, etc.): -# 1. Add a new case in the run_agent() function's agent selection block -# 2. Format the prompt appropriately for that agent's CLI interface -# 3. Ensure the agent outputs to the logfile for promise detection -# -# Example for Aider: -# aider) -# aider --message "$prompt" --yes 2>&1 | tee "$logfile" -# ;; -# -# Example for custom script: -# custom) -# ./my-agent-wrapper.sh "$prompt" 2>&1 | tee "$logfile" -# ;; -# set -euo pipefail MODE="${1:-build}" @@ -45,6 +32,10 @@ SPEC_FILE="PROJECT-SPEC.md" AGENT_FILE="AGENT.md" LOG_DIR=".ralph-logs" +# How often (in seconds) to probe whether the session has reset. +# Default: 10 minutes. Adjust down if you want faster recovery. +SESSION_POLL_INTERVAL="${SESSION_POLL_INTERVAL:-600}" + # Parse arguments shift 2>/dev/null || true while [[ $# -gt 0 ]]; do @@ -62,12 +53,14 @@ GREEN='\033[0;32m' YELLOW='\033[1;33m' RED='\033[0;31m' BLUE='\033[0;34m' +CYAN='\033[0;36m' NC='\033[0m' -log() { echo -e "${BLUE}[ralph]${NC} $1"; } +log() { echo -e "${BLUE}[ralph]${NC} $1"; } success() { echo -e "${GREEN}[ralph]${NC} $1"; } -warn() { echo -e "${YELLOW}[ralph]${NC} $1"; } -error() { echo -e "${RED}[ralph]${NC} $1"; } +warn() { echo -e "${YELLOW}[ralph]${NC} $1"; } +error() { echo -e "${RED}[ralph]${NC} $1"; } +info() { echo -e "${CYAN}[ralph]${NC} $1"; } # Check prerequisites if [[ ! -f "$SPEC_FILE" ]]; then @@ -93,8 +86,9 @@ run_agent() { log "Iteration $iteration ($mode mode) — starting fresh agent..." - # Agent selection block - # Extend this case statement to support additional agents + # Disable pipefail around the agent call so a non-zero claude exit doesn't + # kill the script. We inspect the log content instead. + set +e case "$AGENT" in claude) echo "$prompt" | claude -p --output-format text 2>&1 | tee "$logfile" @@ -103,23 +97,12 @@ run_agent() { echo "$prompt" | codex 2>&1 | tee "$logfile" ;; aider) - # Aider: AI pair programming in your terminal - # https://aider.chat aider --message "$prompt" --yes 2>&1 | tee "$logfile" ;; gemini) - # Google Gemini CLI (if available) - # Adjust command based on actual Gemini CLI interface echo "$prompt" | gemini-cli 2>&1 | tee "$logfile" ;; custom) - # Custom agent integration - # Replace this with your own agent wrapper script - # The script should: - # 1. Accept prompt as first argument or via stdin - # 2. Perform the requested work (read files, write code, run tests, commit) - # 3. Output promise signals: PLANNED|DONE|STUCK|ERROR - # 4. Exit with appropriate code if [[ -x "./custom-agent.sh" ]]; then ./custom-agent.sh "$prompt" 2>&1 | tee "$logfile" else @@ -130,29 +113,82 @@ run_agent() { *) error "Unknown agent: $AGENT" error "Supported agents: claude, codex, aider, gemini, custom" - error "To add support for other agents, edit the run_agent() function in this script" exit 1 ;; esac + set -e return 0 } +# Probe whether claude is available by sending a trivial request. +# Returns 0 if available, 1 if still rate-limited or erroring. +probe_session() { + local probe_log="$LOG_DIR/probe.log" + set +e + echo "Reply with the single word OK and nothing else." \ + | claude -p --output-format text > "$probe_log" 2>&1 + local rc=$? + set -e + + if [[ $rc -ne 0 ]]; then + return 1 + fi + # Also check the output doesn't contain a limit message + if grep -qi 'usage limit\|rate limit\|limit reached\|exceeded.*limit' "$probe_log" 2>/dev/null; then + return 1 + fi + return 0 +} + check_output() { local logfile="$1" + # Session / usage limit — must check BEFORE generic promise checks + if grep -qi 'usage limit\|rate limit\|limit reached\|exceeded.*limit\|Claude AI usage' "$logfile" 2>/dev/null; then + return 4 # Rate limited + fi + if grep -q 'DONE' "$logfile" 2>/dev/null; then return 0 # Done elif grep -q 'STUCK' "$logfile" 2>/dev/null; then - return 2 # Stuck + return 2 # Stuck — needs human intervention elif grep -q 'ERROR' "$logfile" 2>/dev/null; then - return 3 # Error + return 3 # Unrecoverable error else - return 1 # Continue + return 1 # Normal iteration — continue fi } -# Main loop +wait_for_session_reset() { + local iteration=$1 + warn "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + warn "Session usage limit hit during iteration $iteration." + warn "Will probe every ${SESSION_POLL_INTERVAL}s until session resets." + warn "No manual action needed — loop will resume automatically." + warn "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + + local attempt=0 + while true; do + ((attempt++)) + local next_check + next_check=$(date -d "+${SESSION_POLL_INTERVAL} seconds" '+%H:%M:%S' 2>/dev/null \ + || date -v "+${SESSION_POLL_INTERVAL}S" '+%H:%M:%S' 2>/dev/null \ + || echo "soon") + info "Probe attempt $attempt — next check at $next_check..." + sleep "$SESSION_POLL_INTERVAL" + + if probe_session; then + success "Session available! Resuming iteration $iteration..." + return 0 + else + warn "Still rate-limited (attempt $attempt). Waiting another ${SESSION_POLL_INTERVAL}s..." + fi + done +} + +# ─── Main ──────────────────────────────────────────────────────────────────── + if [[ "$MODE" == "plan" ]]; then log "Planning mode — creating implementation plan..." run_agent 0 plan @@ -161,35 +197,44 @@ if [[ "$MODE" == "plan" ]]; then fi log "Starting Ralph Wiggum loop (max $MAX_ITERATIONS iterations)" -log "Agent: $AGENT" -log "Spec: $SPEC_FILE" -log "Plan: $PLAN_FILE" +log "Agent: $AGENT" +log "Spec: $SPEC_FILE" +log "Plan: $PLAN_FILE" +log "Poll interval: ${SESSION_POLL_INTERVAL}s (session limit recovery)" echo "" -for i in $(seq 1 "$MAX_ITERATIONS"); do +i=1 +while [[ $i -le $MAX_ITERATIONS ]]; do run_agent "$i" build logfile="$LOG_DIR/iteration-${i}.log" - check_output "$logfile" - status=$? + # Capture return value without triggering set -e + check_output "$logfile" || status=$? + status=${status:-0} case $status in 0) - success "🎉 ALL TASKS COMPLETE after $i iterations!" + success "ALL TASKS COMPLETE after $i iterations!" exit 0 ;; 2) - warn "Agent is stuck. Review $logfile and intervene." + warn "Agent is stuck on iteration $i. Review $logfile and intervene." exit 1 ;; 3) - error "Agent encountered an error. Review $logfile." + error "Agent encountered an error on iteration $i. Review $logfile." exit 1 ;; + 4) + # Rate limited — wait for reset, then retry the SAME iteration + wait_for_session_reset "$i" + # Do NOT increment i — retry the same task + ;; 1) log "Iteration $i complete. Restarting with fresh context..." echo "" sleep 2 + ((i++)) ;; esac done