Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .cursor-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -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."
}
]
}
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
29 changes: 29 additions & 0 deletions ralph-loop/.cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -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"
}
21 changes: 21 additions & 0 deletions ralph-loop/LICENSE
Original file line number Diff line number Diff line change
@@ -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.
61 changes: 61 additions & 0 deletions ralph-loop/README.md
Original file line number Diff line number Diff line change
@@ -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

Two hooks drive the loop. An `afterAgentResponse` hook watches each response for a `<promise>` 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

```
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 <N>` stops after N iterations (default: unlimited)
- `--completion-promise <text>` 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: <promise>COMPLETE</promise>
```

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
46 changes: 46 additions & 0 deletions ralph-loop/hooks/capture-response.sh
Original file line number Diff line number Diff line change
@@ -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": "<assistant response text>" }
# Output: none (fire-and-forget)

set -euo pipefail

HOOK_INPUT=$(cat)

PROJECT_DIR="${CURSOR_PROJECT_DIR:-.}"
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
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 <promise>TEXT</promise> in the response
PROMISE_TEXT=$(echo "$RESPONSE_TEXT" | perl -0777 -pe 's/.*?<promise>(.*?)<\/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
16 changes: 16 additions & 0 deletions ralph-loop/hooks/hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"version": 1,
"hooks": {
"afterAgentResponse": [
{
"command": "./hooks/capture-response.sh"
}
],
"stop": [
{
"command": "./hooks/stop-hook.sh",
"loop_limit": null
}
]
}
}
86 changes: 86 additions & 0 deletions ralph-loop/hooks/stop-hook.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#!/bin/bash

# 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": "<text>" } to continue, or exit 0 with no output to stop

set -euo pipefail

HOOK_INPUT=$(cat)

PROJECT_DIR="${CURSOR_PROJECT_DIR:-.}"
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
exit 0
fi

# 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: *//')
COMPLETION_PROMISE=$(echo "$FRONTMATTER" | grep '^completion_promise:' | sed 's/completion_promise: *//' | sed 's/^"\(.*\)"$/\1/')

# Validate iteration is numeric
if [[ ! "$ITERATION" =~ ^[0-9]+$ ]]; then
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 "Ralph loop: state file corrupted (max_iterations: '$MAX_ITERATIONS'). Stopping." >&2
rm -f "$STATE_FILE" "$DONE_FLAG"
exit 0
fi

# 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

# 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

# Extract prompt text (everything after the closing --- in frontmatter)
PROMPT_TEXT=$(awk '/^---$/{i++; next} i>=2' "$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

# 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"

# Build the followup message: iteration context + original prompt
if [[ "$COMPLETION_PROMISE" != "null" ]] && [[ -n "$COMPLETION_PROMISE" ]]; then
HEADER="[Ralph loop iteration $NEXT_ITERATION. To complete: output <promise>$COMPLETION_PROMISE</promise> ONLY when genuinely true.]"
else
HEADER="[Ralph loop iteration $NEXT_ITERATION.]"
fi

FOLLOWUP="$HEADER

$PROMPT_TEXT"

# Output followup_message to continue the loop
jq -n --arg msg "$FOLLOWUP" '{"followup_message": $msg}'

exit 0
28 changes: 28 additions & 0 deletions ralph-loop/skills/cancel-ralph/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
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/scratchpad.md` exists.

2. **If it does not exist**: Tell the user "No active Ralph loop found."

3. **If it exists**:
- Read `.cursor/ralph/scratchpad.md` to get the current iteration from the `iteration:` field.
- Remove the state file and any done flag:
```bash
rm -rf .cursor/ralph
```
- Report: "Cancelled Ralph loop (was at iteration N)."

## Output

A short confirmation with the iteration count, or a message that no loop was active.
81 changes: 81 additions & 0 deletions ralph-loop/skills/ralph-loop-help/SKILL.md
Original file line number Diff line number Diff line change
@@ -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/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 `<promise>` tag:

```
<promise>TASK COMPLETE</promise>
```

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.
Loading