Ralph Execute — Autonomous Epic Runner
Process a Beads epic autonomously, dispatching fresh subagents per task with two-stage review gates.
Arguments
- First argument: Beads epic ID (required)
--dry-run: Show what would execute without doing it (optional)
Pre-Flight Checks
Before starting the loop:
- Verify epic exists:
bd show <epic-id> - Check ready tasks:
bd ready --parent <epic-id>— must have at least one - Verify worktree: Must be in a worktree, not main repo (enforced by block-work-on-develop hook)
- Initialize circuit breaker:
.claude/hooks/circuit-breaker.sh reset - Create session marker:
mkdir -p .ralph-session/questions .ralph-session/answers .ralph-session/messages - Notify start: Send ntfy notification "Ralph starting epic <id>"
- Set phase to IMPLEMENT: If
.claude/phases/output/current-phase.jsonexists andcurrent_phaseis notimplement, update it:
If no phase file exists, create one:bash1PHASE_FILE=".claude/phases/output/current-phase.json" 2if [[ -f "$PHASE_FILE" ]]; then 3 python3 -c " 4import json 5with open('$PHASE_FILE') as f: state = json.load(f) 6if state.get('current_phase') != 'implement': 7 completed = state.get('completed_phases', []) 8 if state.get('current_phase') and state['current_phase'] not in completed: 9 completed.append(state['current_phase']) 10 state['current_phase'] = 'implement' 11 state['completed_phases'] = completed 12 with open('$PHASE_FILE', 'w') as f: json.dump(state, f, indent=2) 13" 14fibash1mkdir -p .claude/phases/output 2echo '{"topic":"<epic-name>","current_phase":"implement","completed_phases":["research","plan"],"output_files":{}}' > "$PHASE_FILE"
Execution Loop
For each task from bd ready --parent <epic-id>:
Step A: Claim Task
bash1bd update <task-id> --status in_progress
Log activity and update monitor:
bash1echo "[$(date +%H:%M)] Task <task-id> started: <title>" >> .ralph-session/ralph.log 2.claude/hooks/circuit-breaker.sh write-status "<task-id>" "<title>" <total> <completed> <mode>
Step A.5: Plan Pass (First Iteration Only)
For the FIRST iteration of each task, run a gap analysis:
bash1.claude/hooks/generate-prompt.sh <task-id> .ralph-session/PROMPT.md plan
Dispatch a read-only subagent (model: haiku) with the plan-mode PROMPT.md.
The subagent writes findings to .ralph-session/gap-analysis.md.
Validate plan pass output:
After the plan subagent completes, verify .ralph-session/gap-analysis.md exists and has content:
bash1if [[ ! -f ".ralph-session/gap-analysis.md" ]] || [[ $(wc -w < ".ralph-session/gap-analysis.md") -lt 20 ]]; then 2 echo "[Ralph] Plan pass failed — no gap analysis produced" >> .ralph-session/ralph.log 3 .claude/hooks/circuit-breaker.sh record-failure 4 # Skip to next task 5 continue 6fi
Read the gap analysis before proceeding to build mode. Use it to inform the implementation subagent's context.
Step B: Generate PROMPT.md
bash1.claude/hooks/generate-prompt.sh <task-id> .ralph-session/PROMPT.md build
Step C: Check Circuit Breaker
bash1.claude/hooks/circuit-breaker.sh check
If PAUSED — stop loop, notify human, exit.
If DEGRADED — wait 30 seconds (sleep 30) as a backoff, then continue.
Step D: Dispatch Subagent
Launch a Task subagent with the PROMPT.md content. The subagent:
- Reads the generated PROMPT.md
- Implements the task following the rules
- Runs verification commands (test, lint, typecheck)
- Outputs
RALPH_COMPLETEwhen done - Writes completion signal:
echo "RALPH_COMPLETE" > .ralph-session/completion-signal
Step D.5: Handle Escalation
After the subagent returns, check if it escalated:
If result contains ESCALATE::
- Read the question file:
bash
1cat .ralph-session/questions/<task-id>.json - Log the escalation:
bash
1echo "[$(date +%H:%M)] Task <task-id> ESCALATED: <summary>" >> .ralph-session/ralph.log - Ask the user the question directly in your response text. Include the full question, context, and options from the JSON file.
- Write the user's answer to
.ralph-session/answers/<task-id>.json:json1{"task_id":"<task-id>","answer":"<user's response>","timestamp":"<iso>"} - Dispatch a new subagent with a Task call that includes the user's answer in the prompt context, along with the original task description and any partial work from
.ralph-session/. - After the new subagent completes, proceed to Step E as normal.
If result contains RALPH_COMPLETE:
Proceed to Step E.
If result contains neither: The subagent may have failed silently. Record failure and proceed:
bash1.claude/hooks/circuit-breaker.sh record-failure 2echo "[$(date +%H:%M)] Task <task-id> returned without completion signal" >> .ralph-session/ralph.log
Step E: Two-Stage Review
Stage 1 — Spec Compliance: Launch a review subagent that compares the code changes against the task description. Check: Does the implementation match what was requested? Any missing requirements?
Stage 2 — Code Quality:
Launch the code-reviewer agent (.claude/agents/code-reviewer.md).
Check: Anti-patterns, security, test coverage, conventions.
Step F: Handle Review Results
If both pass:
bash1.claude/hooks/circuit-breaker.sh record-success 2bd close <task-id> 3curl -s -X POST "https://ntfy.sh/property-tracker-claude" \ 4 -d "Task <task-id> complete" -H "Title: Ralph Progress"
Clean up: rm -f .ralph-session/completion-signal
Log completion:
bash1echo "[$(date +%H:%M)] Task <task-id> completed (pass@1)" >> .ralph-session/ralph.log
If either fails:
bash1.claude/hooks/circuit-breaker.sh record-failure
Dispatch a fix subagent with the review feedback. Then re-run Step E. Max 3 fix attempts per task (enforced by circuit breaker).
Step F.5: Record to eval-tracker
After each task resolution (pass or max-attempts-exceeded), append to .claude/instincts/eval-tracker.jsonl:
bash1echo "{\"task_id\":\"<task-id>\",\"epic_id\":\"<epic-id>\",\"attempts\":1,\"fix_attempts\":<N>,\"passed\":<true|false>,\"date\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"branch\":\"$(git branch --show-current)\",\"duration_sec\":<elapsed>,\"skill_context\":[\"<instinct-id-1>\",\"<instinct-id-2>\"]}" >> .claude/instincts/eval-tracker.jsonl
Where:
attempts: always 1 (one dispatch per task)fix_attempts: 0 if passed on first try, 1-3 for fix iterationspassed: true if eventually passed review, false if max attempts exceededduration_sec: wall-clock seconds from dispatch to completionskill_context: array of instinct IDs that were active (injected via{{LEARNED_PATTERNS}}) when this task was dispatched. Enables/eval-reportStep 4 to measure per-instinct effectiveness.
Step G: Next Task
Loop back to Step A with the next bd ready --parent <epic-id> task.
Exit Conditions
The loop exits when ANY of these are true:
bd ready --parent <epic-id>returns no tasks (epic complete)- Circuit breaker enters PAUSED state
- User manually creates
.ralph-session/STOPfile
Cleanup
On exit (success or circuit breaker), save summary before deleting session evidence:
bash1# Preserve session summary for post-mortem analysis 2mkdir -p .claude/agent-memory/ralph 3if [[ -f .ralph-session/circuit-breaker.json ]]; then 4 cp .ralph-session/circuit-breaker.json ".claude/agent-memory/ralph/session-$(date +%Y%m%d-%H%M%S).json" 5fi 6rm -rf .ralph-session
Phase Advance (auto): After all tasks complete (not on PAUSED exit):
bash1PHASE_FILE=".claude/phases/output/current-phase.json" 2TOPIC=$(python3 -c "import json; print(json.load(open('$PHASE_FILE')).get('topic','unknown'))" 2>/dev/null) 3 4# Run checkpoint evaluation 5CHECKPOINT_RESULT=$(.claude/hooks/checkpoint-eval.sh implement review "$TOPIC" 2>&1) 6CHECKPOINT_EXIT=$? 7 8if [[ "$CHECKPOINT_EXIT" -eq 0 ]]; then 9 python3 -c " 10import json 11with open('$PHASE_FILE') as f: state = json.load(f) 12state['completed_phases'] = list(set(state.get('completed_phases', []) + ['implement'])) 13state['current_phase'] = 'review' 14with open('$PHASE_FILE', 'w') as f: json.dump(state, f, indent=2) 15" 16 echo "Phase advanced to REVIEW" 17else 18 echo "Phase remains IMPLEMENT — checkpoint failed: $CHECKPOINT_RESULT" 19fi
On PAUSED exit, do NOT advance phase — user intervention needed.
Send final notification:
bash1curl -s -X POST "https://ntfy.sh/property-tracker-claude" \ 2 -d "Ralph finished epic <id>. Status: <complete|paused>" \ 3 -H "Title: Ralph Done" -H "Priority: high" 4osascript -e 'display notification "Ralph finished" with title "Claude Code"'
Safety Guarantees
- All 17 existing hooks remain active (lint, anti-patterns, security, auth checks)
- Circuit breaker prevents infinite loops (max 3 failures per task, max 5 iterations without progress)
- Fresh subagent per task prevents context pollution
- Two-stage review catches both spec drift and quality issues
- ntfy notifications on every state change
Language Patterns (From Ghuntley Research)
When generating PROMPT.md, these language patterns improve subagent performance:
- "Study" not "read" or "look at" — triggers deeper analysis
- "Don't assume not implemented" — prevents hallucinated code
- "Using parallel subagents" — explicit parallelism instruction
- "Only 1 subagent for build/tests" — enforces backpressure
- "Capture the why" — documentation emphasis