Skip to content
RALPH LOOP

The Ralph Loop Shell Script: How ralph.sh Works and How to Run It

A green CRT terminal showing a Bash shell script that loops an AI coding agent, with ralph.sh named at the top

The Ralph loop shell script is ralph.sh, a single Bash file that runs an AI coding agent in a loop until your task list is done. It is open source. You can read the docs at ralphloop.sh and the full source at github.com/pageai-pro/ralph-loop. You install it into a project with one command, point it at a task list, and let it grind.

This post is about the script itself. What it is, how to get it, the flags that drive it, what it does on each pass, and why it is a shell script you can read top to bottom instead of a framework you have to trust. If you want the concept behind it first, read what the Ralph technique is and come back.

ralph.sh is the practical implementation of the Ralph technique. The technique was popularized by Geoffrey Huntley with one line: Ralph is a Bash loop. The script keeps that idea and adds the parts a real run needs: a task system, a verification gate, a stop condition, a choice of agent backends, and a sandbox boundary.

The whole thing stays a shell script on purpose. There is no orchestration layer, no daemon, no hidden state. When the loop does something you did not expect, you open one file and read it. That is the trade the project makes, and it is the reason the script exists at all when an official Claude Code plugin already does a similar job.

Here is the mental model in two layers. The bare idea is a loop that pipes a prompt into an agent. The script wraps that idea with the machinery that makes it safe to walk away from.

Terminal window
# the bare idea
while :; do
cat PROMPT.md | your-agent-cli
done
Terminal window
# the real script, in one line
./ralph.sh -n 50

Both feed the same prompt to a fresh agent on every pass. The difference is everything the second line does around that: caps the iterations, isolates the run, tracks tasks, and stops on a real signal.

Run one command in your project’s root. It scaffolds the .agent/ directory and drops ralph.sh into the repo.

Terminal window
npx @pageai/ralph-loop

The installer asks a few questions (your dev server URL and port, your agent of choice) and writes a default implementation-mode setup. After that the script lives in your repo as a normal file. You can read it, edit it, and commit it alongside the rest of your code.

If the file is not executable yet, make it so:

Terminal window
chmod +x ralph.sh

That is the entire install. No global binary, no background service. The script and its state files sit in your project, version controlled like anything else.

The script takes a small set of flags. The defaults are sane, so the shortest run is just the script with no arguments, which runs ten iterations.

Terminal window
# default: 10 iterations
./ralph.sh
# set an explicit cap (these are equivalent)
./ralph.sh 50
./ralph.sh -n 50
./ralph.sh --max-iterations 50
# run exactly one iteration to sanity check the setup
./ralph.sh --once

The iteration cap is a safety budget, not a target. The loop stops early the moment the work is actually done. The cap only matters if the agent runs out of budget before finishing, which is a signal to read the log and decide whether to top it up.

The script is agent agnostic. Claude Code is the default. Switch to another supported CLI with --agent or its short form -a.

Terminal window
./ralph.sh --agent codex
./ralph.sh -a cursor -n 5

Supported agents are claude, codex, copilot, cursor, gemini, and opencode. To pass flags straight through to the underlying agent, put them after a -- separator so the script does not try to parse them:

Terminal window
./ralph.sh --agent codex -- --model gpt-5.5
./ralph.sh -a gemini -- --model pro

Three more flags handle setup around the sandbox. Authenticate the agent inside its sandbox, publish your dev port to the host, or print the deterministic sandbox name without starting a run.

Terminal window
./ralph.sh --login
./ralph.sh --login --agent cursor
./ralph.sh --ports
./ralph.sh --print-name
./ralph.sh --print-name --agent codex

The sandbox name follows the pattern ralph-<agent>-<current-dir>-<hash8>, for example ralph-claude-my-app-a1b2c3d4. Because the agent name is part of the sandbox name, switching agents gives each one its own sandbox for the same project.

What does the script do on each iteration?

Section titled “What does the script do on each iteration?”

Every pass follows the same shape. The agent boots with a clean context, reorients from files on disk, does exactly one task, proves it works, and commits. Then the loop runs the agent again.

flowchart TD
    Start(["./ralph.sh -n 50"]) --> Read["Read .agent/tasks.json"]
    Read --> Pick{"Incomplete task left?"}
    Pick -->|"No"| Done["Emit COMPLETE, exit 0"]
    Pick -->|"Yes"| Fresh["Spawn agent with fresh context in the sandbox"]
    Fresh --> Spec["Load TASK-ID.json steps"]
    Spec --> Work["Implement the one task"]
    Work --> Verify["Run tests, lint, typecheck"]
    Verify --> Pass{"All checks pass?"}
    Pass -->|"No"| Work
    Pass -->|"Yes"| Commit["Screenshot, set passes:true, commit"]
    Commit --> Limit{"Max iterations reached?"}
    Limit -->|"No"| Read
    Limit -->|"Yes"| MaxOut["Exit 1"]

In words, the five steps are:

  1. Find the highest-priority incomplete task in .agent/tasks.json.
  2. Work the steps in .agent/tasks/TASK-{ID}.json.
  3. Run tests, linting, and type checking.
  4. Complete the task, take a screenshot, update the task status, and commit.
  5. Repeat until every task passes or the iteration cap is reached.

The verification step in the middle is what makes the loop worth trusting. The agent does not get to call a task done on a vibe. The repo assumes a stack of Playwright for end to end tests, Vitest for unit tests, TypeScript for types, ESLint for linting, and Prettier for formatting. The mantra from the repo is blunt: if you didn’t test it, it doesn’t work.

The prompt that drives every pass lives in .agent/PROMPT.md. The script re-sends that same file on each iteration, so the prompt has to make a fresh agent reorient and act within seconds. Writing it well is its own skill, covered in how to write the PROMPT.md file.

ralph.sh reads and writes a small set of files under .agent/. The script is the engine; these files are its memory. A fresh agent remembers nothing between passes, so progress has to live on disk.

.agent/
├── PROMPT.md # Prompt sent to the agent each iteration
├── tasks.json # Task lookup table (required)
├── tasks/ # Individual task specs (TASK-{ID}.json)
├── prd/
│ ├── PRD.md # Product requirements document
│ └── SUMMARY.md # Short project overview sent each iteration
├── logs/
│ └── LOG.md # Progress log
├── history/ # Per-iteration output logs
├── skills/ # Shared skills
└── STEERING.md # Critical work injected mid-run

The task lookup table in tasks.json is a lightweight index. The agent reads it to pick the next task without loading every spec, which is what lets a project hold hundreds of tasks and still keep each iteration cheap. Each task has a detailed spec in tasks/TASK-{ID}.json with steps and acceptance criteria.

You can steer a run that is already going by editing .agent/STEERING.md. The agent checks that file on each pass and handles any critical work there before resuming the normal task order, so you can correct course without killing the loop.

A loop that never ends is a money fire, so the script stops on explicit signals rather than guesses. There are two layers. The agent emits a promise tag, and the script returns an exit code.

The promise tags are small semantic statuses the agent prints:

<promise>COMPLETE</promise> all tasks finished, exit the loop
<promise>BLOCKED:reason</promise> needs human help
<promise>DECIDE:question</promise> needs a decision

Those tags map to exit codes, which is what makes the script easy to wire into CI or a cron job:

Terminal window
./ralph.sh -n 50
echo $? # 0 COMPLETE, 1 MAX_ITERATIONS, 2 BLOCKED, 3 DECIDE

Exit code 1 is not a failure. It means the loop spent its iteration budget with work still pending, which is exactly what a safety cap is for. You read the log, top up the budget, and run it again. The full design of how a loop knows it is done lives in completion promises and exit codes.

Three reasons, and they all come back to control.

First, you can read it. A shell script you can review in one sitting is a script you can debug at 2 in the morning. When a run does something surprising, you open ralph.sh and trace exactly when it commits, how it caps iterations, how it names the sandbox, and how it reads the completion promise. That is much harder with a closed plugin.

Second, you can hack it. The script is meant to be edited. The default mode is implementation, but the behavior lives in .agent/PROMPT.md, so you can point the same loop at a refactor, a review, or a test backfill by editing one file. The script machinery does not change.

Third, it is honest about its dependencies. The verification stack, the agent CLI, the sandbox, all of it is visible in the script and the prompt. Nothing is doing work you cannot see. For the longer argument about why this beats firing one giant prompt and hoping, read Ralph loop vs one-shot prompting, and for the origin of the one-liner the script grew from, see who invented the Ralph technique.

The script runs each agent inside an isolated Docker Sandbox microVM using the standalone sbx CLI. That isolation is what lets the loop run in bypass-permissions mode, often called YOLO mode, without putting your real machine at risk. The sandbox is the boundary, not the agent’s good behavior. See the Docker Sandboxes docs for the isolation model.

If you need to look inside while a run is going, list the sandboxes and shell into the one you want:

Terminal window
sbx ls
sbx exec -it ralph-claude-my-app-a1b2c3d4 bash

From there you have a normal container shell. You can install packages, run commands, or start the agent CLI by hand to reproduce what the loop is doing.

Putting it together, the shortest path from nothing to a running loop is four commands. Install, create a task list, test one pass, then let it run.

Terminal window
# 1. install the script and scaffolding
npx @pageai/ralph-loop
# 2. (in your editor, plan mode) use the prd-creator skill to turn
# requirements into .agent/prd/PRD.md and task specs
# 3. confirm the setup works end to end
./ralph.sh --once
# 4. let it run for a real batch
./ralph.sh -n 50

The quality of step 2 decides almost everything. The script executes a task list well, but it does not write the task list for you. A vague task list produces a loop that thrashes. A clear one with acceptance criteria produces commits you can trust. Once the script is working, keeping it productive across a long unattended run is its own discipline, covered in how to run an AI coding agent overnight.

Frequently asked questions

What is the Ralph loop shell script?

It is ralph.sh, a single Bash file that runs an AI coding agent in a loop until your task list is done. It implements the Ralph technique with a task system, a verification gate, completion promises, a choice of agent backends, and a Docker sandbox. The source is open at github.com/pageai-pro/ralph-loop and the docs are at ralphloop.sh.

How do I install ralph.sh?

Run npx @pageai/ralph-loop in your project root. It scaffolds the .agent directory and drops ralph.sh into the repo with a default implementation-mode setup. If the file is not executable, run chmod +x ralph.sh. The script and its state files are version controlled like the rest of your code.

How do I run the Ralph loop script?

Run ./ralph.sh for the default ten iterations, or ./ralph.sh -n 50 to set a cap. Use ./ralph.sh --once to run a single pass. Choose an agent with --agent (claude, codex, copilot, cursor, gemini, or opencode) and pass agent flags after a -- separator.

Is ralph.sh just a while loop?

At its core it is the same idea as a shell while loop that feeds a prompt to a fresh agent each pass. The script adds the parts a real run needs: an iteration cap, a task lookup table, a verification gate, promise tags, exit codes, and a sandbox boundary. You can still read the whole thing as a single shell file.

Can I edit the script for my own stack?

Yes, that is the point. The script is hackable and the loop behavior lives in .agent/PROMPT.md, so you change what the loop does by editing the prompt rather than the script. You can also adjust the verification commands, ports, and startup commands in the prompt to match your language and framework.