From c621dad7ae3ff5f3099ad2fc70bbbdbfef1854f3 Mon Sep 17 00:00:00 2001 From: ericzakariasson Date: Wed, 11 Feb 2026 15:26:17 -0800 Subject: [PATCH 1/3] Add Ralph Loop plugin Port of the Ralph Wiggum technique from Claude Code to Cursor. Runs Cursor in a self-referential loop, feeding the same prompt back after every turn until a completion condition is met. Components: - Stop hook that intercepts session exit and re-injects the prompt - Setup script for initializing loop state - Skills for starting, cancelling, and explaining the loop Co-authored-by: Cursor --- .cursor-plugin/marketplace.json | 5 + README.md | 1 + ralph-loop/.cursor-plugin/plugin.json | 29 ++++ ralph-loop/LICENSE | 21 +++ ralph-loop/README.md | 61 +++++++ ralph-loop/hooks/hooks.json | 15 ++ ralph-loop/hooks/stop-hook.sh | 177 ++++++++++++++++++++ ralph-loop/scripts/setup-ralph-loop.sh | 186 +++++++++++++++++++++ ralph-loop/skills/cancel-ralph/SKILL.md | 32 ++++ ralph-loop/skills/ralph-loop-help/SKILL.md | 81 +++++++++ ralph-loop/skills/ralph-loop/SKILL.md | 39 +++++ 11 files changed, 647 insertions(+) create mode 100644 ralph-loop/.cursor-plugin/plugin.json create mode 100644 ralph-loop/LICENSE create mode 100644 ralph-loop/README.md create mode 100644 ralph-loop/hooks/hooks.json create mode 100755 ralph-loop/hooks/stop-hook.sh create mode 100755 ralph-loop/scripts/setup-ralph-loop.sh create mode 100644 ralph-loop/skills/cancel-ralph/SKILL.md create mode 100644 ralph-loop/skills/ralph-loop-help/SKILL.md create mode 100644 ralph-loop/skills/ralph-loop/SKILL.md diff --git a/.cursor-plugin/marketplace.json b/.cursor-plugin/marketplace.json index 4b1c895..193fb08 100644 --- a/.cursor-plugin/marketplace.json +++ b/.cursor-plugin/marketplace.json @@ -72,6 +72,11 @@ "name": "figma", "source": "figma", "description": "Figma MCP integration for design and collaboration." + }, + { + "name": "ralph-loop", + "source": "ralph-loop", + "description": "Iterative self-referential AI loops using the Ralph Wiggum technique." } ] } diff --git a/README.md b/README.md index b3ab0f3..6edfaed 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Official Cursor plugins for popular developer tools, frameworks, and SaaS produc | [Learning](learning/) | Utilities | Skill maps, practice plans, and feedback loops | | [Cursor Dev Kit](cursor-dev-kit/) | Developer Tools | Internal-style workflows for CI, code review, shipping, and testing | | [Create Plugin](create-plugin/) | Developer Tools | Meta workflows for creating Cursor plugins with scaffolding and submission checks | +| [Ralph Loop](ralph-loop/) | Developer Tools | Iterative self-referential AI loops using the Ralph Wiggum technique | ## Repository structure diff --git a/ralph-loop/.cursor-plugin/plugin.json b/ralph-loop/.cursor-plugin/plugin.json new file mode 100644 index 0000000..4d5ed2a --- /dev/null +++ b/ralph-loop/.cursor-plugin/plugin.json @@ -0,0 +1,29 @@ +{ + "name": "ralph-loop", + "displayName": "Ralph Loop", + "version": "1.0.0", + "description": "Continuous self-referential AI loops for iterative development, implementing the Ralph Wiggum technique. Run the agent in a while-true loop with the same prompt until task completion.", + "author": { + "name": "Cursor", + "email": "plugins@cursor.com" + }, + "homepage": "https://github.com/cursor/plugins", + "repository": "https://github.com/cursor/plugins", + "license": "MIT", + "keywords": [ + "ralph", + "loop", + "iteration", + "self-referential", + "automation", + "iterative-development" + ], + "category": "developer-tools", + "tags": [ + "automation", + "iteration", + "agent-loop" + ], + "skills": "./skills/", + "hooks": "./hooks/hooks.json" +} diff --git a/ralph-loop/LICENSE b/ralph-loop/LICENSE new file mode 100644 index 0000000..ca2bba7 --- /dev/null +++ b/ralph-loop/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Cursor + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/ralph-loop/README.md b/ralph-loop/README.md new file mode 100644 index 0000000..b0fd81c --- /dev/null +++ b/ralph-loop/README.md @@ -0,0 +1,61 @@ +# Ralph Loop + +Ralph Loop runs Cursor in a self-referential loop, feeding the same prompt back after every turn until the task is complete. It implements the [Ralph Wiggum technique](https://ghuntley.com/ralph/) pioneered by Geoffrey Huntley. + +## How it works + +A Stop hook intercepts each time Cursor tries to finish. Instead of ending the session, the hook feeds the original prompt back. Cursor sees its own previous edits in the working tree and git history, iterates on them, and repeats. The prompt never changes. The code does. + +## Installation + +``` +agent install ralph-loop +``` + +## Quick start + +> Start a ralph loop: "Build a REST API for todos. CRUD operations, input validation, tests. Output COMPLETE when done." --completion-promise "COMPLETE" --max-iterations 50 + +Cursor will implement the API, run tests, see failures, fix them, and repeat until all requirements are met. + +## Skills + +**ralph-loop** starts the loop. Provide a prompt and options: + +> Start a ralph loop: "Refactor the cache layer" --max-iterations 20 --completion-promise "DONE" + +- `--max-iterations ` stops after N iterations (default: unlimited) +- `--completion-promise ` sets the phrase that signals completion + +**cancel-ralph** removes the state file and stops the loop. + +**ralph-loop-help** explains the technique and usage in detail. + +## Writing good prompts + +Define explicit completion criteria. Vague goals like "make it good" give Cursor nothing to verify against. + +```markdown +Build a REST API for todos. + +When complete: +- All CRUD endpoints working +- Input validation in place +- Tests passing (coverage > 80%) +- Output: COMPLETE +``` + +Break large tasks into phases. Encourage self-correction by including test/fix cycles in the prompt. Always pass `--max-iterations` to prevent runaway loops. + +## When to use Ralph Loop + +Works well for tasks with clear, verifiable success criteria: getting tests to pass, completing a migration, building a feature from a spec. Not a good fit for tasks that need human judgment or have ambiguous goals. + +## Learn more + +- [Original technique by Geoffrey Huntley](https://ghuntley.com/ralph/) +- [Ralph Orchestrator](https://github.com/mikeyobrien/ralph-orchestrator) + +## License + +MIT diff --git a/ralph-loop/hooks/hooks.json b/ralph-loop/hooks/hooks.json new file mode 100644 index 0000000..9b1fd44 --- /dev/null +++ b/ralph-loop/hooks/hooks.json @@ -0,0 +1,15 @@ +{ + "description": "Ralph Loop plugin stop hook for self-referential loops", + "hooks": { + "Stop": [ + { + "hooks": [ + { + "type": "command", + "command": "./hooks/stop-hook.sh" + } + ] + } + ] + } +} diff --git a/ralph-loop/hooks/stop-hook.sh b/ralph-loop/hooks/stop-hook.sh new file mode 100755 index 0000000..d826051 --- /dev/null +++ b/ralph-loop/hooks/stop-hook.sh @@ -0,0 +1,177 @@ +#!/bin/bash + +# Ralph Loop Stop Hook +# Prevents session exit when a ralph-loop is active +# Feeds the agent's output back as input to continue the loop + +set -euo pipefail + +# Read hook input from stdin (advanced stop hook API) +HOOK_INPUT=$(cat) + +# Check if ralph-loop is active +RALPH_STATE_FILE=".cursor/ralph-loop.scratchpad.md" + +if [[ ! -f "$RALPH_STATE_FILE" ]]; then + # No active loop - allow exit + exit 0 +fi + +# Parse markdown frontmatter (YAML between ---) and extract values +FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$RALPH_STATE_FILE") +ITERATION=$(echo "$FRONTMATTER" | grep '^iteration:' | sed 's/iteration: *//') +MAX_ITERATIONS=$(echo "$FRONTMATTER" | grep '^max_iterations:' | sed 's/max_iterations: *//') +# Extract completion_promise and strip surrounding quotes if present +COMPLETION_PROMISE=$(echo "$FRONTMATTER" | grep '^completion_promise:' | sed 's/completion_promise: *//' | sed 's/^"\(.*\)"$/\1/') + +# Validate numeric fields before arithmetic operations +if [[ ! "$ITERATION" =~ ^[0-9]+$ ]]; then + echo "Warning: Ralph loop state file corrupted" >&2 + echo " File: $RALPH_STATE_FILE" >&2 + echo " Problem: 'iteration' field is not a valid number (got: '$ITERATION')" >&2 + echo "" >&2 + echo " This usually means the state file was manually edited or corrupted." >&2 + echo " Ralph loop is stopping. Run /ralph-loop again to start fresh." >&2 + rm "$RALPH_STATE_FILE" + exit 0 +fi + +if [[ ! "$MAX_ITERATIONS" =~ ^[0-9]+$ ]]; then + echo "Warning: Ralph loop state file corrupted" >&2 + echo " File: $RALPH_STATE_FILE" >&2 + echo " Problem: 'max_iterations' field is not a valid number (got: '$MAX_ITERATIONS')" >&2 + echo "" >&2 + echo " This usually means the state file was manually edited or corrupted." >&2 + echo " Ralph loop is stopping. Run /ralph-loop again to start fresh." >&2 + rm "$RALPH_STATE_FILE" + exit 0 +fi + +# Check if max iterations reached +if [[ $MAX_ITERATIONS -gt 0 ]] && [[ $ITERATION -ge $MAX_ITERATIONS ]]; then + echo "Ralph loop: Max iterations ($MAX_ITERATIONS) reached." + rm "$RALPH_STATE_FILE" + exit 0 +fi + +# Get transcript path from hook input +TRANSCRIPT_PATH=$(echo "$HOOK_INPUT" | jq -r '.transcript_path') + +if [[ ! -f "$TRANSCRIPT_PATH" ]]; then + echo "Warning: Ralph loop transcript file not found" >&2 + echo " Expected: $TRANSCRIPT_PATH" >&2 + echo " This is unusual and may indicate an internal issue." >&2 + echo " Ralph loop is stopping." >&2 + rm "$RALPH_STATE_FILE" + exit 0 +fi + +# Read last assistant message from transcript (JSONL format - one JSON per line) +# First check if there are any assistant messages +if ! grep -q '"role":"assistant"' "$TRANSCRIPT_PATH"; then + echo "Warning: Ralph loop found no assistant messages in transcript" >&2 + echo " Transcript: $TRANSCRIPT_PATH" >&2 + echo " This is unusual and may indicate a transcript format issue" >&2 + echo " Ralph loop is stopping." >&2 + rm "$RALPH_STATE_FILE" + exit 0 +fi + +# Extract last assistant message with explicit error handling +LAST_LINE=$(grep '"role":"assistant"' "$TRANSCRIPT_PATH" | tail -1) +if [[ -z "$LAST_LINE" ]]; then + echo "Warning: Ralph loop failed to extract last assistant message" >&2 + echo " Ralph loop is stopping." >&2 + rm "$RALPH_STATE_FILE" + exit 0 +fi + +# Parse JSON with error handling +LAST_OUTPUT=$(echo "$LAST_LINE" | jq -r ' + .message.content | + map(select(.type == "text")) | + map(.text) | + join("\n") +' 2>&1) + +# Check if jq succeeded +if [[ $? -ne 0 ]]; then + echo "Warning: Ralph loop failed to parse assistant message JSON" >&2 + echo " Error: $LAST_OUTPUT" >&2 + echo " This may indicate a transcript format issue" >&2 + echo " Ralph loop is stopping." >&2 + rm "$RALPH_STATE_FILE" + exit 0 +fi + +if [[ -z "$LAST_OUTPUT" ]]; then + echo "Warning: Ralph loop assistant message contained no text content" >&2 + echo " Ralph loop is stopping." >&2 + rm "$RALPH_STATE_FILE" + exit 0 +fi + +# Check for completion promise (only if set) +if [[ "$COMPLETION_PROMISE" != "null" ]] && [[ -n "$COMPLETION_PROMISE" ]]; then + # Extract text from tags using Perl for multiline support + # -0777 slurps entire input, s flag makes . match newlines + # .*? is non-greedy (takes FIRST tag), whitespace normalized + PROMISE_TEXT=$(echo "$LAST_OUTPUT" | perl -0777 -pe 's/.*? (.*?)<\/promise>.*/$1/s; s/^\s+|\s+$//g; s/\s+/ /g' 2>/dev/null || echo "") + + # Use = for literal string comparison (not pattern matching) + # == in [[ ]] does glob pattern matching which breaks with *, ?, [ characters + if [[ -n "$PROMISE_TEXT" ]] && [[ "$PROMISE_TEXT" = "$COMPLETION_PROMISE" ]]; then + echo "Ralph loop: Detected $COMPLETION_PROMISE " + rm "$RALPH_STATE_FILE" + exit 0 + fi +fi + +# Not complete - continue loop with SAME PROMPT +NEXT_ITERATION=$((ITERATION + 1)) + +# Extract prompt (everything after the closing ---) +# Skip first --- line, skip until second --- line, then print everything after +# Use i>=2 instead of i==2 to handle --- in prompt content +PROMPT_TEXT=$(awk '/^---$/{i++; next} i>=2' "$RALPH_STATE_FILE") + +if [[ -z "$PROMPT_TEXT" ]]; then + echo "Warning: Ralph loop state file corrupted or incomplete" >&2 + echo " File: $RALPH_STATE_FILE" >&2 + echo " Problem: No prompt text found" >&2 + echo "" >&2 + echo " This usually means:" >&2 + echo " - State file was manually edited" >&2 + echo " - File was corrupted during writing" >&2 + echo "" >&2 + echo " Ralph loop is stopping. Run /ralph-loop again to start fresh." >&2 + rm "$RALPH_STATE_FILE" + exit 0 +fi + +# Update iteration in frontmatter (portable across macOS and Linux) +# Create temp file, then atomically replace +TEMP_FILE="${RALPH_STATE_FILE}.tmp.$$" +sed "s/^iteration: .*/iteration: $NEXT_ITERATION/" "$RALPH_STATE_FILE" > "$TEMP_FILE" +mv "$TEMP_FILE" "$RALPH_STATE_FILE" + +# Build system message with iteration count and completion promise info +if [[ "$COMPLETION_PROMISE" != "null" ]] && [[ -n "$COMPLETION_PROMISE" ]]; then + SYSTEM_MSG="Ralph iteration $NEXT_ITERATION | To stop: output $COMPLETION_PROMISE (ONLY when statement is TRUE - do not lie to exit!)" +else + SYSTEM_MSG="Ralph iteration $NEXT_ITERATION | No completion promise set - loop runs infinitely" +fi + +# Output JSON to block the stop and feed prompt back +# The "reason" field contains the prompt that will be sent back to the agent +jq -n \ + --arg prompt "$PROMPT_TEXT" \ + --arg msg "$SYSTEM_MSG" \ + '{ + "decision": "block", + "reason": $prompt, + "systemMessage": $msg + }' + +# Exit 0 for successful hook execution +exit 0 diff --git a/ralph-loop/scripts/setup-ralph-loop.sh b/ralph-loop/scripts/setup-ralph-loop.sh new file mode 100755 index 0000000..6475ea6 --- /dev/null +++ b/ralph-loop/scripts/setup-ralph-loop.sh @@ -0,0 +1,186 @@ +#!/bin/bash + +# Ralph Loop Setup Script +# Creates state file for in-session Ralph loop + +set -euo pipefail + +# Parse arguments +PROMPT_PARTS=() +MAX_ITERATIONS=0 +COMPLETION_PROMISE="null" + +# Parse options and positional arguments +while [[ $# -gt 0 ]]; do + case $1 in + -h|--help) + cat << 'HELP_EOF' +Ralph Loop - Interactive self-referential development loop + +USAGE: + /ralph-loop [PROMPT...] [OPTIONS] + +ARGUMENTS: + PROMPT... Initial prompt to start the loop (can be multiple words without quotes) + +OPTIONS: + --max-iterations Maximum iterations before auto-stop (default: unlimited) + --completion-promise Promise phrase (USE QUOTES for multi-word) + -h, --help Show this help message + +DESCRIPTION: + Starts a Ralph Loop in your CURRENT session. The stop hook prevents + exit and feeds your output back as input until completion or iteration limit. + + To signal completion, you must output: YOUR_PHRASE + + Use this for: + - Interactive iteration where you want to see progress + - Tasks requiring self-correction and refinement + - Learning how Ralph works + +EXAMPLES: + /ralph-loop Build a todo API --completion-promise 'DONE' --max-iterations 20 + /ralph-loop --max-iterations 10 Fix the auth bug + /ralph-loop Refactor cache layer (runs forever) + /ralph-loop --completion-promise 'TASK COMPLETE' Create a REST API + +STOPPING: + Only by reaching --max-iterations or detecting --completion-promise + No manual stop - Ralph runs infinitely by default! + +MONITORING: + # View current iteration: + grep '^iteration:' .cursor/ralph-loop.scratchpad.md + + # View full state: + head -10 .cursor/ralph-loop.scratchpad.md +HELP_EOF + exit 0 + ;; + --max-iterations) + if [[ -z "${2:-}" ]]; then + echo "Error: --max-iterations requires a number argument" >&2 + echo "" >&2 + echo " Valid examples:" >&2 + echo " --max-iterations 10" >&2 + echo " --max-iterations 50" >&2 + echo " --max-iterations 0 (unlimited)" >&2 + echo "" >&2 + echo " You provided: --max-iterations (with no number)" >&2 + exit 1 + fi + if ! [[ "$2" =~ ^[0-9]+$ ]]; then + echo "Error: --max-iterations must be a positive integer or 0, got: $2" >&2 + echo "" >&2 + echo " Valid examples:" >&2 + echo " --max-iterations 10" >&2 + echo " --max-iterations 50" >&2 + echo " --max-iterations 0 (unlimited)" >&2 + echo "" >&2 + echo " Invalid: decimals (10.5), negative numbers (-5), text" >&2 + exit 1 + fi + MAX_ITERATIONS="$2" + shift 2 + ;; + --completion-promise) + if [[ -z "${2:-}" ]]; then + echo "Error: --completion-promise requires a text argument" >&2 + echo "" >&2 + echo " Valid examples:" >&2 + echo " --completion-promise 'DONE'" >&2 + echo " --completion-promise 'TASK COMPLETE'" >&2 + echo " --completion-promise 'All tests passing'" >&2 + echo "" >&2 + echo " You provided: --completion-promise (with no text)" >&2 + echo "" >&2 + echo " Note: Multi-word promises must be quoted!" >&2 + exit 1 + fi + COMPLETION_PROMISE="$2" + shift 2 + ;; + *) + # Non-option argument - collect all as prompt parts + PROMPT_PARTS+=("$1") + shift + ;; + esac +done + +# Join all prompt parts with spaces +PROMPT="${PROMPT_PARTS[*]}" + +# Validate prompt is non-empty +if [[ -z "$PROMPT" ]]; then + echo "Error: No prompt provided" >&2 + echo "" >&2 + echo " Ralph needs a task description to work on." >&2 + echo "" >&2 + echo " Examples:" >&2 + echo " /ralph-loop Build a REST API for todos" >&2 + echo " /ralph-loop Fix the auth bug --max-iterations 20" >&2 + echo " /ralph-loop --completion-promise 'DONE' Refactor code" >&2 + echo "" >&2 + echo " For all options: /ralph-loop --help" >&2 + exit 1 +fi + +# Create state file for stop hook (markdown with YAML frontmatter) +mkdir -p .cursor + +# Quote completion promise for YAML if it contains special chars or is not null +if [[ -n "$COMPLETION_PROMISE" ]] && [[ "$COMPLETION_PROMISE" != "null" ]]; then + COMPLETION_PROMISE_YAML="\"$COMPLETION_PROMISE\"" +else + COMPLETION_PROMISE_YAML="null" +fi + +cat > .cursor/ralph-loop.scratchpad.md < $COMPLETION_PROMISE " + echo "" + echo "STRICT REQUIREMENTS (DO NOT VIOLATE):" + echo " - Use XML tags EXACTLY as shown above" + echo " - The statement MUST be completely and unequivocally TRUE" + echo " - Do NOT output false statements to exit the loop" + echo " - Do NOT lie even if you think you should exit" + echo "" + echo "IMPORTANT - Do not circumvent the loop:" + echo " Even if you believe you're stuck, the task is impossible," + echo " or you've been running too long - you MUST NOT output a" + echo " false promise statement. The loop is designed to continue" + echo " until the promise is GENUINELY TRUE. Trust the process." + echo "" + echo " If the loop should stop, the promise statement will become" + echo " true naturally. Do not force it by lying." + echo "=================================================" +fi diff --git a/ralph-loop/skills/cancel-ralph/SKILL.md b/ralph-loop/skills/cancel-ralph/SKILL.md new file mode 100644 index 0000000..39fdbbc --- /dev/null +++ b/ralph-loop/skills/cancel-ralph/SKILL.md @@ -0,0 +1,32 @@ +--- +name: cancel-ralph +description: Cancel an active Ralph Loop. Use when the user wants to stop, cancel, or abort a running ralph loop. +--- + +# Cancel Ralph + +## Trigger + +The user wants to cancel or stop an active Ralph loop. + +## Workflow + +1. Check if `.cursor/ralph-loop.scratchpad.md` exists: + + ```bash + test -f .cursor/ralph-loop.scratchpad.md && echo "EXISTS" || echo "NOT_FOUND" + ``` + +2. **If NOT_FOUND**: Tell the user "No active Ralph loop found." + +3. **If EXISTS**: + - Read `.cursor/ralph-loop.scratchpad.md` to get the current iteration number from the `iteration:` field in the YAML frontmatter. + - Remove the file: + ```bash + rm .cursor/ralph-loop.scratchpad.md + ``` + - Report: "Cancelled Ralph loop (was at iteration N)" where N is the iteration value. + +## Output + +A short confirmation message with the iteration count, or a message that no loop was active. diff --git a/ralph-loop/skills/ralph-loop-help/SKILL.md b/ralph-loop/skills/ralph-loop-help/SKILL.md new file mode 100644 index 0000000..ca456ca --- /dev/null +++ b/ralph-loop/skills/ralph-loop-help/SKILL.md @@ -0,0 +1,81 @@ +--- +name: ralph-loop-help +description: Explain the Ralph Loop plugin, how it works, and available skills. Use when the user asks for help with ralph loop, wants to understand the technique, or needs usage examples. +--- + +# Ralph Loop Help + +## Trigger + +The user asks what Ralph Loop is, how it works, or needs usage guidance. + +## What to Explain + +### What is Ralph Loop? + +Ralph Loop implements the Ralph Wiggum technique — an iterative development methodology based on continuous AI loops, pioneered by Geoffrey Huntley. + +Core concept: the same prompt is fed to the agent repeatedly. The "self-referential" aspect comes from the agent seeing its own previous work in the files and git history, not from feeding output back as input. + +Each iteration: +1. The agent receives the SAME prompt +2. Works on the task, modifying files +3. Tries to exit +4. Stop hook intercepts and feeds the same prompt again +5. The agent sees its previous work in the files +6. Iteratively improves until completion + +### Starting a Ralph Loop + +Tell the agent your task along with options: + +``` +Start a ralph loop: "Build a REST API for todos" --max-iterations 20 --completion-promise "COMPLETE" +``` + +Options: +- `--max-iterations N` — max iterations before auto-stop +- `--completion-promise "TEXT"` — phrase to signal completion + +How it works: +1. Creates `.cursor/ralph-loop.scratchpad.md` state file +2. Agent works on the task +3. Stop hook intercepts exit and feeds the same prompt back +4. Agent sees its previous work and iterates +5. Continues until promise detected or max iterations reached + +### Cancelling a Ralph Loop + +Ask the agent to cancel the ralph loop. It will remove the state file and report the iteration count. + +### Completion Promises + +To signal completion, the agent outputs a `` tag: + +``` +TASK COMPLETE +``` + +The stop hook looks for this specific tag. Without it (or `--max-iterations`), Ralph runs indefinitely. + +### When to Use Ralph + +**Good for:** +- Well-defined tasks with clear success criteria +- Tasks requiring iteration and refinement +- Iterative development with self-correction +- Greenfield projects + +**Not good for:** +- Tasks requiring human judgment or design decisions +- One-shot operations +- Tasks with unclear success criteria + +### Learn More + +- Original technique: https://ghuntley.com/ralph/ +- Ralph Orchestrator: https://github.com/mikeyobrien/ralph-orchestrator + +## Output + +Present the above information clearly to the user, tailored to their specific question. diff --git a/ralph-loop/skills/ralph-loop/SKILL.md b/ralph-loop/skills/ralph-loop/SKILL.md new file mode 100644 index 0000000..7c47263 --- /dev/null +++ b/ralph-loop/skills/ralph-loop/SKILL.md @@ -0,0 +1,39 @@ +--- +name: ralph-loop +description: Start a Ralph Loop for iterative self-referential development. Use when the user asks to run a ralph loop, start an iterative loop, or wants repeated autonomous iteration on a task until completion. +--- + +# Ralph Loop + +## Trigger + +The user wants to start a Ralph loop — an iterative development loop where the agent receives the same prompt repeatedly, seeing its own previous work each iteration, until a completion condition is met. + +## Workflow + +1. Gather the user's task prompt text and optional flags: + - `--max-iterations N` — stop after N iterations (default: unlimited) + - `--completion-promise "TEXT"` — phrase the agent must output inside `` tags when the task is genuinely complete + +2. Run the setup script with the collected arguments: + + ```bash + ./scripts/setup-ralph-loop.sh [--max-iterations N] [--completion-promise "TEXT"] + ``` + + This creates the state file at `.cursor/ralph-loop.scratchpad.md`. + +3. Begin working on the task described in the prompt. + +4. When the session tries to exit, the Stop hook (`hooks/stop-hook.sh`) intercepts and feeds the same prompt back automatically. + +## Guardrails + +- If a completion promise is set, you may ONLY output it when the statement is completely and unequivocally TRUE. +- Do not output false promises to escape the loop, even if you think you're stuck or should exit for other reasons. +- The loop is designed to continue until genuine completion. Trust the process. +- Always recommend `--max-iterations` as a safety net to prevent runaway loops. + +## Output + +After running the setup script, confirm to the user that the Ralph loop is active, then immediately begin working on the task. From e597b2ff6929a3d3214a09e330a69daa950ddd87 Mon Sep 17 00:00:00 2001 From: ericzakariasson Date: Wed, 11 Feb 2026 15:42:32 -0800 Subject: [PATCH 2/3] Comply with Cursor hooks API - Rewrite hooks.json to Cursor format (version 1, lowercase event names, flat structure, loop_limit: null) - Replace Claude Code stop hook output (decision/block/reason) with Cursor's followup_message API - Add afterAgentResponse hook to detect completion promise without depending on transcript format - Use CURSOR_PROJECT_DIR env var for reliable state file paths - Remove setup script; ralph-loop skill now instructs the agent to create the state file directly - Update cancel-ralph skill to clean up done flag file Co-authored-by: Cursor --- ralph-loop/README.md | 2 +- ralph-loop/hooks/capture-response.sh | 46 ++++++ ralph-loop/hooks/hooks.json | 17 ++- ralph-loop/hooks/stop-hook.sh | 181 ++++++----------------- ralph-loop/scripts/setup-ralph-loop.sh | 186 ------------------------ ralph-loop/skills/cancel-ralph/SKILL.md | 20 +-- ralph-loop/skills/ralph-loop/SKILL.md | 45 ++++-- 7 files changed, 139 insertions(+), 358 deletions(-) create mode 100755 ralph-loop/hooks/capture-response.sh delete mode 100755 ralph-loop/scripts/setup-ralph-loop.sh diff --git a/ralph-loop/README.md b/ralph-loop/README.md index b0fd81c..67a3f75 100644 --- a/ralph-loop/README.md +++ b/ralph-loop/README.md @@ -4,7 +4,7 @@ Ralph Loop runs Cursor in a self-referential loop, feeding the same prompt back ## How it works -A Stop hook intercepts each time Cursor tries to finish. Instead of ending the session, the hook feeds the original prompt back. Cursor sees its own previous edits in the working tree and git history, iterates on them, and repeats. The prompt never changes. The code does. +Two hooks drive the loop. An `afterAgentResponse` hook watches each response for a `` tag matching the completion phrase. A `stop` hook fires when Cursor finishes a turn. If the promise hasn't been detected and the iteration limit hasn't been reached, the stop hook sends the original prompt back as a `followup_message`, starting the next iteration. Cursor sees its own previous edits in the working tree and git history, iterates on them, and repeats. The prompt never changes. The code does. ## Installation diff --git a/ralph-loop/hooks/capture-response.sh b/ralph-loop/hooks/capture-response.sh new file mode 100755 index 0000000..412fc8a --- /dev/null +++ b/ralph-loop/hooks/capture-response.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +# afterAgentResponse hook for Ralph Loop. +# Checks if the agent's response contains a matching completion promise. +# If found, writes a done flag so the stop hook knows to end the loop. +# +# Input: { "text": "" } +# Output: none (fire-and-forget) + +set -euo pipefail + +HOOK_INPUT=$(cat) + +PROJECT_DIR="${CURSOR_PROJECT_DIR:-.}" +STATE_FILE="$PROJECT_DIR/.cursor/ralph-loop.scratchpad.md" +DONE_FLAG="$PROJECT_DIR/.cursor/ralph-loop-done" + +# No active loop, nothing to do +if [[ ! -f "$STATE_FILE" ]]; then + exit 0 +fi + +# Extract completion promise from state file frontmatter +FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$STATE_FILE") +COMPLETION_PROMISE=$(echo "$FRONTMATTER" | grep '^completion_promise:' | sed 's/completion_promise: *//' | sed 's/^"\(.*\)"$/\1/') + +# No promise configured, nothing to check +if [[ "$COMPLETION_PROMISE" = "null" ]] || [[ -z "$COMPLETION_PROMISE" ]]; then + exit 0 +fi + +# Extract response text from hook input +RESPONSE_TEXT=$(echo "$HOOK_INPUT" | jq -r '.text // empty') + +if [[ -z "$RESPONSE_TEXT" ]]; then + exit 0 +fi + +# Check for TEXT in the response +PROMISE_TEXT=$(echo "$RESPONSE_TEXT" | perl -0777 -pe 's/.*?(.*?)<\/promise>.*/$1/s; s/^\s+|\s+$//g; s/\s+/ /g' 2>/dev/null || echo "") + +if [[ -n "$PROMISE_TEXT" ]] && [[ "$PROMISE_TEXT" = "$COMPLETION_PROMISE" ]]; then + touch "$DONE_FLAG" +fi + +exit 0 diff --git a/ralph-loop/hooks/hooks.json b/ralph-loop/hooks/hooks.json index 9b1fd44..04a9966 100644 --- a/ralph-loop/hooks/hooks.json +++ b/ralph-loop/hooks/hooks.json @@ -1,14 +1,15 @@ { - "description": "Ralph Loop plugin stop hook for self-referential loops", + "version": 1, "hooks": { - "Stop": [ + "afterAgentResponse": [ { - "hooks": [ - { - "type": "command", - "command": "./hooks/stop-hook.sh" - } - ] + "command": "./hooks/capture-response.sh" + } + ], + "stop": [ + { + "command": "./hooks/stop-hook.sh", + "loop_limit": null } ] } diff --git a/ralph-loop/hooks/stop-hook.sh b/ralph-loop/hooks/stop-hook.sh index d826051..3b78da2 100755 --- a/ralph-loop/hooks/stop-hook.sh +++ b/ralph-loop/hooks/stop-hook.sh @@ -1,177 +1,86 @@ #!/bin/bash -# Ralph Loop Stop Hook -# Prevents session exit when a ralph-loop is active -# Feeds the agent's output back as input to continue the loop +# Ralph Loop stop hook. +# When the agent finishes a turn, this hook decides whether to feed the +# same prompt back for another iteration or let the session end. +# +# Cursor stop hook API: +# Input: { "status": "completed"|"aborted"|"error", "loop_count": N, ...common } +# Output: { "followup_message": "" } to continue, or exit 0 with no output to stop set -euo pipefail -# Read hook input from stdin (advanced stop hook API) HOOK_INPUT=$(cat) -# Check if ralph-loop is active -RALPH_STATE_FILE=".cursor/ralph-loop.scratchpad.md" +PROJECT_DIR="${CURSOR_PROJECT_DIR:-.}" +STATE_FILE="$PROJECT_DIR/.cursor/ralph-loop.scratchpad.md" +DONE_FLAG="$PROJECT_DIR/.cursor/ralph-loop-done" -if [[ ! -f "$RALPH_STATE_FILE" ]]; then - # No active loop - allow exit +# No active loop. Let the session end. +if [[ ! -f "$STATE_FILE" ]]; then exit 0 fi -# Parse markdown frontmatter (YAML between ---) and extract values -FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$RALPH_STATE_FILE") +# Parse state file frontmatter +FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$STATE_FILE") ITERATION=$(echo "$FRONTMATTER" | grep '^iteration:' | sed 's/iteration: *//') MAX_ITERATIONS=$(echo "$FRONTMATTER" | grep '^max_iterations:' | sed 's/max_iterations: *//') -# Extract completion_promise and strip surrounding quotes if present COMPLETION_PROMISE=$(echo "$FRONTMATTER" | grep '^completion_promise:' | sed 's/completion_promise: *//' | sed 's/^"\(.*\)"$/\1/') -# Validate numeric fields before arithmetic operations +# Validate iteration is numeric if [[ ! "$ITERATION" =~ ^[0-9]+$ ]]; then - echo "Warning: Ralph loop state file corrupted" >&2 - echo " File: $RALPH_STATE_FILE" >&2 - echo " Problem: 'iteration' field is not a valid number (got: '$ITERATION')" >&2 - echo "" >&2 - echo " This usually means the state file was manually edited or corrupted." >&2 - echo " Ralph loop is stopping. Run /ralph-loop again to start fresh." >&2 - rm "$RALPH_STATE_FILE" + echo "Ralph loop: state file corrupted (iteration: '$ITERATION'). Stopping." >&2 + rm -f "$STATE_FILE" "$DONE_FLAG" exit 0 fi if [[ ! "$MAX_ITERATIONS" =~ ^[0-9]+$ ]]; then - echo "Warning: Ralph loop state file corrupted" >&2 - echo " File: $RALPH_STATE_FILE" >&2 - echo " Problem: 'max_iterations' field is not a valid number (got: '$MAX_ITERATIONS')" >&2 - echo "" >&2 - echo " This usually means the state file was manually edited or corrupted." >&2 - echo " Ralph loop is stopping. Run /ralph-loop again to start fresh." >&2 - rm "$RALPH_STATE_FILE" + echo "Ralph loop: state file corrupted (max_iterations: '$MAX_ITERATIONS'). Stopping." >&2 + rm -f "$STATE_FILE" "$DONE_FLAG" exit 0 fi -# Check if max iterations reached -if [[ $MAX_ITERATIONS -gt 0 ]] && [[ $ITERATION -ge $MAX_ITERATIONS ]]; then - echo "Ralph loop: Max iterations ($MAX_ITERATIONS) reached." - rm "$RALPH_STATE_FILE" +# Check if completion promise was detected by the afterAgentResponse hook +if [[ -f "$DONE_FLAG" ]]; then + echo "Ralph loop: completion promise fulfilled at iteration $ITERATION." >&2 + rm -f "$STATE_FILE" "$DONE_FLAG" exit 0 fi -# Get transcript path from hook input -TRANSCRIPT_PATH=$(echo "$HOOK_INPUT" | jq -r '.transcript_path') - -if [[ ! -f "$TRANSCRIPT_PATH" ]]; then - echo "Warning: Ralph loop transcript file not found" >&2 - echo " Expected: $TRANSCRIPT_PATH" >&2 - echo " This is unusual and may indicate an internal issue." >&2 - echo " Ralph loop is stopping." >&2 - rm "$RALPH_STATE_FILE" +# Check max iterations +if [[ $MAX_ITERATIONS -gt 0 ]] && [[ $ITERATION -ge $MAX_ITERATIONS ]]; then + echo "Ralph loop: max iterations ($MAX_ITERATIONS) reached." >&2 + rm -f "$STATE_FILE" "$DONE_FLAG" exit 0 fi -# Read last assistant message from transcript (JSONL format - one JSON per line) -# First check if there are any assistant messages -if ! grep -q '"role":"assistant"' "$TRANSCRIPT_PATH"; then - echo "Warning: Ralph loop found no assistant messages in transcript" >&2 - echo " Transcript: $TRANSCRIPT_PATH" >&2 - echo " This is unusual and may indicate a transcript format issue" >&2 - echo " Ralph loop is stopping." >&2 - rm "$RALPH_STATE_FILE" - exit 0 -fi +# Extract prompt text (everything after the closing --- in frontmatter) +PROMPT_TEXT=$(awk '/^---$/{i++; next} i>=2' "$STATE_FILE") -# Extract last assistant message with explicit error handling -LAST_LINE=$(grep '"role":"assistant"' "$TRANSCRIPT_PATH" | tail -1) -if [[ -z "$LAST_LINE" ]]; then - echo "Warning: Ralph loop failed to extract last assistant message" >&2 - echo " Ralph loop is stopping." >&2 - rm "$RALPH_STATE_FILE" - exit 0 -fi - -# Parse JSON with error handling -LAST_OUTPUT=$(echo "$LAST_LINE" | jq -r ' - .message.content | - map(select(.type == "text")) | - map(.text) | - join("\n") -' 2>&1) - -# Check if jq succeeded -if [[ $? -ne 0 ]]; then - echo "Warning: Ralph loop failed to parse assistant message JSON" >&2 - echo " Error: $LAST_OUTPUT" >&2 - echo " This may indicate a transcript format issue" >&2 - echo " Ralph loop is stopping." >&2 - rm "$RALPH_STATE_FILE" +if [[ -z "$PROMPT_TEXT" ]]; then + echo "Ralph loop: no prompt text found in state file. Stopping." >&2 + rm -f "$STATE_FILE" "$DONE_FLAG" exit 0 fi -if [[ -z "$LAST_OUTPUT" ]]; then - echo "Warning: Ralph loop assistant message contained no text content" >&2 - echo " Ralph loop is stopping." >&2 - rm "$RALPH_STATE_FILE" - exit 0 -fi +# Increment iteration +NEXT_ITERATION=$((ITERATION + 1)) +TEMP_FILE="${STATE_FILE}.tmp.$$" +sed "s/^iteration: .*/iteration: $NEXT_ITERATION/" "$STATE_FILE" > "$TEMP_FILE" +mv "$TEMP_FILE" "$STATE_FILE" -# Check for completion promise (only if set) +# Build the followup message: iteration context + original prompt if [[ "$COMPLETION_PROMISE" != "null" ]] && [[ -n "$COMPLETION_PROMISE" ]]; then - # Extract text from tags using Perl for multiline support - # -0777 slurps entire input, s flag makes . match newlines - # .*? is non-greedy (takes FIRST tag), whitespace normalized - PROMISE_TEXT=$(echo "$LAST_OUTPUT" | perl -0777 -pe 's/.*? (.*?)<\/promise>.*/$1/s; s/^\s+|\s+$//g; s/\s+/ /g' 2>/dev/null || echo "") - - # Use = for literal string comparison (not pattern matching) - # == in [[ ]] does glob pattern matching which breaks with *, ?, [ characters - if [[ -n "$PROMISE_TEXT" ]] && [[ "$PROMISE_TEXT" = "$COMPLETION_PROMISE" ]]; then - echo "Ralph loop: Detected $COMPLETION_PROMISE " - rm "$RALPH_STATE_FILE" - exit 0 - fi + HEADER="[Ralph loop iteration $NEXT_ITERATION. To complete: output $COMPLETION_PROMISE ONLY when genuinely true.]" +else + HEADER="[Ralph loop iteration $NEXT_ITERATION.]" fi -# Not complete - continue loop with SAME PROMPT -NEXT_ITERATION=$((ITERATION + 1)) +FOLLOWUP="$HEADER -# Extract prompt (everything after the closing ---) -# Skip first --- line, skip until second --- line, then print everything after -# Use i>=2 instead of i==2 to handle --- in prompt content -PROMPT_TEXT=$(awk '/^---$/{i++; next} i>=2' "$RALPH_STATE_FILE") +$PROMPT_TEXT" -if [[ -z "$PROMPT_TEXT" ]]; then - echo "Warning: Ralph loop state file corrupted or incomplete" >&2 - echo " File: $RALPH_STATE_FILE" >&2 - echo " Problem: No prompt text found" >&2 - echo "" >&2 - echo " This usually means:" >&2 - echo " - State file was manually edited" >&2 - echo " - File was corrupted during writing" >&2 - echo "" >&2 - echo " Ralph loop is stopping. Run /ralph-loop again to start fresh." >&2 - rm "$RALPH_STATE_FILE" - exit 0 -fi - -# Update iteration in frontmatter (portable across macOS and Linux) -# Create temp file, then atomically replace -TEMP_FILE="${RALPH_STATE_FILE}.tmp.$$" -sed "s/^iteration: .*/iteration: $NEXT_ITERATION/" "$RALPH_STATE_FILE" > "$TEMP_FILE" -mv "$TEMP_FILE" "$RALPH_STATE_FILE" - -# Build system message with iteration count and completion promise info -if [[ "$COMPLETION_PROMISE" != "null" ]] && [[ -n "$COMPLETION_PROMISE" ]]; then - SYSTEM_MSG="Ralph iteration $NEXT_ITERATION | To stop: output $COMPLETION_PROMISE (ONLY when statement is TRUE - do not lie to exit!)" -else - SYSTEM_MSG="Ralph iteration $NEXT_ITERATION | No completion promise set - loop runs infinitely" -fi +# Output followup_message to continue the loop +jq -n --arg msg "$FOLLOWUP" '{"followup_message": $msg}' -# Output JSON to block the stop and feed prompt back -# The "reason" field contains the prompt that will be sent back to the agent -jq -n \ - --arg prompt "$PROMPT_TEXT" \ - --arg msg "$SYSTEM_MSG" \ - '{ - "decision": "block", - "reason": $prompt, - "systemMessage": $msg - }' - -# Exit 0 for successful hook execution exit 0 diff --git a/ralph-loop/scripts/setup-ralph-loop.sh b/ralph-loop/scripts/setup-ralph-loop.sh deleted file mode 100755 index 6475ea6..0000000 --- a/ralph-loop/scripts/setup-ralph-loop.sh +++ /dev/null @@ -1,186 +0,0 @@ -#!/bin/bash - -# Ralph Loop Setup Script -# Creates state file for in-session Ralph loop - -set -euo pipefail - -# Parse arguments -PROMPT_PARTS=() -MAX_ITERATIONS=0 -COMPLETION_PROMISE="null" - -# Parse options and positional arguments -while [[ $# -gt 0 ]]; do - case $1 in - -h|--help) - cat << 'HELP_EOF' -Ralph Loop - Interactive self-referential development loop - -USAGE: - /ralph-loop [PROMPT...] [OPTIONS] - -ARGUMENTS: - PROMPT... Initial prompt to start the loop (can be multiple words without quotes) - -OPTIONS: - --max-iterations Maximum iterations before auto-stop (default: unlimited) - --completion-promise Promise phrase (USE QUOTES for multi-word) - -h, --help Show this help message - -DESCRIPTION: - Starts a Ralph Loop in your CURRENT session. The stop hook prevents - exit and feeds your output back as input until completion or iteration limit. - - To signal completion, you must output: YOUR_PHRASE - - Use this for: - - Interactive iteration where you want to see progress - - Tasks requiring self-correction and refinement - - Learning how Ralph works - -EXAMPLES: - /ralph-loop Build a todo API --completion-promise 'DONE' --max-iterations 20 - /ralph-loop --max-iterations 10 Fix the auth bug - /ralph-loop Refactor cache layer (runs forever) - /ralph-loop --completion-promise 'TASK COMPLETE' Create a REST API - -STOPPING: - Only by reaching --max-iterations or detecting --completion-promise - No manual stop - Ralph runs infinitely by default! - -MONITORING: - # View current iteration: - grep '^iteration:' .cursor/ralph-loop.scratchpad.md - - # View full state: - head -10 .cursor/ralph-loop.scratchpad.md -HELP_EOF - exit 0 - ;; - --max-iterations) - if [[ -z "${2:-}" ]]; then - echo "Error: --max-iterations requires a number argument" >&2 - echo "" >&2 - echo " Valid examples:" >&2 - echo " --max-iterations 10" >&2 - echo " --max-iterations 50" >&2 - echo " --max-iterations 0 (unlimited)" >&2 - echo "" >&2 - echo " You provided: --max-iterations (with no number)" >&2 - exit 1 - fi - if ! [[ "$2" =~ ^[0-9]+$ ]]; then - echo "Error: --max-iterations must be a positive integer or 0, got: $2" >&2 - echo "" >&2 - echo " Valid examples:" >&2 - echo " --max-iterations 10" >&2 - echo " --max-iterations 50" >&2 - echo " --max-iterations 0 (unlimited)" >&2 - echo "" >&2 - echo " Invalid: decimals (10.5), negative numbers (-5), text" >&2 - exit 1 - fi - MAX_ITERATIONS="$2" - shift 2 - ;; - --completion-promise) - if [[ -z "${2:-}" ]]; then - echo "Error: --completion-promise requires a text argument" >&2 - echo "" >&2 - echo " Valid examples:" >&2 - echo " --completion-promise 'DONE'" >&2 - echo " --completion-promise 'TASK COMPLETE'" >&2 - echo " --completion-promise 'All tests passing'" >&2 - echo "" >&2 - echo " You provided: --completion-promise (with no text)" >&2 - echo "" >&2 - echo " Note: Multi-word promises must be quoted!" >&2 - exit 1 - fi - COMPLETION_PROMISE="$2" - shift 2 - ;; - *) - # Non-option argument - collect all as prompt parts - PROMPT_PARTS+=("$1") - shift - ;; - esac -done - -# Join all prompt parts with spaces -PROMPT="${PROMPT_PARTS[*]}" - -# Validate prompt is non-empty -if [[ -z "$PROMPT" ]]; then - echo "Error: No prompt provided" >&2 - echo "" >&2 - echo " Ralph needs a task description to work on." >&2 - echo "" >&2 - echo " Examples:" >&2 - echo " /ralph-loop Build a REST API for todos" >&2 - echo " /ralph-loop Fix the auth bug --max-iterations 20" >&2 - echo " /ralph-loop --completion-promise 'DONE' Refactor code" >&2 - echo "" >&2 - echo " For all options: /ralph-loop --help" >&2 - exit 1 -fi - -# Create state file for stop hook (markdown with YAML frontmatter) -mkdir -p .cursor - -# Quote completion promise for YAML if it contains special chars or is not null -if [[ -n "$COMPLETION_PROMISE" ]] && [[ "$COMPLETION_PROMISE" != "null" ]]; then - COMPLETION_PROMISE_YAML="\"$COMPLETION_PROMISE\"" -else - COMPLETION_PROMISE_YAML="null" -fi - -cat > .cursor/ralph-loop.scratchpad.md < $COMPLETION_PROMISE " - echo "" - echo "STRICT REQUIREMENTS (DO NOT VIOLATE):" - echo " - Use XML tags EXACTLY as shown above" - echo " - The statement MUST be completely and unequivocally TRUE" - echo " - Do NOT output false statements to exit the loop" - echo " - Do NOT lie even if you think you should exit" - echo "" - echo "IMPORTANT - Do not circumvent the loop:" - echo " Even if you believe you're stuck, the task is impossible," - echo " or you've been running too long - you MUST NOT output a" - echo " false promise statement. The loop is designed to continue" - echo " until the promise is GENUINELY TRUE. Trust the process." - echo "" - echo " If the loop should stop, the promise statement will become" - echo " true naturally. Do not force it by lying." - echo "=================================================" -fi diff --git a/ralph-loop/skills/cancel-ralph/SKILL.md b/ralph-loop/skills/cancel-ralph/SKILL.md index 39fdbbc..3f277b1 100644 --- a/ralph-loop/skills/cancel-ralph/SKILL.md +++ b/ralph-loop/skills/cancel-ralph/SKILL.md @@ -11,22 +11,18 @@ The user wants to cancel or stop an active Ralph loop. ## Workflow -1. Check if `.cursor/ralph-loop.scratchpad.md` exists: +1. Check if `.cursor/ralph-loop.scratchpad.md` exists. - ```bash - test -f .cursor/ralph-loop.scratchpad.md && echo "EXISTS" || echo "NOT_FOUND" - ``` +2. **If it does not exist**: Tell the user "No active Ralph loop found." -2. **If NOT_FOUND**: Tell the user "No active Ralph loop found." - -3. **If EXISTS**: - - Read `.cursor/ralph-loop.scratchpad.md` to get the current iteration number from the `iteration:` field in the YAML frontmatter. - - Remove the file: +3. **If it exists**: + - Read `.cursor/ralph-loop.scratchpad.md` to get the current iteration from the `iteration:` field. + - Remove the state file and any done flag: ```bash - rm .cursor/ralph-loop.scratchpad.md + rm -f .cursor/ralph-loop.scratchpad.md .cursor/ralph-loop-done ``` - - Report: "Cancelled Ralph loop (was at iteration N)" where N is the iteration value. + - Report: "Cancelled Ralph loop (was at iteration N)." ## Output -A short confirmation message with the iteration count, or a message that no loop was active. +A short confirmation with the iteration count, or a message that no loop was active. diff --git a/ralph-loop/skills/ralph-loop/SKILL.md b/ralph-loop/skills/ralph-loop/SKILL.md index 7c47263..eb83639 100644 --- a/ralph-loop/skills/ralph-loop/SKILL.md +++ b/ralph-loop/skills/ralph-loop/SKILL.md @@ -7,33 +7,48 @@ description: Start a Ralph Loop for iterative self-referential development. Use ## Trigger -The user wants to start a Ralph loop — an iterative development loop where the agent receives the same prompt repeatedly, seeing its own previous work each iteration, until a completion condition is met. +The user wants to start a Ralph loop. An iterative development loop where the same prompt is fed back after every turn, and the agent sees its own previous work each iteration. ## Workflow -1. Gather the user's task prompt text and optional flags: - - `--max-iterations N` — stop after N iterations (default: unlimited) - - `--completion-promise "TEXT"` — phrase the agent must output inside `` tags when the task is genuinely complete +1. Gather the user's task prompt and optional parameters: + - `max_iterations` (number, default 0 for unlimited) + - `completion_promise` (text, or "null" if not set) -2. Run the setup script with the collected arguments: +2. Create the state file at `.cursor/ralph-loop.scratchpad.md` with this exact format: - ```bash - ./scripts/setup-ralph-loop.sh [--max-iterations N] [--completion-promise "TEXT"] + ```markdown + --- + iteration: 1 + max_iterations: + completion_promise: "" or null + --- + + ``` - This creates the state file at `.cursor/ralph-loop.scratchpad.md`. + Example: + ```markdown + --- + iteration: 1 + max_iterations: 20 + completion_promise: "COMPLETE" + --- + + Build a REST API for todos with CRUD operations, input validation, and tests. + ``` -3. Begin working on the task described in the prompt. +3. Confirm to the user that the Ralph loop is active, then begin working on the task. -4. When the session tries to exit, the Stop hook (`hooks/stop-hook.sh`) intercepts and feeds the same prompt back automatically. +4. The stop hook automatically intercepts each turn end and feeds the same prompt back as a followup message. You will see it prefixed with `[Ralph loop iteration N.]`. ## Guardrails -- If a completion promise is set, you may ONLY output it when the statement is completely and unequivocally TRUE. -- Do not output false promises to escape the loop, even if you think you're stuck or should exit for other reasons. -- The loop is designed to continue until genuine completion. Trust the process. -- Always recommend `--max-iterations` as a safety net to prevent runaway loops. +- If a completion promise is set, you may ONLY output `TEXT` when the statement is completely and genuinely true. +- Do not output false promises to escape the loop. +- Always recommend setting `max_iterations` as a safety net. +- Quote the `completion_promise` value in the YAML frontmatter if it contains special characters. ## Output -After running the setup script, confirm to the user that the Ralph loop is active, then immediately begin working on the task. +Confirm the loop is active (prompt, iteration limit, promise if set), then start working on the task immediately. From 1cb26ef1caebb72f5ef5d69379954a4183e90e6b Mon Sep 17 00:00:00 2001 From: ericzakariasson Date: Wed, 11 Feb 2026 15:46:32 -0800 Subject: [PATCH 3/3] Move state files to .cursor/ralph/ subdirectory State file: .cursor/ralph/scratchpad.md Done flag: .cursor/ralph/done Cancel cleans up with rm -rf .cursor/ralph Co-authored-by: Cursor --- ralph-loop/hooks/capture-response.sh | 4 ++-- ralph-loop/hooks/stop-hook.sh | 4 ++-- ralph-loop/skills/cancel-ralph/SKILL.md | 6 +++--- ralph-loop/skills/ralph-loop-help/SKILL.md | 2 +- ralph-loop/skills/ralph-loop/SKILL.md | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/ralph-loop/hooks/capture-response.sh b/ralph-loop/hooks/capture-response.sh index 412fc8a..d8da50a 100755 --- a/ralph-loop/hooks/capture-response.sh +++ b/ralph-loop/hooks/capture-response.sh @@ -12,8 +12,8 @@ set -euo pipefail HOOK_INPUT=$(cat) PROJECT_DIR="${CURSOR_PROJECT_DIR:-.}" -STATE_FILE="$PROJECT_DIR/.cursor/ralph-loop.scratchpad.md" -DONE_FLAG="$PROJECT_DIR/.cursor/ralph-loop-done" +STATE_FILE="$PROJECT_DIR/.cursor/ralph/scratchpad.md" +DONE_FLAG="$PROJECT_DIR/.cursor/ralph/done" # No active loop, nothing to do if [[ ! -f "$STATE_FILE" ]]; then diff --git a/ralph-loop/hooks/stop-hook.sh b/ralph-loop/hooks/stop-hook.sh index 3b78da2..ba0ba34 100755 --- a/ralph-loop/hooks/stop-hook.sh +++ b/ralph-loop/hooks/stop-hook.sh @@ -13,8 +13,8 @@ set -euo pipefail HOOK_INPUT=$(cat) PROJECT_DIR="${CURSOR_PROJECT_DIR:-.}" -STATE_FILE="$PROJECT_DIR/.cursor/ralph-loop.scratchpad.md" -DONE_FLAG="$PROJECT_DIR/.cursor/ralph-loop-done" +STATE_FILE="$PROJECT_DIR/.cursor/ralph/scratchpad.md" +DONE_FLAG="$PROJECT_DIR/.cursor/ralph/done" # No active loop. Let the session end. if [[ ! -f "$STATE_FILE" ]]; then diff --git a/ralph-loop/skills/cancel-ralph/SKILL.md b/ralph-loop/skills/cancel-ralph/SKILL.md index 3f277b1..1e75c5b 100644 --- a/ralph-loop/skills/cancel-ralph/SKILL.md +++ b/ralph-loop/skills/cancel-ralph/SKILL.md @@ -11,15 +11,15 @@ The user wants to cancel or stop an active Ralph loop. ## Workflow -1. Check if `.cursor/ralph-loop.scratchpad.md` exists. +1. Check if `.cursor/ralph/scratchpad.md` exists. 2. **If it does not exist**: Tell the user "No active Ralph loop found." 3. **If it exists**: - - Read `.cursor/ralph-loop.scratchpad.md` to get the current iteration from the `iteration:` field. + - Read `.cursor/ralph/scratchpad.md` to get the current iteration from the `iteration:` field. - Remove the state file and any done flag: ```bash - rm -f .cursor/ralph-loop.scratchpad.md .cursor/ralph-loop-done + rm -rf .cursor/ralph ``` - Report: "Cancelled Ralph loop (was at iteration N)." diff --git a/ralph-loop/skills/ralph-loop-help/SKILL.md b/ralph-loop/skills/ralph-loop-help/SKILL.md index ca456ca..8e6a76a 100644 --- a/ralph-loop/skills/ralph-loop-help/SKILL.md +++ b/ralph-loop/skills/ralph-loop-help/SKILL.md @@ -38,7 +38,7 @@ Options: - `--completion-promise "TEXT"` — phrase to signal completion How it works: -1. Creates `.cursor/ralph-loop.scratchpad.md` state file +1. Creates `.cursor/ralph/scratchpad.md` state file 2. Agent works on the task 3. Stop hook intercepts exit and feeds the same prompt back 4. Agent sees its previous work and iterates diff --git a/ralph-loop/skills/ralph-loop/SKILL.md b/ralph-loop/skills/ralph-loop/SKILL.md index eb83639..5c94318 100644 --- a/ralph-loop/skills/ralph-loop/SKILL.md +++ b/ralph-loop/skills/ralph-loop/SKILL.md @@ -15,7 +15,7 @@ The user wants to start a Ralph loop. An iterative development loop where the sa - `max_iterations` (number, default 0 for unlimited) - `completion_promise` (text, or "null" if not set) -2. Create the state file at `.cursor/ralph-loop.scratchpad.md` with this exact format: +2. Create the directory `.cursor/ralph/` if it doesn't exist, then write the state file at `.cursor/ralph/scratchpad.md` with this exact format: ```markdown ---