<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Ralph Loop | Ralph Loop Blog</title><description>A long-running AI agent loop that codes for days.</description><link>https://ralphloop.sh/</link><language>en</language><item><title>How to Run a Ralph Loop With the Cursor CLI</title><link>https://ralphloop.sh/blog/ralph-loop-with-cursor-cli/</link><guid isPermaLink="true">https://ralphloop.sh/blog/ralph-loop-with-cursor-cli/</guid><description>A full end-to-end setup for running the Ralph loop with the Cursor CLI: install, create a task list, log in inside the sandbox, run cursor-agent in a loop, and review the commits in the morning.

</description><pubDate>Mon, 29 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;To run a Ralph loop with the Cursor CLI, install Ralph into your project, create a task list, log in to Cursor once inside its sandbox, then run &lt;code dir=&quot;auto&quot;&gt;./ralph.sh --agent cursor&lt;/code&gt;. Ralph wraps the headless &lt;code dir=&quot;auto&quot;&gt;cursor-agent&lt;/code&gt; in print mode, gives it a fresh context window every iteration, and keeps re-running it against your task list until the work is done or you hit the iteration cap.&lt;/p&gt;
&lt;p&gt;This is the full setup walkthrough, from an empty repo to a finished diff. If you are new to the loop itself, start with &lt;a href=&quot;https://ralphloop.sh/blog/what-is-the-ralph-technique/&quot;&gt;what the Ralph technique is&lt;/a&gt;. If you want the flag-level mechanics of the Cursor backend, the companion piece is &lt;a href=&quot;https://ralphloop.sh/blog/run-cursor-cli-agent-loop/&quot;&gt;running the Cursor CLI agent in a loop&lt;/a&gt;. This post is the end-to-end recipe.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-pair-ralph-with-the-cursor-cli&quot;&gt;Why pair Ralph with the Cursor CLI?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Cursor ships a headless agent, &lt;code dir=&quot;auto&quot;&gt;cursor-agent&lt;/code&gt;, that runs from the terminal with no editor attached. In print mode (&lt;code dir=&quot;auto&quot;&gt;-p&lt;/code&gt;) it reads a prompt, edits files, runs shell commands, prints a final message, and exits. That exit is the hook Ralph needs. Each iteration is a discrete unit: spawn the agent, let it do one task, read what it emitted, repeat.&lt;/p&gt;
&lt;p&gt;Ralph supplies the three things a single &lt;code dir=&quot;auto&quot;&gt;cursor-agent&lt;/code&gt; call cannot:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A loop, so the agent gets another pass after every commit instead of stopping.&lt;/li&gt;
&lt;li&gt;Fresh context per iteration, so a long run never rots into a confused, drifting session.&lt;/li&gt;
&lt;li&gt;A Docker sandbox boundary, so the agent can edit files and run commands in YOLO mode without that blast radius reaching your real machine.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The state lives on disk (the task list, the logs, the git history), not in a chat transcript. That is what lets a stateless &lt;code dir=&quot;auto&quot;&gt;cursor-agent&lt;/code&gt; reorient on every pass and still make forward progress across dozens of iterations.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-1-install-ralph&quot;&gt;Step 1: Install Ralph&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Run the installer in your project root. It drops the &lt;code dir=&quot;auto&quot;&gt;ralph.sh&lt;/code&gt; script and the &lt;code dir=&quot;auto&quot;&gt;.agent/&lt;/code&gt; directory into the repo.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@pageai/ralph-loop&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The installer asks for your dev server URL and port and your default agent, then writes a default implementation-mode setup. Pick &lt;code dir=&quot;auto&quot;&gt;cursor&lt;/code&gt; when prompted, or set it per run with &lt;code dir=&quot;auto&quot;&gt;--agent cursor&lt;/code&gt; later. After install, make the script executable if it is not already:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;chmod&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+x&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph.sh&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Ralph runs each agent inside a Docker Sandbox, so install and sign in to Docker Sandbox first. See the &lt;a href=&quot;https://docs.docker.com/ai/sandboxes/&quot;&gt;Docker Sandboxes docs&lt;/a&gt; for the official setup flow. The loop drives the standalone &lt;code dir=&quot;auto&quot;&gt;sbx&lt;/code&gt; CLI under the hood.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-2-create-a-prd-and-task-list&quot;&gt;Step 2: Create a PRD and task list&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The loop executes a task list. It does not invent one. Before you run anything, turn your requirements into a PRD and a set of task specs with the &lt;code dir=&quot;auto&quot;&gt;prd-creator&lt;/code&gt; skill.&lt;/p&gt;
&lt;p&gt;Open Cursor (or any agent) in plan mode and prompt it with your requirements:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Use the prd-creator skill to help me create a PRD and task list for the below requirements.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;An app is already set up with React, Tailwind CSS and TypeScript.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Requirements:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- A SaaS product that helps users manage their finances.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- Track income and expenses, create invoices, generate reports.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- Use shadcn/ui for components and Supabase for the database.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// etc.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The skill writes &lt;code dir=&quot;auto&quot;&gt;.agent/prd/PRD.md&lt;/code&gt;, a short &lt;code dir=&quot;auto&quot;&gt;.agent/prd/SUMMARY.md&lt;/code&gt;, and per-task specs under &lt;code dir=&quot;auto&quot;&gt;.agent/tasks/&lt;/code&gt;, indexed by &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt;. Review each task individually before you start. A clear task list with real acceptance criteria is the single biggest factor in whether the loop succeeds. The deeper version of that workflow is in &lt;a href=&quot;https://ralphloop.sh/blog/spec-driven-development-with-ai/&quot;&gt;spec-driven development with AI&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-3-log-in-to-cursor-inside-the-sandbox&quot;&gt;Step 3: Log in to Cursor inside the sandbox&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Cursor runs inside an isolated sandbox, not on your host, so it needs credentials in that environment. Authenticate once with the login action:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--login&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cursor&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Ralph prints the login command for every supported agent, highlights the Cursor one, and drops you into the sandbox shell. The command it runs to create the box looks like this:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-cursor-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cursor&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Inside the sandbox, authenticate Cursor once. Run &lt;code dir=&quot;auto&quot;&gt;cursor-agent login&lt;/code&gt; and follow the prompt, or set a &lt;code dir=&quot;auto&quot;&gt;CURSOR_API_KEY&lt;/code&gt; environment variable. Confirm the session with &lt;code dir=&quot;auto&quot;&gt;cursor-agent status&lt;/code&gt;. The credential persists in that named sandbox, so later runs attach to the same box already logged in.&lt;/p&gt;
&lt;p&gt;Each agent gets its own deterministic sandbox name, built from the agent slug, the project directory, and an 8-character hash of the absolute path:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ralph-cursor-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Print the exact name for your project without starting a run:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--print-name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cursor&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If your tasks run a dev server you want to reach from the host, publish the configured port to the Cursor sandbox. The port comes from the dev-server URL you set during install:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--ports&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cursor&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That expands to an &lt;code dir=&quot;auto&quot;&gt;sbx ports&lt;/code&gt; call against the Cursor sandbox, for example &lt;code dir=&quot;auto&quot;&gt;sbx ports ralph-cursor-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt; --publish 3000:3000&lt;/code&gt;. Per-agent names keep state separate: your Cursor sandbox and your Claude sandbox never share credentials or installed tools, so you can run both against the same repo without one clobbering the other.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-4-run-the-loop-with-cursor&quot;&gt;Step 4: Run the loop with Cursor&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;With a task list in place and Cursor authenticated, start the loop. The default agent is Claude, so name Cursor explicitly:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 10 iterations (the default)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cursor&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 50 iterations for a longer unattended run&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cursor&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# exactly one iteration, good for a smoke test&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cursor&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--once&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The short form &lt;code dir=&quot;auto&quot;&gt;-a&lt;/code&gt; works too: &lt;code dir=&quot;auto&quot;&gt;./ralph.sh -a cursor -n 5&lt;/code&gt;. The iteration count is a safety budget, not a target. The loop stops the moment the work is done.&lt;/p&gt;
&lt;p&gt;Under the hood, Ralph builds a &lt;code dir=&quot;auto&quot;&gt;cursor-agent&lt;/code&gt; command and runs it inside the sandbox. For &lt;code dir=&quot;auto&quot;&gt;./ralph.sh --agent cursor&lt;/code&gt;, the expansion is:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-cursor-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cursor&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-p&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$PROMPT_CONTENT&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;-p&lt;/code&gt; flag is Cursor’s print (headless) mode, which still has full tool access including file writes and shell. That is exactly what a loop needs: an agent that can do real work without a person approving each step.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;pass-a-model-and-flags-after-the-separator&quot;&gt;Pass a model and flags after the separator&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Everything to the right of Ralph’s own &lt;code dir=&quot;auto&quot;&gt;--&lt;/code&gt; separator is forwarded straight to &lt;code dir=&quot;auto&quot;&gt;cursor-agent&lt;/code&gt;, inserted right after &lt;code dir=&quot;auto&quot;&gt;-p&lt;/code&gt;. So this:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cursor&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--model&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;auto&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;expands to:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-cursor-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cursor&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-p&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--model&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$PROMPT_CONTENT&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Do not guess at model names. Run &lt;code dir=&quot;auto&quot;&gt;cursor-agent models&lt;/code&gt; inside the sandbox to print the exact identifiers Cursor accepts, then pass one. The rule to remember: everything left of &lt;code dir=&quot;auto&quot;&gt;--&lt;/code&gt; configures Ralph (agent, iteration count, login), and everything right of &lt;code dir=&quot;auto&quot;&gt;--&lt;/code&gt; configures Cursor. A practical full command for an overnight run:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cursor&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--model&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--force&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;--force&lt;/code&gt; flag (alias &lt;code dir=&quot;auto&quot;&gt;--yolo&lt;/code&gt;) lets Cursor run commands without pausing for approval, which is what keeps an unattended run from stalling on a prompt nobody is there to answer. It is safe here only because the sandbox is the boundary.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-happens-each-iteration&quot;&gt;What happens each iteration&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Every pass is mechanical and identical. The agent reorients from disk, does one task, proves it works, and commits.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Find the highest-priority incomplete task in &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Work the steps in &lt;code dir=&quot;auto&quot;&gt;.agent/tasks/TASK-{ID}.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run tests, linting, and type checking.&lt;/li&gt;
&lt;li&gt;Complete the task, take a screenshot, update the task status, and commit.&lt;/li&gt;
&lt;li&gt;Repeat until all tasks pass or the iteration cap is reached.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The key detail is that each iteration spawns a fresh &lt;code dir=&quot;auto&quot;&gt;cursor-agent -p&lt;/code&gt; with a clean context window. The agent does not carry an hours-long transcript from one task to the next. It reads the current state from disk, does one task, and exits. This is why the loop deliberately does not use Cursor’s own session resume flags: continuity is a liability here, not a feature.&lt;/p&gt;
&lt;pre dir=&quot;ltr&quot;&gt;flowchart TD
    Start([&quot;./ralph.sh --agent cursor -n 50&quot;]) --&gt; Pick[&quot;Pick top task from .agent/tasks.json&quot;]
    Pick --&gt; Spawn[&quot;sbx run cursor . -- -p (fresh context)&quot;]
    Spawn --&gt; Work[&quot;cursor-agent reads state, edits files, runs commands&quot;]
    Work --&gt; Verify[&quot;Run tests, lint, type check, screenshot&quot;]
    Verify --&gt; Commit[&quot;Commit and update task status&quot;]
    Commit --&gt; Check{&quot;Promise tag emitted?&quot;}
    Check --&gt;|&quot;none&quot;| Pick
    Check --&gt;|&quot;COMPLETE&quot;| Done([&quot;exit 0, all tasks done&quot;])
    Check --&gt;|&quot;BLOCKED or DECIDE&quot;| Stop([&quot;exit 2 or 3, wants a human&quot;])&lt;/pre&gt;
&lt;p&gt;The filesystem and git history are the memory layer. Progress lives in &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;.agent/logs/LOG.md&lt;/code&gt;, the per-task specs, and the git log, not in a chat transcript. The prompt that drives every pass lives in &lt;code dir=&quot;auto&quot;&gt;.agent/PROMPT.md&lt;/code&gt;, and writing it well is its own skill, covered in &lt;a href=&quot;https://ralphloop.sh/blog/ralph-loop-prompt-file/&quot;&gt;how to write the PROMPT.md file&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;One rule keeps the whole thing reliable: one task per invocation. Cursor completes exactly one task, commits, and stops. It never batches several tasks into a single iteration, which is what keeps each commit small and each diff reviewable.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-the-loop-knows-when-to-stop&quot;&gt;How the loop knows when to stop&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A loop that never ends is a money fire, so Ralph stops on an explicit signal rather than a guess. Cursor emits a semantic promise tag in its final message and Ralph reads it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;COMPLETE&amp;#x3C;/promise&gt;&lt;/code&gt; means every task is finished.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;BLOCKED:reason&amp;#x3C;/promise&gt;&lt;/code&gt; means the agent needs human help.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;DECIDE:question&amp;#x3C;/promise&gt;&lt;/code&gt; means it needs a decision you have to make.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those map to exit codes, which is what makes the loop scriptable:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cursor&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;echo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;$?&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt;# 0 COMPLETE, 1 MAX_ITERATIONS, 2 BLOCKED, 3 DECIDE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;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. The full design lives in &lt;a href=&quot;https://ralphloop.sh/blog/ralph-loop-completion-promise/&quot;&gt;completion promises and exit codes&lt;/a&gt;. If Cursor is not authenticated when the loop starts, Ralph detects the auth failure, stops, and tells you to run &lt;code dir=&quot;auto&quot;&gt;./ralph.sh --login --agent cursor&lt;/code&gt;, so it never thrashes on a box that can never make progress.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;steer-debug-and-review&quot;&gt;Steer, debug, and review&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Three things keep a long Cursor run under control.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;steer-without-stopping&quot;&gt;Steer without stopping&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;If you notice the agent heading the wrong way, you do not have to kill the loop. Edit &lt;code dir=&quot;auto&quot;&gt;.agent/STEERING.md&lt;/code&gt; while it runs. The agent checks that file on each pass and handles the critical work there before resuming the normal task order. That is steering, not stopping, and it keeps momentum while you correct course.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;get-inside-the-sandbox&quot;&gt;Get inside the sandbox&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The sandbox is a normal container you can poke at. List what exists, then open a shell in the Cursor box:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ls&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;exec&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-it&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-cursor-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bash&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;From there you can re-run a failing test by hand, confirm auth with &lt;code dir=&quot;auto&quot;&gt;cursor-agent status&lt;/code&gt;, or read &lt;code dir=&quot;auto&quot;&gt;.agent/logs/LOG.md&lt;/code&gt; and the per-iteration logs in &lt;code dir=&quot;auto&quot;&gt;.agent/history/&lt;/code&gt;. When an install fails, the deny-by-default network is usually the cause. Allow the domain it needs:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;policy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;allow&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;network&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-cursor-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;registry.npmjs.org&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That is a feature, not a bug. The agent installs what the task needs without a path to exfiltrate your source. The full argument is in &lt;a href=&quot;https://ralphloop.sh/blog/run-ai-agents-in-docker-sandboxes-safely/&quot;&gt;running AI coding agents in Docker sandboxes safely&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;review-the-diff-in-the-morning&quot;&gt;Review the diff in the morning&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Because every task is committed separately, the morning review is a git review, not an archaeology dig:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--oneline&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;diff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main...HEAD&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;You read the commits in order, eyeball the screenshots the agent captured, and accept or revert. A single bad task is one revert, not a tangled mess. This is the whole premise of &lt;a href=&quot;https://ralphloop.sh/blog/run-ai-coding-agent-overnight/&quot;&gt;running an AI coding agent overnight&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;should-you-use-cursor-or-another-agent&quot;&gt;Should you use Cursor or another agent?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Ralph is agent agnostic. The supported agents are &lt;code dir=&quot;auto&quot;&gt;claude&lt;/code&gt; (default), &lt;code dir=&quot;auto&quot;&gt;codex&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;copilot&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;cursor&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;gemini&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;opencode&lt;/code&gt;, and switching is a single flag. Cursor is a strong default when you already pay for it and want its model routing through &lt;code dir=&quot;auto&quot;&gt;--model auto&lt;/code&gt;. The loop machinery is identical across agents, so the choice comes down to which CLI holds up over a multi-hour run. That comparison is in &lt;a href=&quot;https://ralphloop.sh/blog/best-agentic-cli-for-long-running-tasks/&quot;&gt;the best agentic CLI for long-running tasks&lt;/a&gt;, and the broader field guide is &lt;a href=&quot;https://ralphloop.sh/blog/agentic-coding-clis/&quot;&gt;agentic coding CLIs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A real Cursor loop, start to finish, is four commands:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 1. install Ralph and scaffolding&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@pageai/ralph-loop&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 2. authenticate once (creates the sandbox, you log in inside it)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--login&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cursor&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 3. smoke test a single pass&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cursor&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--once&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 4. run the loop for real with a model and force mode&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cursor&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--model&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--force&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Define your tasks in &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt;, write a clear &lt;code dir=&quot;auto&quot;&gt;.agent/PROMPT.md&lt;/code&gt;, start the loop, and read the commits in the morning.&lt;/p&gt;
&lt;section aria-label=&quot;Frequently asked questions&quot;&gt; &lt;h2&gt;Frequently asked questions&lt;/h2&gt; &lt;div&gt; &lt;h3&gt;How do I run a Ralph loop with the Cursor CLI?&lt;/h3&gt; &lt;p&gt;Install Ralph with npx @pageai/ralph-loop, create a task list, log in with ./ralph.sh --login --agent cursor, then run ./ralph.sh --agent cursor. Ralph wraps the headless cursor-agent in print mode inside a Docker sandbox, starts a fresh context each iteration, and repeats until every task in .agent/tasks.json is done or the iteration cap is reached.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;Does ralph.sh support the Cursor CLI?&lt;/h3&gt; &lt;p&gt;Yes. Cursor is one of the supported agents, alongside claude, codex, copilot, gemini, and opencode. Select it with --agent cursor or the short form -a cursor. Ralph builds a cursor-agent command and runs it inside a deterministic sandbox named ralph-cursor-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;How do I pass a Cursor model through Ralph?&lt;/h3&gt; &lt;p&gt;Put it after the -- separator. Anything to the right of -- is forwarded to cursor-agent, so ./ralph.sh --agent cursor -- --model auto runs cursor-agent in print mode with that model and the Ralph prompt. Run cursor-agent models inside the sandbox to list the exact identifiers Cursor accepts.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;How do I log in to Cursor for the loop?&lt;/h3&gt; &lt;p&gt;Run ./ralph.sh --login --agent cursor. It drops you into the sandbox shell where you run cursor-agent login, or you can set CURSOR_API_KEY. The credential persists in that named sandbox, so future runs attach to the same box already authenticated.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;Is it safe to run cursor-agent in force or YOLO mode?&lt;/h3&gt; &lt;p&gt;It is unsafe on your laptop and reasonable inside a sandbox. Ralph runs cursor-agent in an isolated Docker Sandbox microVM with network denied by default, so --force lets the agent run commands without approval prompts while staying unable to touch your real files. The sandbox is the boundary, not the agent.&lt;/p&gt; &lt;/div&gt; &lt;/section&gt; 
&lt;aside aria-label=&quot;Get started with Ralph Loop&quot;&gt; &lt;h2&gt;Run your own Ralph loop&lt;/h2&gt; &lt;p&gt;Ralph is a hackable script you point at your project. Install it and let an agent work through your task list.&lt;/p&gt; &lt;pre&gt;&lt;code&gt;npx @pageai/ralph-loop&lt;/code&gt;&lt;/pre&gt; &lt;div&gt; &lt;a href=&quot;https://www.npmjs.com/package/@pageai/ralph-loop&quot;&gt;
Install from npm
&lt;/a&gt; &lt;a href=&quot;https://github.com/pageai-pro/ralph-loop&quot;&gt;Star on GitHub&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=3TL8Ez66I3o&quot;&gt;Watch the walkthrough&lt;/a&gt; &lt;/div&gt; &lt;/aside&gt;</content:encoded><category>ralph-technique</category></item><item><title>The Ralph Loop Shell Script: How ralph.sh Works and How to Run It</title><link>https://ralphloop.sh/blog/ralph-loop-shell-script/</link><guid isPermaLink="true">https://ralphloop.sh/blog/ralph-loop-shell-script/</guid><description>ralph.sh is the open-source shell script behind the Ralph loop. Here is how to install it, the flags that drive it, what it does on every iteration, and why it stays hackable.

</description><pubDate>Sun, 28 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The Ralph loop shell script is &lt;code dir=&quot;auto&quot;&gt;ralph.sh&lt;/code&gt;, 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 &lt;a href=&quot;https://ralphloop.sh&quot;&gt;ralphloop.sh&lt;/a&gt; and the full source at &lt;a href=&quot;https://github.com/pageai-pro/ralph-loop&quot;&gt;github.com/pageai-pro/ralph-loop&lt;/a&gt;. You install it into a project with one command, point it at a task list, and let it grind.&lt;/p&gt;
&lt;p&gt;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 &lt;a href=&quot;https://ralphloop.sh/blog/what-is-the-ralph-technique/&quot;&gt;what the Ralph technique is&lt;/a&gt; and come back.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-is-the-ralph-loop-shell-script&quot;&gt;What is the Ralph loop shell script?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;ralph.sh&lt;/code&gt; is the practical implementation of the Ralph technique. The technique was popularized by Geoffrey Huntley with one line: &lt;a href=&quot;https://ghuntley.com/ralph/&quot;&gt;Ralph is a Bash loop&lt;/a&gt;. 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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# the bare idea&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;while&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;do&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;cat&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;PROMPT.md&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your-agent-cli&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;done&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# the real script, in one line&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-do-you-get-the-ralphsh-script&quot;&gt;How do you get the ralph.sh script?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Run one command in your project’s root. It scaffolds the &lt;code dir=&quot;auto&quot;&gt;.agent/&lt;/code&gt; directory and drops &lt;code dir=&quot;auto&quot;&gt;ralph.sh&lt;/code&gt; into the repo.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@pageai/ralph-loop&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;If the file is not executable yet, make it so:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;chmod&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+x&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph.sh&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-do-you-run-the-ralph-loop-script&quot;&gt;How do you run the Ralph loop script?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# default: 10 iterations&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# set an explicit cap (these are equivalent)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--max-iterations&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# run exactly one iteration to sanity check the setup&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--once&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;choosing-an-agent&quot;&gt;Choosing an agent&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The script is agent agnostic. Claude Code is the default. Switch to another supported CLI with &lt;code dir=&quot;auto&quot;&gt;--agent&lt;/code&gt; or its short form &lt;code dir=&quot;auto&quot;&gt;-a&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;codex&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cursor&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Supported agents are &lt;code dir=&quot;auto&quot;&gt;claude&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;codex&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;copilot&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;cursor&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;gemini&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;opencode&lt;/code&gt;. To pass flags straight through to the underlying agent, put them after a &lt;code dir=&quot;auto&quot;&gt;--&lt;/code&gt; separator so the script does not try to parse them:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;codex&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--model&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gpt-5.5&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gemini&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--model&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pro&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;auth-ports-and-the-sandbox-name&quot;&gt;Auth, ports, and the sandbox name&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--login&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--login&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cursor&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--ports&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--print-name&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--print-name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;codex&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The sandbox name follows the pattern &lt;code dir=&quot;auto&quot;&gt;ralph-&amp;#x3C;agent&gt;-&amp;#x3C;current-dir&gt;-&amp;#x3C;hash8&gt;&lt;/code&gt;, for example &lt;code dir=&quot;auto&quot;&gt;ralph-claude-my-app-a1b2c3d4&lt;/code&gt;. Because the agent name is part of the sandbox name, switching agents gives each one its own sandbox for the same project.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-does-the-script-do-on-each-iteration&quot;&gt;What does the script do on each iteration?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;pre dir=&quot;ltr&quot;&gt;flowchart TD
    Start([&quot;./ralph.sh -n 50&quot;]) --&gt; Read[&quot;Read .agent/tasks.json&quot;]
    Read --&gt; Pick{&quot;Incomplete task left?&quot;}
    Pick --&gt;|&quot;No&quot;| Done[&quot;Emit COMPLETE, exit 0&quot;]
    Pick --&gt;|&quot;Yes&quot;| Fresh[&quot;Spawn agent with fresh context in the sandbox&quot;]
    Fresh --&gt; Spec[&quot;Load TASK-ID.json steps&quot;]
    Spec --&gt; Work[&quot;Implement the one task&quot;]
    Work --&gt; Verify[&quot;Run tests, lint, typecheck&quot;]
    Verify --&gt; Pass{&quot;All checks pass?&quot;}
    Pass --&gt;|&quot;No&quot;| Work
    Pass --&gt;|&quot;Yes&quot;| Commit[&quot;Screenshot, set passes:true, commit&quot;]
    Commit --&gt; Limit{&quot;Max iterations reached?&quot;}
    Limit --&gt;|&quot;No&quot;| Read
    Limit --&gt;|&quot;Yes&quot;| MaxOut[&quot;Exit 1&quot;]&lt;/pre&gt;
&lt;p&gt;In words, the five steps are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Find the highest-priority incomplete task in &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Work the steps in &lt;code dir=&quot;auto&quot;&gt;.agent/tasks/TASK-{ID}.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run tests, linting, and type checking.&lt;/li&gt;
&lt;li&gt;Complete the task, take a screenshot, update the task status, and commit.&lt;/li&gt;
&lt;li&gt;Repeat until every task passes or the iteration cap is reached.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;The prompt that drives every pass lives in &lt;code dir=&quot;auto&quot;&gt;.agent/PROMPT.md&lt;/code&gt;. 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 &lt;a href=&quot;https://ralphloop.sh/blog/ralph-loop-prompt-file/&quot;&gt;how to write the PROMPT.md file&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-lives-next-to-the-script&quot;&gt;What lives next to the script?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;ralph.sh&lt;/code&gt; reads and writes a small set of files under &lt;code dir=&quot;auto&quot;&gt;.agent/&lt;/code&gt;. The script is the engine; these files are its memory. A fresh agent remembers nothing between passes, so progress has to live on disk.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.agent/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── PROMPT.md           # Prompt sent to the agent each iteration&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── tasks.json          # Task lookup table (required)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── tasks/              # Individual task specs (TASK-{ID}.json)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── prd/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   ├── PRD.md          # Product requirements document&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   └── SUMMARY.md      # Short project overview sent each iteration&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── logs/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;│   └── LOG.md          # Progress log&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── history/            # Per-iteration output logs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── skills/             # Shared skills&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;└── STEERING.md         # Critical work injected mid-run&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The task lookup table in &lt;code dir=&quot;auto&quot;&gt;tasks.json&lt;/code&gt; 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 &lt;code dir=&quot;auto&quot;&gt;tasks/TASK-{ID}.json&lt;/code&gt; with steps and acceptance criteria.&lt;/p&gt;
&lt;p&gt;You can steer a run that is already going by editing &lt;code dir=&quot;auto&quot;&gt;.agent/STEERING.md&lt;/code&gt;. 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.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;promise-tags-and-exit-codes&quot;&gt;Promise tags and exit codes&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;The promise tags are small semantic statuses the agent prints:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;promise&gt;COMPLETE&amp;#x3C;/promise&gt;         all tasks finished, exit the loop&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;promise&gt;BLOCKED:reason&amp;#x3C;/promise&gt;   needs human help&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;promise&gt;DECIDE:question&amp;#x3C;/promise&gt;  needs a decision&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Those tags map to exit codes, which is what makes the script easy to wire into CI or a cron job:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;echo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;$?&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt;# 0 COMPLETE, 1 MAX_ITERATIONS, 2 BLOCKED, 3 DECIDE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;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 &lt;a href=&quot;https://ralphloop.sh/blog/ralph-loop-completion-promise/&quot;&gt;completion promises and exit codes&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-a-shell-script-and-not-a-framework&quot;&gt;Why a shell script and not a framework?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Three reasons, and they all come back to control.&lt;/p&gt;
&lt;p&gt;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 &lt;code dir=&quot;auto&quot;&gt;ralph.sh&lt;/code&gt; 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.&lt;/p&gt;
&lt;p&gt;Second, you can hack it. The script is meant to be edited. The default mode is implementation, but the behavior lives in &lt;code dir=&quot;auto&quot;&gt;.agent/PROMPT.md&lt;/code&gt;, 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.&lt;/p&gt;
&lt;p&gt;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 &lt;a href=&quot;https://ralphloop.sh/blog/ralph-loop-vs-one-shot-prompting/&quot;&gt;Ralph loop vs one-shot prompting&lt;/a&gt;, and for the origin of the one-liner the script grew from, see &lt;a href=&quot;https://ralphloop.sh/blog/who-invented-the-ralph-technique/&quot;&gt;who invented the Ralph technique&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-sandbox-boundary&quot;&gt;The sandbox boundary&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The script runs each agent inside an isolated Docker Sandbox microVM using the standalone &lt;code dir=&quot;auto&quot;&gt;sbx&lt;/code&gt; 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 &lt;a href=&quot;https://docs.docker.com/ai/sandboxes/&quot;&gt;Docker Sandboxes docs&lt;/a&gt; for the isolation model.&lt;/p&gt;
&lt;p&gt;If you need to look inside while a run is going, list the sandboxes and shell into the one you want:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ls&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;exec&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-it&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-claude-my-app-a1b2c3d4&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bash&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-typical-first-run&quot;&gt;A typical first run&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 1. install the script and scaffolding&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@pageai/ralph-loop&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 2. (in your editor, plan mode) use the prd-creator skill to turn&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#    requirements into .agent/prd/PRD.md and task specs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 3. confirm the setup works end to end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--once&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 4. let it run for a real batch&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;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 &lt;a href=&quot;https://ralphloop.sh/blog/run-ai-coding-agent-overnight/&quot;&gt;how to run an AI coding agent overnight&lt;/a&gt;.&lt;/p&gt;
&lt;section aria-label=&quot;Frequently asked questions&quot;&gt; &lt;h2&gt;Frequently asked questions&lt;/h2&gt; &lt;div&gt; &lt;h3&gt;What is the Ralph loop shell script?&lt;/h3&gt; &lt;p&gt;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.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;How do I install ralph.sh?&lt;/h3&gt; &lt;p&gt;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.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;How do I run the Ralph loop script?&lt;/h3&gt; &lt;p&gt;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.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;Is ralph.sh just a while loop?&lt;/h3&gt; &lt;p&gt;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.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;Can I edit the script for my own stack?&lt;/h3&gt; &lt;p&gt;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.&lt;/p&gt; &lt;/div&gt; &lt;/section&gt; 
&lt;aside aria-label=&quot;Get started with Ralph Loop&quot;&gt; &lt;h2&gt;Run your own Ralph loop&lt;/h2&gt; &lt;p&gt;Ralph is a hackable script you point at your project. Install it and let an agent work through your task list.&lt;/p&gt; &lt;pre&gt;&lt;code&gt;npx @pageai/ralph-loop&lt;/code&gt;&lt;/pre&gt; &lt;div&gt; &lt;a href=&quot;https://www.npmjs.com/package/@pageai/ralph-loop&quot;&gt;
Install from npm
&lt;/a&gt; &lt;a href=&quot;https://github.com/pageai-pro/ralph-loop&quot;&gt;Star on GitHub&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=3TL8Ez66I3o&quot;&gt;Watch the walkthrough&lt;/a&gt; &lt;/div&gt; &lt;/aside&gt;</content:encoded><category>ralph-technique</category></item><item><title>How to Run a Ralph Loop With Claude Code</title><link>https://ralphloop.sh/blog/ralph-loop-with-claude-code/</link><guid isPermaLink="true">https://ralphloop.sh/blog/ralph-loop-with-claude-code/</guid><description>Claude Code is Ralph&apos;s default agent. A full end-to-end walkthrough for running the Ralph loop with Claude Code: install, log in inside the sandbox, loop on your task list, and review the commits.

</description><pubDate>Fri, 26 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A ralph loop with Claude Code is the default path: install Ralph, log in to Claude once inside its sandbox, then run &lt;code dir=&quot;auto&quot;&gt;./ralph.sh&lt;/code&gt; with no agent flag at all. Claude Code is Ralph’s default agent, so the bare command already wraps &lt;code dir=&quot;auto&quot;&gt;claude&lt;/code&gt; in headless print mode, hands it a fresh context window every iteration, and keeps re-running it against your task list until the work is done or you hit the iteration cap.&lt;/p&gt;
&lt;p&gt;Claude is also the only agent whose output Ralph fully parses. That one fact is why the default is the default, and it changes what you see on screen while a run is happening. This post is the end-to-end setup walkthrough, from an empty repo to a reviewable diff. If the loop itself is new to you, read &lt;a href=&quot;https://ralphloop.sh/blog/what-is-the-ralph-technique/&quot;&gt;what the Ralph technique is&lt;/a&gt; first. If you want the flag-level mechanics of driving Claude in a loop, the companion piece is &lt;a href=&quot;https://ralphloop.sh/blog/run-claude-code-in-a-loop/&quot;&gt;running Claude Code in a loop&lt;/a&gt;. Consider this the brand recipe and that one the mechanics deep dive.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-differentiator-claude-gives-ralph-the-richest-live-telemetry&quot;&gt;The differentiator: Claude gives Ralph the richest live telemetry&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Every supported agent runs the same loop. Only Claude streams structured JSON back that Ralph can read line by line.&lt;/p&gt;
&lt;p&gt;Look at &lt;code dir=&quot;auto&quot;&gt;scripts/lib/agents.sh&lt;/code&gt;. The helper &lt;code dir=&quot;auto&quot;&gt;agent_uses_stream_json()&lt;/code&gt; returns success for exactly one agent:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;agent_uses_stream_json&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[ &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$1&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;claude&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; ]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;And &lt;code dir=&quot;auto&quot;&gt;build_agent_command()&lt;/code&gt; emits Claude with two extra flags no other agent gets:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-claude-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;claude&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--output-format&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stream-json&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--verbose&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-p&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$PROMPT_CONTENT&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;--output-format stream-json --verbose&lt;/code&gt; pair turns Claude’s run into a stream of JSON events instead of an opaque wall of text. Ralph consumes that stream to power its live step detection (Thinking, Implementing, Testing) and the rolling preview of what the agent is doing right now. With Codex, Cursor, Copilot, Gemini, or OpenCode you still get a working loop, commits, and history logs, but you do not get the same real-time read on the agent’s internal state. If you want to watch a long unattended run and actually know what phase it is in, Claude is the agent with the most to say.&lt;/p&gt;
&lt;p&gt;That is the practical case for the default. Not model quality (pick whatever your account supports), but observability. Ralph was built around Claude’s stream first, and everything else is a good citizen on top of the same machinery.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;two-ways-to-loop-claude-the-stop-hook-plugin-vs-the-hackable-script&quot;&gt;Two ways to loop Claude: the Stop Hook plugin vs the hackable script&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Anthropic shipped an official way to loop Claude Code: a plugin that registers a Stop Hook, which fires when Claude finishes and re-injects the prompt so it keeps going. It lives in the &lt;a href=&quot;https://code.claude.com/docs&quot;&gt;Claude Code docs&lt;/a&gt;, and it is a clean, in-process way to keep a single Claude session churning.&lt;/p&gt;
&lt;p&gt;Ralph takes the other road. Instead of a hook that resumes the same session, &lt;code dir=&quot;auto&quot;&gt;ralph.sh&lt;/code&gt; is a Bash loop that spawns a brand new &lt;code dir=&quot;auto&quot;&gt;claude&lt;/code&gt; process every iteration inside a Docker sandbox, with a clean context window each time. This is the original shape of the technique, popularized in &lt;a href=&quot;https://ghuntley.com/ralph/&quot;&gt;Geoffrey Huntley’s Ralph writeup&lt;/a&gt;, where “Ralph is a Bash loop” is the whole point.&lt;/p&gt;
&lt;p&gt;The trade-off is deliberate:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Stop Hook keeps one session alive and re-prompts it. Continuity is the feature.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;ralph.sh&lt;/code&gt; throws the session away and rebuilds state from disk. Discarding continuity is the feature, because it kills context rot on long runs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ralph’s script is also yours to edit. The verification stack, the sandbox boundary, the prompt, the step detection, all of it is in a shell script you can read and change. If you want the shape of the wrapper itself, that is covered in &lt;a href=&quot;https://ralphloop.sh/blog/ralph-loop-shell-script/&quot;&gt;the Ralph loop shell script&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-1-install-ralph&quot;&gt;Step 1: Install Ralph&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Run the installer in your project root. It writes the &lt;code dir=&quot;auto&quot;&gt;ralph.sh&lt;/code&gt; script and the &lt;code dir=&quot;auto&quot;&gt;.agent/&lt;/code&gt; directory into the repo.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@pageai/ralph-loop&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The installer asks for your dev server URL and port and your default agent. Claude is the sensible pick here, and it is already the fallback if you skip the choice. After install, make the script executable if it is not already:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;chmod&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+x&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph.sh&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Ralph runs each agent inside a Docker Sandbox, so install and sign in to Docker Sandboxes first. The &lt;a href=&quot;https://docs.docker.com/ai/sandboxes/&quot;&gt;Docker Sandboxes docs&lt;/a&gt; cover the official setup. Under the hood Ralph drives the standalone &lt;code dir=&quot;auto&quot;&gt;sbx&lt;/code&gt; CLI.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-2-create-a-prd-and-task-list&quot;&gt;Step 2: Create a PRD and task list&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The loop runs a task list. It does not invent one for you. Turn your requirements into a PRD and a set of task specs with the &lt;code dir=&quot;auto&quot;&gt;prd-creator&lt;/code&gt; skill before you run anything.&lt;/p&gt;
&lt;p&gt;Open Claude Code (or any agent) in plan mode and prompt it:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Use the prd-creator skill to help me create a PRD and task list for the below requirements.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;An app is already set up with React, Tailwind CSS and TypeScript.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Requirements:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- A SaaS product that helps users manage their finances.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- Track income and expenses, create invoices, generate reports.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- Use shadcn/ui for components and Supabase for the database.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// etc.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The skill writes &lt;code dir=&quot;auto&quot;&gt;.agent/prd/PRD.md&lt;/code&gt;, a short &lt;code dir=&quot;auto&quot;&gt;.agent/prd/SUMMARY.md&lt;/code&gt;, and per-task specs under &lt;code dir=&quot;auto&quot;&gt;.agent/tasks/&lt;/code&gt;, indexed by &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt;. Review each task individually before you start. A clear task list with real acceptance criteria is the single biggest factor in whether an overnight run succeeds.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-3-log-in-to-claude-inside-the-sandbox&quot;&gt;Step 3: Log in to Claude inside the sandbox&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Claude runs inside an isolated sandbox, not on your host, so it needs credentials in that environment. Authenticate once with the login action. Claude is the default, so you can drop the agent flag, but naming it keeps things explicit:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--login&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;claude&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Ralph prints the login command for every supported agent, highlights the Claude one, and drops you into the sandbox shell. The command it runs to create the box looks like this:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-claude-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;claude&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Inside the sandbox, authenticate Claude once and follow the prompt. If OAuth login does not persist, the fallback is an API key: Docker Sandboxes forward environment variables at creation time, so you can set &lt;code dir=&quot;auto&quot;&gt;ANTHROPIC_API_KEY&lt;/code&gt; in your shell before recreating the box. When Claude is not authenticated at the start of a run, Ralph detects the failure and tells you to run &lt;code dir=&quot;auto&quot;&gt;/login&lt;/code&gt;, so it never burns iterations on a box that can never make progress.&lt;/p&gt;
&lt;p&gt;Each agent gets its own deterministic sandbox name, built from the agent slug, the project directory, and an 8-character hash of the absolute path:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ralph-claude-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Print the exact name for your project without starting a run:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--print-name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;claude&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If your tasks run a dev server you want to reach from the host, publish the configured port to the Claude sandbox. The port comes from the dev-server URL you set during install:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--ports&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;claude&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Per-agent names keep state separate. Your Claude sandbox and, say, your Cursor sandbox never share credentials or installed tools, so you can run both against the same repo without one clobbering the other. If you later want the Cursor variant, that walkthrough is &lt;a href=&quot;https://ralphloop.sh/blog/ralph-loop-with-cursor-cli/&quot;&gt;running a Ralph loop with the Cursor CLI&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-4-run-the-loop-with-claude&quot;&gt;Step 4: Run the loop with Claude&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;With a task list in place and Claude authenticated, start the loop. Because Claude is the default, the bare command already runs it:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 10 iterations (the default), Claude by default&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# the same thing, stated explicitly&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;claude&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 50 iterations for a longer unattended run&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# exactly one iteration, good for a smoke test&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--once&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The iteration count is a safety budget, not a target. The loop stops the moment the work is done. The short form &lt;code dir=&quot;auto&quot;&gt;-a&lt;/code&gt; works too, and &lt;code dir=&quot;auto&quot;&gt;--max-iterations 5&lt;/code&gt; is a synonym for &lt;code dir=&quot;auto&quot;&gt;-n 5&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Under the hood, Ralph builds a &lt;code dir=&quot;auto&quot;&gt;claude&lt;/code&gt; command and runs it inside the sandbox. For a first run that creates the box, the expansion is:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-claude-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;claude&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--output-format&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stream-json&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--verbose&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-p&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$PROMPT_CONTENT&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;-p&lt;/code&gt; flag is Claude’s print (headless) mode, which still has full tool access including file writes and shell. The &lt;code dir=&quot;auto&quot;&gt;--output-format stream-json --verbose&lt;/code&gt; pair is Claude-only, and it is what feeds Ralph’s live preview.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;pass-a-model-and-flags-after-the-separator&quot;&gt;Pass a model and flags after the separator&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Everything to the right of Ralph’s own &lt;code dir=&quot;auto&quot;&gt;--&lt;/code&gt; separator is forwarded straight to Claude, inserted between &lt;code dir=&quot;auto&quot;&gt;--verbose&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;-p&lt;/code&gt;. So this:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;claude&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--permission-mode&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bypassPermissions&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;expands to:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-claude-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;claude&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--output-format&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stream-json&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--verbose&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--permission-mode&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bypassPermissions&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-p&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$PROMPT_CONTENT&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The rule to remember: everything left of &lt;code dir=&quot;auto&quot;&gt;--&lt;/code&gt; configures Ralph (agent, iteration count, login), and everything right of &lt;code dir=&quot;auto&quot;&gt;--&lt;/code&gt; configures Claude. If you want to pin a model, pass one your Anthropic account supports rather than guessing at an identifier. Do not hardcode a model name you are not sure exists.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;yolo-mode-is-safe-because-the-sandbox-is-the-boundary&quot;&gt;YOLO mode is safe because the sandbox is the boundary&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;For a loop to run unattended it cannot pause for a human to approve each tool call. Claude Code has two ways to skip those prompts: &lt;code dir=&quot;auto&quot;&gt;--dangerously-skip-permissions&lt;/code&gt;, or the equivalent &lt;code dir=&quot;auto&quot;&gt;--permission-mode bypassPermissions&lt;/code&gt;. On your laptop that flag is genuinely reckless. Inside a Docker Sandbox it is the intended setup.&lt;/p&gt;
&lt;p&gt;The sandbox is an isolated microVM with network denied by default. Claude can edit files and run shell commands freely inside it, but that blast radius never reaches your real machine. When the Docker Sandboxes login flow asks whether to bypass permissions, answering yes is exactly why you are running in a sandbox in the first place.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-happens-each-iteration&quot;&gt;What happens each iteration&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Every pass is mechanical and identical. Claude reorients from disk, does one task, proves it works, and commits.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Find the highest-priority incomplete task in &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Work the steps in &lt;code dir=&quot;auto&quot;&gt;.agent/tasks/TASK-{ID}.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run tests, linting, and type checking.&lt;/li&gt;
&lt;li&gt;Complete the task, take a screenshot, update the task status, and commit.&lt;/li&gt;
&lt;li&gt;Repeat until all tasks pass or the iteration cap is reached.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The key detail is that each iteration spawns a fresh &lt;code dir=&quot;auto&quot;&gt;claude -p&lt;/code&gt; with a clean context window. Claude does not carry an hours-long transcript from one task to the next. It reads the current state from disk, does one task, and exits. That is why the loop does not resume a prior Claude session: continuity is a liability here, not a feature.&lt;/p&gt;
&lt;pre dir=&quot;ltr&quot;&gt;flowchart TD
    Start([&quot;./ralph.sh -n 50 (Claude default)&quot;]) --&gt; Pick[&quot;Pick top task from .agent/tasks.json&quot;]
    Pick --&gt; Spawn[&quot;sbx run claude . -- stream-json (fresh context)&quot;]
    Spawn --&gt; Stream[&quot;Ralph parses stream-json: Thinking, Implementing, Testing&quot;]
    Stream --&gt; Verify[&quot;Run tests, lint, type check, screenshot&quot;]
    Verify --&gt; Commit[&quot;Commit and update task status&quot;]
    Commit --&gt; Check{&quot;Promise tag emitted?&quot;}
    Check --&gt;|&quot;none&quot;| Pick
    Check --&gt;|&quot;COMPLETE&quot;| Done([&quot;exit 0, all tasks done&quot;])
    Check --&gt;|&quot;BLOCKED or DECIDE&quot;| Stop([&quot;exit 2 or 3, wants a human&quot;])&lt;/pre&gt;
&lt;p&gt;The filesystem and git history are the memory layer. Progress lives in &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;.agent/logs/LOG.md&lt;/code&gt;, the per-task specs under &lt;code dir=&quot;auto&quot;&gt;.agent/tasks/&lt;/code&gt;, and the git log, not in a chat transcript. The prompt that drives every pass lives in &lt;code dir=&quot;auto&quot;&gt;.agent/PROMPT.md&lt;/code&gt;, alongside &lt;code dir=&quot;auto&quot;&gt;.agent/prd/SUMMARY.md&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;.agent/history/&lt;/code&gt; per-iteration logs, and &lt;code dir=&quot;auto&quot;&gt;.agent/STEERING.md&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;One rule keeps the whole thing reliable: one task per invocation. Claude completes exactly one task, commits, and stops. It never batches several tasks into a single iteration, which is what keeps each commit small and each diff reviewable.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-the-loop-knows-when-to-stop&quot;&gt;How the loop knows when to stop&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A loop that never ends is a money fire, so Ralph stops on an explicit signal rather than a guess. Claude emits a semantic promise tag in its final message and Ralph reads it out of the stream:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;COMPLETE&amp;#x3C;/promise&gt;&lt;/code&gt; means every task is finished.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;BLOCKED:reason&amp;#x3C;/promise&gt;&lt;/code&gt; means the agent needs human help.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;DECIDE:question&amp;#x3C;/promise&gt;&lt;/code&gt; means it needs a decision you have to make.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those map to exit codes, which is what makes the loop scriptable:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;echo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;$?&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt;# 0 COMPLETE, 1 MAX_ITERATIONS, 2 BLOCKED, 3 DECIDE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;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. Because Claude’s output is fully parsed, Ralph can pick these tags and phase changes out of the live stream instead of grepping a final blob, which is the observability edge again.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;steer-debug-and-review&quot;&gt;Steer, debug, and review&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Three things keep a long Claude run under control.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;steer-without-stopping&quot;&gt;Steer without stopping&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;If you notice Claude heading the wrong way, you do not have to kill the loop. Edit &lt;code dir=&quot;auto&quot;&gt;.agent/STEERING.md&lt;/code&gt; while it runs. The agent checks that file on each pass and handles the critical work there before resuming the normal task order. That is steering, not stopping, and it keeps momentum while you correct course.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;get-inside-the-sandbox&quot;&gt;Get inside the sandbox&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The sandbox is a normal container you can poke at. List what exists, then open a shell in the Claude box:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ls&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;exec&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-it&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-claude-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bash&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;From there you can re-run a failing test by hand, check auth by running &lt;code dir=&quot;auto&quot;&gt;claude&lt;/code&gt;, or read &lt;code dir=&quot;auto&quot;&gt;.agent/logs/LOG.md&lt;/code&gt; and the per-iteration logs in &lt;code dir=&quot;auto&quot;&gt;.agent/history/&lt;/code&gt;. To reattach to an existing box, &lt;code dir=&quot;auto&quot;&gt;sbx run ralph-claude-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/code&gt; picks it back up without the create-only &lt;code dir=&quot;auto&quot;&gt;--name&lt;/code&gt; form.&lt;/p&gt;
&lt;p&gt;When an install fails, the deny-by-default network is usually the cause. Allow the domain it needs:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;policy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;allow&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;network&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-claude-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;registry.npmjs.org&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That is a feature, not a bug. Claude installs what the task needs without a path to exfiltrate your source.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;review-the-diff-in-the-morning&quot;&gt;Review the diff in the morning&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Because every task is committed separately, the morning review is a git review, not an archaeology dig:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--oneline&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;diff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main...HEAD&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;You read the commits in order, eyeball the screenshots the agent captured, and accept or revert. A single bad task is one revert, not a tangled mess. That whole premise is covered in &lt;a href=&quot;https://ralphloop.sh/blog/run-ai-coding-agent-overnight/&quot;&gt;running an AI coding agent overnight&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-full-claude-recipe-start-to-finish&quot;&gt;The full Claude recipe, start to finish&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A real Claude loop is four commands:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 1. install Ralph and scaffolding&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@pageai/ralph-loop&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 2. authenticate once (creates the sandbox, you log in inside it)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--login&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;claude&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 3. smoke test a single pass&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--once&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 4. run the loop for real (Claude is the default agent)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Define your tasks in &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt;, write a clear &lt;code dir=&quot;auto&quot;&gt;.agent/PROMPT.md&lt;/code&gt;, start the loop, and read the commits in the morning. The verification stack it assumes is Playwright for e2e, Vitest for units, TypeScript for types, ESLint for lint, and Prettier for format, so the mantra holds: if you didn’t test it, it doesn’t work.&lt;/p&gt;
&lt;section aria-label=&quot;Frequently asked questions&quot;&gt; &lt;h2&gt;Frequently asked questions&lt;/h2&gt; &lt;div&gt; &lt;h3&gt;How do I run a Ralph loop with Claude Code?&lt;/h3&gt; &lt;p&gt;Install Ralph with npx @pageai/ralph-loop, create a task list, log in with ./ralph.sh --login --agent claude, then run ./ralph.sh. Claude Code is the default agent, so the bare command already wraps claude in headless print mode inside a Docker sandbox, starts a fresh context each iteration, and repeats until every task in .agent/tasks.json is done or the iteration cap is reached.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;Why is Claude Code the default agent in Ralph?&lt;/h3&gt; &lt;p&gt;Claude is the only agent whose output Ralph fully parses. Ralph runs Claude with --output-format stream-json --verbose, and it reads that structured stream to detect the current phase (Thinking, Implementing, Testing) and show a live preview. Other agents still loop, commit, and log correctly, but they do not give the same real-time read on what the agent is doing.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;What is the difference between Ralph and the official Claude Code loop?&lt;/h3&gt; &lt;p&gt;Anthropic ships a plugin that uses a Stop Hook to re-inject the prompt and keep one Claude session going, so continuity is the point. Ralph is a hackable Bash script that spawns a brand new claude process each iteration inside a Docker sandbox with a clean context window, which kills context rot on long runs and lets you edit the wrapper yourself.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;How do I pass flags or a model to Claude through Ralph?&lt;/h3&gt; &lt;p&gt;Put them after the -- separator. Anything to the right of -- is forwarded to Claude, inserted between --verbose and -p, so ./ralph.sh --agent claude -- --permission-mode bypassPermissions runs Claude in print mode with that flag and the Ralph prompt. Pass a model your Anthropic account supports rather than guessing an identifier.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;Is it safe to run Claude with --dangerously-skip-permissions?&lt;/h3&gt; &lt;p&gt;It is reckless on your laptop and reasonable inside a sandbox. Ralph runs Claude in an isolated Docker Sandbox microVM with network denied by default, so --dangerously-skip-permissions (or --permission-mode bypassPermissions) lets the agent act without approval prompts while staying unable to touch your real files. The sandbox is the boundary, not the agent.&lt;/p&gt; &lt;/div&gt; &lt;/section&gt; 
&lt;aside aria-label=&quot;Get started with Ralph Loop&quot;&gt; &lt;h2&gt;Run your own Ralph loop&lt;/h2&gt; &lt;p&gt;Ralph is a hackable script you point at your project. Install it and let an agent work through your task list.&lt;/p&gt; &lt;pre&gt;&lt;code&gt;npx @pageai/ralph-loop&lt;/code&gt;&lt;/pre&gt; &lt;div&gt; &lt;a href=&quot;https://www.npmjs.com/package/@pageai/ralph-loop&quot;&gt;
Install from npm
&lt;/a&gt; &lt;a href=&quot;https://github.com/pageai-pro/ralph-loop&quot;&gt;Star on GitHub&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=3TL8Ez66I3o&quot;&gt;Watch the walkthrough&lt;/a&gt; &lt;/div&gt; &lt;/aside&gt;</content:encoded><category>ralph-technique</category></item><item><title>How to Run a Ralph Loop With the Codex CLI</title><link>https://ralphloop.sh/blog/ralph-loop-with-codex-cli/</link><guid isPermaLink="true">https://ralphloop.sh/blog/ralph-loop-with-codex-cli/</guid><description>A full end-to-end walkthrough for running the Ralph loop with OpenAI Codex CLI: install, log in inside the sandbox, drive codex exec non-interactively, pick a model, and review the commits.

</description><pubDate>Tue, 23 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The ralph loop Codex setup is four moves: install Ralph into your project, log in to Codex once inside its sandbox, then run &lt;code dir=&quot;auto&quot;&gt;./ralph.sh --agent codex&lt;/code&gt;. Ralph drives Codex through its non-interactive &lt;code dir=&quot;auto&quot;&gt;exec&lt;/code&gt; subcommand, hands it a fresh context window every iteration, and keeps re-running it against your task list until the work is done or you hit the iteration cap.&lt;/p&gt;
&lt;p&gt;This is the full walkthrough, from an empty repo to a diff you review in the morning. If the loop itself is new to you, start with &lt;a href=&quot;https://ralphloop.sh/blog/what-is-the-ralph-technique/&quot;&gt;what the Ralph technique is&lt;/a&gt;. If you want the flag-level mechanics of the Codex backend, the companion piece is &lt;a href=&quot;https://ralphloop.sh/blog/run-codex-cli-in-a-loop/&quot;&gt;running the Codex CLI in a loop&lt;/a&gt;. This post is the brand-specific setup recipe.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-makes-codex-different-inside-a-ralph-loop&quot;&gt;What makes Codex different inside a Ralph loop?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Codex is the one agent Ralph does not drive with a print flag. Claude and Cursor take a &lt;code dir=&quot;auto&quot;&gt;-p&lt;/code&gt; flag; Gemini and Copilot take &lt;code dir=&quot;auto&quot;&gt;-p&lt;/code&gt; after their separators; OpenCode gets a &lt;code dir=&quot;auto&quot;&gt;run&lt;/code&gt; subcommand. Codex gets &lt;code dir=&quot;auto&quot;&gt;exec&lt;/code&gt;, its non-interactive execution mode. You can see the exact shape in &lt;code dir=&quot;auto&quot;&gt;scripts/lib/agents.sh&lt;/code&gt;, where &lt;code dir=&quot;auto&quot;&gt;build_agent_command&lt;/code&gt; emits the Codex case as &lt;code dir=&quot;auto&quot;&gt;... -- exec%s &quot;$PROMPT_CONTENT&quot;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That subcommand matters more than it looks. &lt;code dir=&quot;auto&quot;&gt;codex exec&lt;/code&gt; is the headless path: it reads a prompt, edits files, runs commands, prints a final message, and exits with a status code. No REPL, no approval prompt waiting on a human, no interactive session to resume. Exit is the whole contract, and exit is exactly what a loop needs. Each iteration spawns one &lt;code dir=&quot;auto&quot;&gt;codex exec&lt;/code&gt;, lets it finish one task, reads what it emitted, and starts the next pass clean.&lt;/p&gt;
&lt;p&gt;Ralph supplies the three things a lone &lt;code dir=&quot;auto&quot;&gt;codex exec&lt;/code&gt; call cannot:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A loop, so Codex gets another pass after every commit instead of stopping.&lt;/li&gt;
&lt;li&gt;Fresh context per iteration, so a long unattended run never rots into a confused, drifting session.&lt;/li&gt;
&lt;li&gt;A Docker sandbox boundary, so Codex can edit files and run shell commands in bypass-permissions mode without that blast radius reaching your real machine.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The state lives on disk (the task list, the logs, the git history), not in a chat transcript. That is what lets a stateless &lt;code dir=&quot;auto&quot;&gt;codex exec&lt;/code&gt; reorient on every pass and still make forward progress across dozens of iterations.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-1-install-ralph&quot;&gt;Step 1: Install Ralph&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Run the installer in your project root. It drops the &lt;code dir=&quot;auto&quot;&gt;ralph.sh&lt;/code&gt; script and the &lt;code dir=&quot;auto&quot;&gt;.agent/&lt;/code&gt; directory into the repo.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@pageai/ralph-loop&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The installer asks for your dev server URL and port and your default agent, then writes a default implementation-mode setup. Pick &lt;code dir=&quot;auto&quot;&gt;codex&lt;/code&gt; when prompted, or set it per run with &lt;code dir=&quot;auto&quot;&gt;--agent codex&lt;/code&gt; later. If the script is not already executable, fix that:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;chmod&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+x&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph.sh&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Ralph runs each agent inside a Docker Sandbox, so install and sign in to Docker Sandbox first. See the &lt;a href=&quot;https://docs.docker.com/ai/sandboxes/&quot;&gt;Docker Sandboxes docs&lt;/a&gt; for the official setup flow. The loop calls the standalone &lt;code dir=&quot;auto&quot;&gt;sbx&lt;/code&gt; CLI under the hood, and the sandbox is what makes bypass-permissions mode safe.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-2-log-in-to-codex-inside-the-sandbox&quot;&gt;Step 2: Log in to Codex inside the sandbox&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Codex runs inside an isolated sandbox, not on your host, so it needs credentials in that environment. Authenticate once with the login action:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--login&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;codex&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Ralph prints the login command for every supported agent, highlights the Codex one, and drops you into the sandbox shell. The command it runs to create the box looks like this:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-codex-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;codex&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Inside the sandbox, authenticate Codex once. Run &lt;code dir=&quot;auto&quot;&gt;codex login&lt;/code&gt; and follow the prompt to connect your OpenAI account. If OAuth does not persist in the sandbox, the alternative is an &lt;code dir=&quot;auto&quot;&gt;OPENAI_API_KEY&lt;/code&gt; environment variable, which Docker Sandboxes forward when you create the box. The credential persists in that named sandbox, so later runs attach to the same box already logged in.&lt;/p&gt;
&lt;p&gt;Each agent gets its own deterministic sandbox name, built from the agent slug, the project directory, and an 8-character hash of the absolute path:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ralph-codex-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Print the exact name for your project without starting a run:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--print-name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;codex&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If your tasks run a dev server you want to reach from the host, publish the configured port to the Codex sandbox. The port comes from the dev-server URL you set during install:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--ports&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;codex&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That expands to an &lt;code dir=&quot;auto&quot;&gt;sbx ports&lt;/code&gt; call against the Codex sandbox, for example &lt;code dir=&quot;auto&quot;&gt;sbx ports ralph-codex-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt; --publish 3000:3000&lt;/code&gt;. Per-agent names keep state separate: your Codex sandbox and your Claude sandbox never share credentials or installed tools, so you can run both against the same repo without one clobbering the other.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-3-create-a-prd-and-task-list&quot;&gt;Step 3: Create a PRD and task list&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The loop executes a task list. It does not invent one. Before you run anything, turn your requirements into a PRD and a set of task specs with the &lt;code dir=&quot;auto&quot;&gt;prd-creator&lt;/code&gt; skill.&lt;/p&gt;
&lt;p&gt;Open Codex (or any agent) in plan mode and prompt it with your requirements:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Use the prd-creator skill to help me create a PRD and task list for the below requirements.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;An app is already set up with React, Tailwind CSS and TypeScript.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Requirements:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- A SaaS product that helps users manage their finances.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- Track income and expenses, create invoices, generate reports.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- Use shadcn/ui for components and Supabase for the database.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// etc.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The skill writes &lt;code dir=&quot;auto&quot;&gt;.agent/prd/PRD.md&lt;/code&gt;, a short &lt;code dir=&quot;auto&quot;&gt;.agent/prd/SUMMARY.md&lt;/code&gt;, and per-task specs under &lt;code dir=&quot;auto&quot;&gt;.agent/tasks/&lt;/code&gt;, indexed by &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt;. Review each task individually before you start. A clear task list with real acceptance criteria is the single biggest factor in whether the loop succeeds.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-4-run-the-ralph-loop-codex-command&quot;&gt;Step 4: Run the ralph loop codex command&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;With a task list in place and Codex authenticated, start the loop. Claude is the default agent, so name Codex explicitly:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 10 iterations (the default)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;codex&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 50 iterations for a longer unattended run&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;codex&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# exactly one iteration, good for a smoke test&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;codex&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--once&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The short form &lt;code dir=&quot;auto&quot;&gt;-a&lt;/code&gt; works too: &lt;code dir=&quot;auto&quot;&gt;./ralph.sh -a codex -n 5&lt;/code&gt;. You can also cap iterations with &lt;code dir=&quot;auto&quot;&gt;--max-iterations 5&lt;/code&gt;. The count is a safety budget, not a target. The loop stops the moment the work is done.&lt;/p&gt;
&lt;p&gt;Under the hood, Ralph builds a &lt;code dir=&quot;auto&quot;&gt;codex exec&lt;/code&gt; command and runs it inside the sandbox. For &lt;code dir=&quot;auto&quot;&gt;./ralph.sh --agent codex&lt;/code&gt;, the expansion is:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-codex-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;codex&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;exec&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$PROMPT_CONTENT&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Read that right to left. &lt;code dir=&quot;auto&quot;&gt;&quot;$PROMPT_CONTENT&quot;&lt;/code&gt; is the contents of &lt;code dir=&quot;auto&quot;&gt;.agent/PROMPT.md&lt;/code&gt;, the instruction set for one pass. &lt;code dir=&quot;auto&quot;&gt;exec&lt;/code&gt; puts Codex in non-interactive mode. Everything before the second &lt;code dir=&quot;auto&quot;&gt;--&lt;/code&gt; is the sandbox wrapper. That is the entire ralph loop codex command: one headless Codex run per iteration, no session state carried between passes.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;pick-a-model-and-reasoning-effort&quot;&gt;Pick a model and reasoning effort&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Model selection is where the Codex path earns its own section, because Codex is the CLI where Ralph documents real model flags. Everything to the right of Ralph’s own &lt;code dir=&quot;auto&quot;&gt;--&lt;/code&gt; separator is forwarded straight to Codex, inserted right after &lt;code dir=&quot;auto&quot;&gt;exec&lt;/code&gt;. So this:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;codex&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--model&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gpt-5.5&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;expands to:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-codex-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;codex&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;exec&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--model&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gpt-5.5&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$PROMPT_CONTENT&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;And the codex-reasoning variant works the same way:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;codex&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--model&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gpt-5.3-codex&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;which expands to:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-codex-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;codex&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;exec&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--model&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gpt-5.3-codex&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$PROMPT_CONTENT&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The rule to remember: everything left of &lt;code dir=&quot;auto&quot;&gt;--&lt;/code&gt; configures Ralph (agent, iteration count, login), and everything right of &lt;code dir=&quot;auto&quot;&gt;--&lt;/code&gt; configures Codex, landing right after &lt;code dir=&quot;auto&quot;&gt;exec&lt;/code&gt;. Pick the model that fits the run. A heavier reasoning model is worth the token cost on a gnarly refactor where a wrong turn compounds across iterations. A faster model is fine for mechanical task lists where each step is well specified. A practical full command for an overnight run:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;codex&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--model&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gpt-5.3-codex&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Do not guess at other model identifiers. Use the ones your account and CLI version actually expose, and confirm them inside the sandbox before committing to a long run. The two above (&lt;code dir=&quot;auto&quot;&gt;gpt-5.5&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;gpt-5.3-codex&lt;/code&gt;) are the ones Ralph’s own docs show, so they are safe starting points.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-happens-each-iteration&quot;&gt;What happens each iteration&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Every pass is mechanical and identical. Codex reorients from disk, does one task, proves it works, and commits.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Find the highest-priority incomplete task in &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Work the steps in &lt;code dir=&quot;auto&quot;&gt;.agent/tasks/TASK-{ID}.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run tests, linting, and type checking.&lt;/li&gt;
&lt;li&gt;Complete the task, take a screenshot, update the task status, and commit.&lt;/li&gt;
&lt;li&gt;Repeat until all tasks pass or the iteration cap is reached.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The key detail is that each iteration spawns a fresh &lt;code dir=&quot;auto&quot;&gt;codex exec&lt;/code&gt; with a clean context window. Codex does not carry an hours-long transcript from one task to the next. It reads the current state from disk, does one task, and exits. This is why the loop deliberately does not resume a prior Codex session: continuity is a liability here, not a feature.&lt;/p&gt;
&lt;pre dir=&quot;ltr&quot;&gt;flowchart TD
    Start([&quot;./ralph.sh --agent codex -n 50&quot;]) --&gt; Pick[&quot;Pick top task from .agent/tasks.json&quot;]
    Pick --&gt; Spawn[&quot;sbx run codex . -- exec (fresh context)&quot;]
    Spawn --&gt; Work[&quot;codex exec reads state, edits files, runs commands&quot;]
    Work --&gt; Verify[&quot;Run tests, lint, type check, screenshot&quot;]
    Verify --&gt; Commit[&quot;Commit and update task status&quot;]
    Commit --&gt; Check{&quot;Promise tag emitted?&quot;}
    Check --&gt;|&quot;none&quot;| Pick
    Check --&gt;|&quot;COMPLETE&quot;| Done([&quot;exit 0, all tasks done&quot;])
    Check --&gt;|&quot;BLOCKED or DECIDE&quot;| Stop([&quot;exit 2 or 3, wants a human&quot;])&lt;/pre&gt;
&lt;p&gt;The filesystem and git history are the memory layer. Progress lives in &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;.agent/logs/LOG.md&lt;/code&gt;, the per-task specs, and the git log, not in a chat transcript. The prompt that drives every pass lives in &lt;code dir=&quot;auto&quot;&gt;.agent/PROMPT.md&lt;/code&gt;, which defaults to implementation mode and can be swapped for refactor, review, or test mode.&lt;/p&gt;
&lt;p&gt;One rule keeps the whole thing reliable: one task per invocation. Codex completes exactly one task, commits, and stops. It never batches several tasks into a single iteration, which is what keeps each commit small and each diff reviewable.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-the-loop-knows-when-to-stop&quot;&gt;How the loop knows when to stop&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A loop that never ends is a money fire, so Ralph stops on an explicit signal rather than a guess. Codex emits a semantic promise tag in its final message and Ralph reads it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;COMPLETE&amp;#x3C;/promise&gt;&lt;/code&gt; means every task is finished.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;BLOCKED:reason&amp;#x3C;/promise&gt;&lt;/code&gt; means the agent needs human help.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;DECIDE:question&amp;#x3C;/promise&gt;&lt;/code&gt; means it needs a decision you have to make.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those map to exit codes, which is what makes the loop scriptable:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;codex&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;echo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;$?&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt;# 0 COMPLETE, 1 MAX_ITERATIONS, 2 BLOCKED, 3 DECIDE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;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. If Codex is not authenticated when the loop starts, Ralph detects the auth failure, stops, and tells you to run &lt;code dir=&quot;auto&quot;&gt;codex login&lt;/code&gt;, so it never thrashes on a box that can never make progress.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;steer-debug-and-review&quot;&gt;Steer, debug, and review&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Three things keep a long Codex run under control.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;steer-without-stopping&quot;&gt;Steer without stopping&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;If you notice Codex heading the wrong way, you do not have to kill the loop. Edit &lt;code dir=&quot;auto&quot;&gt;.agent/STEERING.md&lt;/code&gt; while it runs. Codex checks that file on each pass and handles the critical work there before resuming the normal task order. That is steering, not stopping, and it keeps momentum while you correct course.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;get-inside-the-sandbox&quot;&gt;Get inside the sandbox&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The sandbox is a normal container you can poke at. List what exists, then open a shell in the Codex box:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ls&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;exec&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-it&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-codex-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bash&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;From there you can re-run a failing test by hand, run &lt;code dir=&quot;auto&quot;&gt;codex&lt;/code&gt; interactively to confirm auth, or read &lt;code dir=&quot;auto&quot;&gt;.agent/logs/LOG.md&lt;/code&gt; and the per-iteration logs in &lt;code dir=&quot;auto&quot;&gt;.agent/history/&lt;/code&gt;. To reattach to the running box directly, use &lt;code dir=&quot;auto&quot;&gt;sbx run ralph-codex-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/code&gt; with no agent slug and no path. When an install fails, the deny-by-default network is usually the cause. Allow the domain it needs:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;policy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;allow&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;network&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-codex-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;registry.npmjs.org&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That is a feature, not a bug. Codex installs what the task needs without a path to exfiltrate your source. If you want the broader argument for why the sandbox is the boundary, &lt;a href=&quot;https://ralphloop.sh/blog/run-ai-coding-agent-overnight/&quot;&gt;running an AI coding agent overnight&lt;/a&gt; covers the safety model end to end.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;review-the-diff-in-the-morning&quot;&gt;Review the diff in the morning&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Because every task is committed separately, the morning review is a git review, not an archaeology dig:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--oneline&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;diff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main...HEAD&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;You read the commits in order, eyeball the screenshots Codex captured, and accept or revert. A single bad task is one revert, not a tangled mess. The verification stack Ralph assumes (Playwright for end-to-end, Vitest for unit tests, TypeScript for types, ESLint for lint, Prettier for format) is what gives each commit evidence behind it. The repo mantra holds: if you did not test it, it did not work.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;should-you-use-codex-or-another-agent&quot;&gt;Should you use Codex or another agent?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Ralph is agent agnostic. The supported agents are &lt;code dir=&quot;auto&quot;&gt;claude&lt;/code&gt; (default), &lt;code dir=&quot;auto&quot;&gt;codex&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;copilot&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;cursor&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;gemini&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;opencode&lt;/code&gt;, and switching is a single flag. The loop machinery is identical across all of them. What sets Codex apart is the interface: it is the one agent Ralph drives through the &lt;code dir=&quot;auto&quot;&gt;exec&lt;/code&gt; subcommand rather than a print flag, and the one where model selection is a documented, first-class part of the command. If you already run OpenAI models and want explicit control over which model and how much reasoning each run gets, Codex is a natural fit. If you want the pure shell mechanics behind all of this, read &lt;a href=&quot;https://ralphloop.sh/blog/ralph-loop-shell-script/&quot;&gt;how Ralph is a shell script&lt;/a&gt;, and for the Cursor equivalent of this walkthrough, see &lt;a href=&quot;https://ralphloop.sh/blog/ralph-loop-with-cursor-cli/&quot;&gt;running a Ralph loop with the Cursor CLI&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A real Codex loop, start to finish, is four commands:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 1. install Ralph and scaffolding&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@pageai/ralph-loop&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 2. authenticate once (creates the sandbox, you log in inside it)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--login&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;codex&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 3. smoke test a single pass&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;codex&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--once&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 4. run the loop for real with a model&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;codex&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--model&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gpt-5.3-codex&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Define your tasks in &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt;, write a clear &lt;code dir=&quot;auto&quot;&gt;.agent/PROMPT.md&lt;/code&gt;, pick a model, start the loop, and read the commits in the morning. The Ralph technique itself, fresh context plus disk-backed memory plus an explicit stop signal, was popularized by Geoffrey Huntley in &lt;a href=&quot;https://ghuntley.com/ralph/&quot;&gt;his original writeup&lt;/a&gt;, and Codex slots into it cleanly through one non-interactive subcommand.&lt;/p&gt;
&lt;section aria-label=&quot;Frequently asked questions&quot;&gt; &lt;h2&gt;Frequently asked questions&lt;/h2&gt; &lt;div&gt; &lt;h3&gt;How do I run a Ralph loop with the Codex CLI?&lt;/h3&gt; &lt;p&gt;Install Ralph with npx @pageai/ralph-loop, create a task list, log in with ./ralph.sh --login --agent codex, then run ./ralph.sh --agent codex. Ralph drives Codex through its non-interactive exec subcommand inside a Docker sandbox, starts a fresh context each iteration, and repeats until every task in .agent/tasks.json is done or the iteration cap is reached.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;Why does Ralph use codex exec instead of a print flag?&lt;/h3&gt; &lt;p&gt;exec is Codex CLI non-interactive mode. It reads a prompt, edits files, runs commands, prints a final message, and exits with a status code, with no REPL or approval prompt waiting on a human. In scripts/lib/agents.sh the Codex case expands to sbx run codex . -- exec &quot;$PROMPT_CONTENT&quot;, which is the exact behavior a loop needs.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;How do I pass a model to Codex through Ralph?&lt;/h3&gt; &lt;p&gt;Put it after the -- separator. Anything to the right of -- is forwarded to Codex and inserted right after exec, so ./ralph.sh --agent codex -- --model gpt-5.5 runs codex exec with that model and the Ralph prompt. The docs also show ./ralph.sh --agent codex -- --model gpt-5.3-codex. Confirm the exact identifiers your account exposes before a long run.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;How do I log in to Codex for the loop?&lt;/h3&gt; &lt;p&gt;Run ./ralph.sh --login --agent codex. It drops you into the sandbox shell where you run codex login, or you can set an OPENAI_API_KEY environment variable that Docker Sandboxes forwards when the box is created. The credential persists in the sandbox named ralph-codex-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;, so future runs attach already authenticated.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;What does the ralph loop codex exit code mean?&lt;/h3&gt; &lt;p&gt;Codex emits a promise tag that Ralph maps to an exit code: 0 for COMPLETE when all tasks finish, 1 for MAX_ITERATIONS when the budget runs out with work pending, 2 for BLOCKED when it needs help, and 3 for DECIDE when it needs a decision. Exit code 1 is not a failure, it just means the safety cap did its job.&lt;/p&gt; &lt;/div&gt; &lt;/section&gt; 
&lt;aside aria-label=&quot;Get started with Ralph Loop&quot;&gt; &lt;h2&gt;Run your own Ralph loop&lt;/h2&gt; &lt;p&gt;Ralph is a hackable script you point at your project. Install it and let an agent work through your task list.&lt;/p&gt; &lt;pre&gt;&lt;code&gt;npx @pageai/ralph-loop&lt;/code&gt;&lt;/pre&gt; &lt;div&gt; &lt;a href=&quot;https://www.npmjs.com/package/@pageai/ralph-loop&quot;&gt;
Install from npm
&lt;/a&gt; &lt;a href=&quot;https://github.com/pageai-pro/ralph-loop&quot;&gt;Star on GitHub&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=3TL8Ez66I3o&quot;&gt;Watch the walkthrough&lt;/a&gt; &lt;/div&gt; &lt;/aside&gt;</content:encoded><category>ralph-technique</category></item><item><title>How to Run a Ralph Loop With the Gemini CLI</title><link>https://ralphloop.sh/blog/ralph-loop-with-gemini-cli/</link><guid isPermaLink="true">https://ralphloop.sh/blog/ralph-loop-with-gemini-cli/</guid><description>A full end-to-end walkthrough for running the Ralph loop with the Gemini CLI: install, log in inside the sandbox, loop on your task list, choose a model, and review the commits.

</description><pubDate>Sat, 20 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A ralph loop gemini cli setup is the cheapest way to keep an agent working for hours: install Ralph into your project, log in to Gemini once inside its sandbox, then run &lt;code dir=&quot;auto&quot;&gt;./ralph.sh --agent gemini&lt;/code&gt;. Ralph feeds the Gemini CLI a fresh context every iteration and keeps re-running it against your task list until the work is done or the iteration cap runs out. The reason to reach for Gemini specifically is money. A long unattended run is not one prompt, it is dozens, and the per-iteration model cost compounds across every pass.&lt;/p&gt;
&lt;p&gt;This is the full recipe, from an empty repo to a reviewed diff, with cost as the through line. If the loop itself is new to you, start with &lt;a href=&quot;https://ralphloop.sh/blog/what-is-the-ralph-technique/&quot;&gt;what the Ralph technique is&lt;/a&gt;. If you want the flag-level internals of the Gemini backend, the companion piece is &lt;a href=&quot;https://ralphloop.sh/blog/run-gemini-cli-in-a-loop/&quot;&gt;running the Gemini CLI in a loop&lt;/a&gt;. This post is the branded, end-to-end setup.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-the-gemini-cli-is-the-economical-pick-for-a-long-loop&quot;&gt;Why the Gemini CLI is the economical pick for a long loop&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Think about what a long run actually is. The loop does not spend one model call and stop. It spends one call per task, and it keeps going until the task list is empty. Run fifty iterations overnight and you paid for fifty separate agent sessions, each reading state, editing files, running tests, and writing a commit. That is the arithmetic that matters: total cost equals the per-iteration cost multiplied by the number of iterations you allow.&lt;/p&gt;
&lt;p&gt;Two properties make the Gemini CLI attractive for that arithmetic.&lt;/p&gt;
&lt;p&gt;First, context size. Each Ralph iteration starts the agent from a clean context window, so it re-reads the task spec, the summary, and the relevant files every single pass. A model with a large context window swallows that reorientation without truncating, which keeps each pass from thrashing on a half-loaded picture of the repo. Fresh context is the point of Ralph, and a roomy window makes fresh context cheap to fill.&lt;/p&gt;
&lt;p&gt;Second, model choice you can actually name. Ralph forwards agent-specific flags after its own &lt;code dir=&quot;auto&quot;&gt;--&lt;/code&gt; separator, and the &lt;a href=&quot;https://github.com/danmindru/ralph-loop&quot;&gt;project README&lt;/a&gt; documents &lt;code dir=&quot;auto&quot;&gt;./ralph.sh -a gemini -- --model pro&lt;/code&gt; as a real example. So you get an explicit lever: run the standard model for routine tasks, and pass &lt;code dir=&quot;auto&quot;&gt;--model pro&lt;/code&gt; when a batch of tasks needs more reasoning. You decide where the money goes instead of hoping a router guesses right.&lt;/p&gt;
&lt;p&gt;Put those together and the emphasis of this whole post falls out: cost and context over a long unattended run. The iteration cap is not just a safety rail against runaway loops, it is your budget dial. Set &lt;code dir=&quot;auto&quot;&gt;-n&lt;/code&gt; and you have set a hard ceiling on how many model calls the run can make before it stops on its own.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;cap-iterations-to-cap-spend&quot;&gt;Cap iterations to cap spend&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The iteration count is the single most important number for cost control, so treat it as a budget, not a target.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 10 iterations (the default)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gemini&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# a tight budget: exactly one pass, good for a smoke test&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gemini&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--once&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# a small capped batch&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gemini&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--max-iterations&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# a long overnight run with a firm ceiling&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gemini&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Every one of those numbers is a spending limit. &lt;code dir=&quot;auto&quot;&gt;--once&lt;/code&gt; buys exactly one model call. &lt;code dir=&quot;auto&quot;&gt;-n 50&lt;/code&gt; says the run may cost at most fifty passes before Ralph exits on its own, whether or not the task list is finished. The loop also stops early the moment the work is genuinely done, so you often pay for fewer iterations than the cap allows. You never pay for more.&lt;/p&gt;
&lt;p&gt;This is why exit code 1 is not a failure (more on codes below). It means the run spent its budget with work still pending, which is exactly what a ceiling is for. Raise the cap and continue tomorrow, or shrink the task list, but you were never at risk of an open-ended bill.&lt;/p&gt;
&lt;p&gt;The short form &lt;code dir=&quot;auto&quot;&gt;-a&lt;/code&gt; works everywhere the long &lt;code dir=&quot;auto&quot;&gt;--agent&lt;/code&gt; does, so &lt;code dir=&quot;auto&quot;&gt;./ralph.sh -a gemini -n 50&lt;/code&gt; is the same run. Pick the number that matches how much you are willing to spend while you sleep, then walk away.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;install-ralph&quot;&gt;Install Ralph&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Run the installer in your project root. It drops the &lt;code dir=&quot;auto&quot;&gt;ralph.sh&lt;/code&gt; script and the &lt;code dir=&quot;auto&quot;&gt;.agent/&lt;/code&gt; directory into the repo.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@pageai/ralph-loop&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The installer asks for your dev server URL and port and your default agent, then writes a default implementation-mode setup. Pick &lt;code dir=&quot;auto&quot;&gt;gemini&lt;/code&gt; when prompted, or set it per run with &lt;code dir=&quot;auto&quot;&gt;--agent gemini&lt;/code&gt; later. If the script is not executable yet, fix that:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;chmod&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+x&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph.sh&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Ralph runs each agent inside a Docker Sandbox, so install and sign in to Docker Sandboxes first. See the &lt;a href=&quot;https://docs.docker.com/ai/sandboxes/&quot;&gt;Docker Sandboxes docs&lt;/a&gt; for the official setup flow. The loop drives the standalone &lt;code dir=&quot;auto&quot;&gt;sbx&lt;/code&gt; CLI under the hood, and the sandbox is what lets Gemini edit files and run commands without that reach touching your real machine.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;create-a-prd-and-task-list&quot;&gt;Create a PRD and task list&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The loop executes a task list. It does not invent one. Before you run anything, turn your requirements into a PRD and a set of task specs with the &lt;code dir=&quot;auto&quot;&gt;prd-creator&lt;/code&gt; skill. This step is also a cost decision: a vague task list makes Gemini flail across extra iterations, and every wasted iteration is money.&lt;/p&gt;
&lt;p&gt;Open any agent in plan mode and prompt it with your requirements:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Use the prd-creator skill to help me create a PRD and task list for the below requirements.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;An app is already set up with React, Tailwind CSS and TypeScript.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Requirements:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- A SaaS product that helps users manage their finances.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- Track income and expenses, create invoices, generate reports.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- Use shadcn/ui for components and Supabase for the database.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// etc.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The skill writes &lt;code dir=&quot;auto&quot;&gt;.agent/prd/PRD.md&lt;/code&gt;, a short &lt;code dir=&quot;auto&quot;&gt;.agent/prd/SUMMARY.md&lt;/code&gt;, and per-task specs under &lt;code dir=&quot;auto&quot;&gt;.agent/tasks/&lt;/code&gt;, indexed by &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt;. Review each task individually before you start. A clear task list with real acceptance criteria is the single biggest factor in whether the loop succeeds, and the fewer dead-end passes you buy. The deeper version of that workflow is in &lt;a href=&quot;https://ralphloop.sh/blog/spec-driven-development-with-ai/&quot;&gt;spec-driven development with AI&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;log-in-to-gemini-inside-the-sandbox&quot;&gt;Log in to Gemini inside the sandbox&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Gemini runs inside an isolated sandbox, not on your host, so it needs credentials in that environment. Authenticate once with the login action:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--login&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gemini&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Ralph prints the login command for every supported agent, highlights the Gemini one, and drops you into the sandbox shell. The command it runs to create the box looks like this:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-gemini-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gemini&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Inside the sandbox, complete the Gemini sign-in flow and follow the prompt. If a run later fails on auth, Ralph surfaces a hint that points at &lt;code dir=&quot;auto&quot;&gt;gemini auth&lt;/code&gt;, which tells you the box has no valid session. The credential persists in that named sandbox, so later runs attach to the same box already logged in.&lt;/p&gt;
&lt;p&gt;An API key is an alternative to interactive login, and it matters for cost math because Docker Sandboxes only forward environment variables when a sandbox is created. If you go the key route, delete the sandbox first, set your provider API key in your shell before recreating it, then run &lt;code dir=&quot;auto&quot;&gt;./ralph.sh --login --agent gemini&lt;/code&gt; again so the fresh sandbox picks the value up. The README’s authentication section describes this create-time forwarding pattern in full. Interactive login is usually the simpler path.&lt;/p&gt;
&lt;p&gt;Each agent gets its own deterministic sandbox name, built from the agent slug, the project directory, and an 8-character hash of the absolute path:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ralph-gemini-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Print the exact name for your project without starting a run:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--print-name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gemini&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If your tasks run a dev server you want to reach from the host, publish the configured port to the Gemini sandbox. The port comes from the dev-server URL you set during install:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--ports&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gemini&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That expands to an &lt;code dir=&quot;auto&quot;&gt;sbx ports&lt;/code&gt; call against the Gemini sandbox, for example &lt;code dir=&quot;auto&quot;&gt;sbx ports ralph-gemini-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt; --publish 3000:3000&lt;/code&gt;. Per-agent names keep state separate: your Gemini sandbox and your Claude sandbox never share credentials or installed tools, so you can trial Gemini on a repo without disturbing another agent’s box.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;run-the-loop-with-gemini&quot;&gt;Run the loop with Gemini&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;With a task list in place and Gemini authenticated, start the loop. The default agent is Claude, so name Gemini explicitly:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gemini&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Under the hood, Ralph builds a Gemini CLI command and runs it inside the sandbox. For a first run that creates the box, the expansion is:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-gemini-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gemini&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-p&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$PROMPT_CONTENT&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;-p&lt;/code&gt; flag is Gemini’s non-interactive prompt mode, which still has full tool access including file writes and shell. That is exactly what a loop needs: an agent that can do real work without a person approving each step.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;pass-the-model-after-the-separator&quot;&gt;Pass the model after the separator&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Everything to the right of Ralph’s own &lt;code dir=&quot;auto&quot;&gt;--&lt;/code&gt; separator is forwarded straight to the Gemini CLI, and Ralph inserts those extra args right before &lt;code dir=&quot;auto&quot;&gt;-p&lt;/code&gt;. So the README example:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gemini&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--model&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pro&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;expands to:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-gemini-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gemini&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--model&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pro&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-p&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$PROMPT_CONTENT&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That ordering is the mechanism to remember: the flags you pass land before the prompt flag, so the Gemini CLI sees your model choice and then the prompt. &lt;code dir=&quot;auto&quot;&gt;--model pro&lt;/code&gt; is the model the README documents for Gemini, so it is a safe lever to pull when a run needs stronger reasoning. Do not guess at other identifiers. Reach for the standard model on cheap, mechanical tasks and reserve &lt;code dir=&quot;auto&quot;&gt;pro&lt;/code&gt; for the passes that earn it, because that split is where a long run’s cost is won or lost.&lt;/p&gt;
&lt;p&gt;The rule of the separator holds in general: everything left of &lt;code dir=&quot;auto&quot;&gt;--&lt;/code&gt; configures Ralph (agent, iteration count, login), and everything right of &lt;code dir=&quot;auto&quot;&gt;--&lt;/code&gt; configures Gemini. A practical full command for an overnight run:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gemini&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--model&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pro&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;what-each-iteration-buys&quot;&gt;What each iteration buys&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Every pass is mechanical and identical. The agent reorients from disk, does one task, proves it works, and commits. One iteration is one unit of spend, so it helps to know exactly what that unit does.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Find the highest-priority incomplete task in &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Work the steps in &lt;code dir=&quot;auto&quot;&gt;.agent/tasks/TASK-{ID}.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run tests, linting, and type checking.&lt;/li&gt;
&lt;li&gt;Complete the task, take a screenshot, update the task status, and commit.&lt;/li&gt;
&lt;li&gt;Repeat until all tasks pass or the iteration cap is reached.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The key detail is that each iteration spawns a fresh Gemini process with a clean context window. The agent does not carry an hours-long transcript from one task to the next. It reads the current state from disk, does one task, and exits. This is why the loop deliberately does not reuse a running session: continuity is a liability here, not a feature, and a fresh window avoids paying to re-send a transcript that has drifted off the plot.&lt;/p&gt;
&lt;pre dir=&quot;ltr&quot;&gt;flowchart TD
    Start([&quot;./ralph.sh --agent gemini -n 50&quot;]) --&gt; Budget{&quot;Iterations left in budget?&quot;}
    Budget --&gt;|&quot;no&quot;| Cap([&quot;exit 1, budget spent&quot;])
    Budget --&gt;|&quot;yes&quot;| Pick[&quot;Pick top task from .agent/tasks.json&quot;]
    Pick --&gt; Spawn[&quot;sbx run gemini . -- --model pro -p (fresh context)&quot;]
    Spawn --&gt; Work[&quot;Gemini reads state, edits files, runs commands&quot;]
    Work --&gt; Verify[&quot;Run tests, lint, type check, screenshot&quot;]
    Verify --&gt; Commit[&quot;Commit and update task status&quot;]
    Commit --&gt; Check{&quot;Promise tag emitted?&quot;}
    Check --&gt;|&quot;none&quot;| Budget
    Check --&gt;|&quot;COMPLETE&quot;| Done([&quot;exit 0, all tasks done&quot;])
    Check --&gt;|&quot;BLOCKED or DECIDE&quot;| Stop([&quot;exit 2 or 3, wants a human&quot;])&lt;/pre&gt;
&lt;p&gt;The filesystem and git history are the memory layer. Progress lives in &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;.agent/logs/LOG.md&lt;/code&gt;, the per-task specs, and the git log, not in a chat transcript. The prompt that drives every pass lives in &lt;code dir=&quot;auto&quot;&gt;.agent/PROMPT.md&lt;/code&gt;, and writing it well is its own skill, covered in &lt;a href=&quot;https://ralphloop.sh/blog/ralph-loop-prompt-file/&quot;&gt;how to write the PROMPT.md file&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;One rule keeps the whole thing reliable and cheap: one task per invocation. Gemini completes exactly one task, commits, and stops. It never batches several tasks into a single pass, which keeps each commit small, each diff reviewable, and each unit of spend accountable to a single piece of work.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;stop-signals-promises-and-exit-codes&quot;&gt;Stop signals: promises and exit codes&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A loop that never ends is a money fire, so Ralph stops on an explicit signal rather than a guess. Gemini emits a semantic promise tag in its final message and Ralph reads it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;COMPLETE&amp;#x3C;/promise&gt;&lt;/code&gt; means every task is finished.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;BLOCKED:reason&amp;#x3C;/promise&gt;&lt;/code&gt; means the agent needs human help.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;DECIDE:question&amp;#x3C;/promise&gt;&lt;/code&gt; means it needs a decision you have to make.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those map to exit codes, which is what makes the loop scriptable and its spend legible after the fact:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gemini&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;echo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;$?&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt;# 0 COMPLETE, 1 MAX_ITERATIONS, 2 BLOCKED, 3 DECIDE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Exit code 1 means the loop spent its iteration budget with work still pending, which is the expected outcome of a firm cap, not a bug. Codes 2 and 3 mean Gemini stopped early and wants a human, so you did not burn the rest of the budget on a task the agent could not finish alone. The full design lives in &lt;a href=&quot;https://ralphloop.sh/blog/ralph-loop-completion-promise/&quot;&gt;completion promises and exit codes&lt;/a&gt;. If Gemini is not authenticated when the loop starts, Ralph detects the auth failure, stops, and points you back at &lt;code dir=&quot;auto&quot;&gt;./ralph.sh --login --agent gemini&lt;/code&gt;, so it never thrashes (and never spends) on a box that can never make progress.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;steer-inspect-and-review&quot;&gt;Steer, inspect, and review&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Three habits keep a long Gemini run under control and keep you from paying twice for the same work.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;steer-without-stopping&quot;&gt;Steer without stopping&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;If you notice the agent heading the wrong way, do not kill the loop and restart it, because a restart pays for a cold start again. Edit &lt;code dir=&quot;auto&quot;&gt;.agent/STEERING.md&lt;/code&gt; while it runs. The agent checks that file on each pass and handles the critical work there before resuming the normal task order. That is steering, not stopping, and it corrects course without wasting the budget you already committed.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;get-inside-the-sandbox&quot;&gt;Get inside the sandbox&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The sandbox is a normal container you can poke at. List what exists, then open a shell in the Gemini box:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ls&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;exec&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-it&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-gemini-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bash&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;From there you can re-run a failing test by hand, confirm the session is valid, or read &lt;code dir=&quot;auto&quot;&gt;.agent/logs/LOG.md&lt;/code&gt; and the per-iteration logs in &lt;code dir=&quot;auto&quot;&gt;.agent/history/&lt;/code&gt;. When an install fails, the deny-by-default network is usually the cause. Allow the domain it needs:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;policy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;allow&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;network&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-gemini-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;registry.npmjs.org&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That is a feature, not a limitation. The agent installs what the task needs without a path to exfiltrate your source. The full argument is in &lt;a href=&quot;https://ralphloop.sh/blog/run-ai-agents-in-docker-sandboxes-safely/&quot;&gt;running AI coding agents in Docker sandboxes safely&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;review-the-diff-in-the-morning&quot;&gt;Review the diff in the morning&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Because every task is committed separately, the morning review is a git review, not an archaeology dig:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--oneline&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;diff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main...HEAD&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;You read the commits in order, eyeball the screenshots the agent captured, and accept or revert. A single bad task is one revert, not a tangled mess, so a wasted iteration costs you one small commit rather than the whole night’s work. This is the whole premise of &lt;a href=&quot;https://ralphloop.sh/blog/run-ai-coding-agent-overnight/&quot;&gt;running an AI coding agent overnight&lt;/a&gt;, and the broader money discipline is in &lt;a href=&quot;https://ralphloop.sh/blog/ai-agent-cost-control/&quot;&gt;controlling AI agent cost&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;gemini-or-another-agent&quot;&gt;Gemini or another agent?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Ralph is agent agnostic. The supported agents are &lt;code dir=&quot;auto&quot;&gt;claude&lt;/code&gt; (default), &lt;code dir=&quot;auto&quot;&gt;codex&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;copilot&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;cursor&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;gemini&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;opencode&lt;/code&gt;, and switching is a single flag. The loop machinery is byte-for-byte identical across all of them, so the choice comes down to the economics of a multi-hour run: how much context each model can hold on a fresh start, and what each pass costs you. Gemini earns its place when you want a large context window and an explicit &lt;code dir=&quot;auto&quot;&gt;--model pro&lt;/code&gt; lever over a long, capped run. The shell-script guts that make the loop hackable are in &lt;a href=&quot;https://ralphloop.sh/blog/ralph-loop-shell-script/&quot;&gt;the Ralph loop shell script&lt;/a&gt;, and the branded sibling recipe is &lt;a href=&quot;https://ralphloop.sh/blog/ralph-loop-with-cursor-cli/&quot;&gt;running a Ralph loop with the Cursor CLI&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A real Gemini loop, start to finish, is four commands:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 1. install Ralph and scaffolding&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@pageai/ralph-loop&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 2. authenticate once (creates the sandbox, you log in inside it)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--login&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gemini&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 3. smoke test a single pass (one unit of spend)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gemini&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--once&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 4. run the loop for real with a model and a firm budget&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gemini&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--model&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pro&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Define your tasks in &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt;, write a clear &lt;code dir=&quot;auto&quot;&gt;.agent/PROMPT.md&lt;/code&gt;, set the iteration cap to a number you are comfortable paying for, start the loop, and read the commits in the morning. The Ralph technique itself traces back to Geoffrey Huntley’s original writeup, &lt;a href=&quot;https://ghuntley.com/ralph/&quot;&gt;Ralph is a Bash loop&lt;/a&gt;, and everything above is that idea wired to the Gemini CLI inside a sandbox.&lt;/p&gt;
&lt;section aria-label=&quot;Frequently asked questions&quot;&gt; &lt;h2&gt;Frequently asked questions&lt;/h2&gt; &lt;div&gt; &lt;h3&gt;How do I run a Ralph loop with the Gemini CLI?&lt;/h3&gt; &lt;p&gt;Install Ralph with npx @pageai/ralph-loop, create a task list, log in with ./ralph.sh --login --agent gemini, then run ./ralph.sh --agent gemini. Ralph runs the Gemini CLI in non-interactive prompt mode inside a Docker sandbox, starts a fresh context each iteration, and repeats until every task in .agent/tasks.json is done or the iteration cap is reached.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;Why choose Gemini for a long unattended loop?&lt;/h3&gt; &lt;p&gt;Cost and context. A long run is many iterations, and the per-iteration model cost compounds across every pass, so the model you pick matters. Gemini offers a large context window that absorbs the fresh-context reorientation each iteration needs, and Ralph lets you name the model with --model pro when a batch of tasks needs stronger reasoning.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;How do I control cost when looping Gemini?&lt;/h3&gt; &lt;p&gt;Use the iteration cap as a budget. ./ralph.sh --agent gemini --once buys exactly one pass, -n 50 caps the run at fifty passes, and the loop still stops early when the work is done so you often pay for fewer. Exit code 1 means the budget was spent with work still pending, which is the cap doing its job.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;How do I pass a Gemini model through Ralph?&lt;/h3&gt; &lt;p&gt;Put it after the -- separator. Anything to the right of -- is forwarded to the Gemini CLI and inserted just before the prompt flag, so ./ralph.sh -a gemini -- --model pro expands to sbx run --name ralph-gemini-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt; gemini . -- --model pro -p with the Ralph prompt. --model pro is the model documented in the README.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;How do I log in to Gemini for the loop?&lt;/h3&gt; &lt;p&gt;Run ./ralph.sh --login --agent gemini. It creates the sandbox and drops you into its shell to complete the Gemini sign-in. If a run later fails on auth, Ralph points you back at gemini auth. As an alternative you can set your provider API key in your shell before recreating the sandbox, since Docker Sandboxes only forward environment variables at create time.&lt;/p&gt; &lt;/div&gt; &lt;/section&gt; 
&lt;aside aria-label=&quot;Get started with Ralph Loop&quot;&gt; &lt;h2&gt;Run your own Ralph loop&lt;/h2&gt; &lt;p&gt;Ralph is a hackable script you point at your project. Install it and let an agent work through your task list.&lt;/p&gt; &lt;pre&gt;&lt;code&gt;npx @pageai/ralph-loop&lt;/code&gt;&lt;/pre&gt; &lt;div&gt; &lt;a href=&quot;https://www.npmjs.com/package/@pageai/ralph-loop&quot;&gt;
Install from npm
&lt;/a&gt; &lt;a href=&quot;https://github.com/pageai-pro/ralph-loop&quot;&gt;Star on GitHub&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=3TL8Ez66I3o&quot;&gt;Watch the walkthrough&lt;/a&gt; &lt;/div&gt; &lt;/aside&gt;</content:encoded><category>ralph-technique</category></item><item><title>How to Run a Ralph Loop With the GitHub Copilot CLI</title><link>https://ralphloop.sh/blog/ralph-loop-with-copilot-cli/</link><guid isPermaLink="true">https://ralphloop.sh/blog/ralph-loop-with-copilot-cli/</guid><description>A full end-to-end walkthrough for running the Ralph loop with the GitHub Copilot CLI: install, authenticate with gh inside the sandbox, loop on your task list, and review the commits.

</description><pubDate>Wed, 17 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A Ralph loop with Copilot runs the GitHub Copilot CLI on your task list in a fresh context every iteration, and the one detail that makes it different from every other agent is auth: Copilot signs in through the GitHub CLI (&lt;code dir=&quot;auto&quot;&gt;gh auth login&lt;/code&gt;), not an agent-specific login. Install Ralph into your repo, authenticate with &lt;code dir=&quot;auto&quot;&gt;gh&lt;/code&gt; inside the sandbox, then run &lt;code dir=&quot;auto&quot;&gt;./ralph.sh --agent copilot&lt;/code&gt;. Ralph re-runs Copilot against &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt; until the work is done or you hit the iteration cap.&lt;/p&gt;
&lt;p&gt;This is the full recipe, from an empty repo to a reviewable diff. If the loop itself is new to you, start with &lt;a href=&quot;https://ralphloop.sh/blog/what-is-the-ralph-technique/&quot;&gt;what the Ralph technique is&lt;/a&gt;. If you want a wider look at terminal agents before you commit, the field guide is &lt;a href=&quot;https://ralphloop.sh/blog/agentic-coding-clis/&quot;&gt;agentic coding CLIs&lt;/a&gt;. This post is the canonical Ralph plus Copilot page: everything below is the end-to-end setup.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-run-a-ralph-loop-with-copilot&quot;&gt;Why run a Ralph loop with Copilot?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Copilot is the GitHub-native agent. Its CLI runs from the terminal, reads a prompt, edits files, runs shell commands, prints a final message, and exits. That clean exit is the hook a loop needs. Each iteration is a discrete unit of work: spawn the agent, let it do one task, read what it emitted, repeat.&lt;/p&gt;
&lt;p&gt;The reason to pick Copilot over another CLI is almost never the model. It is the account. If your team already standardized on GitHub, your identity, your seats, your billing, and your permissions already live there. Copilot authenticates through &lt;code dir=&quot;auto&quot;&gt;gh&lt;/code&gt;, so the loop rides on the same GitHub login your engineers use every day. There is no separate vendor account to provision, no extra key to rotate, no new invoice to reconcile. For a shop that runs everything through GitHub, that is one less system to babysit.&lt;/p&gt;
&lt;p&gt;Ralph supplies the three things a single Copilot CLI call cannot:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A loop, so the agent gets another pass after every commit instead of stopping cold.&lt;/li&gt;
&lt;li&gt;Fresh context per iteration, so a long run never rots into a confused, drifting session.&lt;/li&gt;
&lt;li&gt;A Docker sandbox boundary, so the agent edits files and runs commands in bypass-permissions mode without that blast radius reaching your real machine.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;State lives on disk (the task list, the logs, the git history), not in a chat transcript. That is what lets a stateless Copilot invocation reorient on every pass and still make forward progress across dozens of iterations. The original argument for why a plain Bash loop beats a clever agent runtime is in Geoffrey Huntley’s &lt;a href=&quot;https://ghuntley.com/ralph/&quot;&gt;writeup on Ralph&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-1-install-ralph&quot;&gt;Step 1: Install Ralph&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Run the installer in your project root. It drops the &lt;code dir=&quot;auto&quot;&gt;ralph.sh&lt;/code&gt; script and the &lt;code dir=&quot;auto&quot;&gt;.agent/&lt;/code&gt; directory into the repo.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@pageai/ralph-loop&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The installer asks for your dev server URL and port and your default agent, then writes a default implementation-mode setup. Pick &lt;code dir=&quot;auto&quot;&gt;copilot&lt;/code&gt; when prompted, or set it per run with &lt;code dir=&quot;auto&quot;&gt;--agent copilot&lt;/code&gt; later. After install, make the script executable if it is not already:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;chmod&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+x&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph.sh&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Ralph runs each agent inside a Docker Sandbox, so install and sign in to Docker Sandboxes first. See the &lt;a href=&quot;https://docs.docker.com/ai/sandboxes/&quot;&gt;Docker Sandboxes docs&lt;/a&gt; for the official setup flow. The loop drives the standalone &lt;code dir=&quot;auto&quot;&gt;sbx&lt;/code&gt; CLI under the hood.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-2-create-a-prd-and-task-list&quot;&gt;Step 2: Create a PRD and task list&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The loop executes a task list. It does not invent one. Before you run anything, turn your requirements into a PRD and a set of task specs with the &lt;code dir=&quot;auto&quot;&gt;prd-creator&lt;/code&gt; skill.&lt;/p&gt;
&lt;p&gt;Open Copilot (or any agent) in plan mode and prompt it with your requirements:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Use the prd-creator skill to help me create a PRD and task list for the below requirements.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;An app is already set up with React, Tailwind CSS and TypeScript.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Requirements:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- A SaaS product that helps users manage their finances.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- Track income and expenses, create invoices, generate reports.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- Use shadcn/ui for components and Supabase for the database.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// etc.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The skill writes &lt;code dir=&quot;auto&quot;&gt;.agent/prd/PRD.md&lt;/code&gt;, a short &lt;code dir=&quot;auto&quot;&gt;.agent/prd/SUMMARY.md&lt;/code&gt;, and per-task specs under &lt;code dir=&quot;auto&quot;&gt;.agent/tasks/&lt;/code&gt;, indexed by &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt;. Review each task individually before you start. A clear task list with real acceptance criteria is the single biggest factor in whether the loop succeeds. Vague tasks produce vague diffs no matter which agent runs them.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-3-authenticate-copilot-through-the-github-cli&quot;&gt;Step 3: Authenticate Copilot through the GitHub CLI&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This is the step that sets Copilot apart. The agent runs inside an isolated sandbox, not on your host, so it needs credentials in that environment. Kick off the login action:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--login&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;copilot&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Ralph prints the login command for every supported agent, highlights the Copilot one, and drops you into the sandbox shell. The command it runs to create the box looks like this:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-copilot-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;copilot&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Inside the sandbox, authenticate with the GitHub CLI. Copilot does not have its own login flow. It leans on &lt;code dir=&quot;auto&quot;&gt;gh&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;gh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;auth&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;login&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Follow the device-code prompt, pick GitHub.com, and pick HTTPS. Once &lt;code dir=&quot;auto&quot;&gt;gh&lt;/code&gt; reports you as logged in, Copilot inside that sandbox can act as you. This is not incidental. Ralph’s own auth-failure detection for Copilot watches for the string &lt;code dir=&quot;auto&quot;&gt;gh auth login&lt;/code&gt; in the agent output, so if the loop starts against an unauthenticated box, it stops and tells you to run exactly that. It never thrashes on a sandbox that can never make progress.&lt;/p&gt;
&lt;p&gt;The credential persists in that named sandbox, so later runs attach to the same box already authenticated. That persistence is the whole point of a deterministic sandbox name. Each agent gets its own name, built from the agent slug, the project directory, and an 8-character hash of the absolute path:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ralph-copilot-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Print the exact name for your project without starting a run:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--print-name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;copilot&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If interactive login through &lt;code dir=&quot;auto&quot;&gt;gh&lt;/code&gt; ever refuses to stick (Docker Sandboxes persist OAuth tokens on a best-effort basis), there is a token-based fallback. Docker Sandboxes forward environment variables from your shell into the box at create time, not after. So you export the credential in your host shell, delete the stale sandbox, and let Ralph recreate it so the new box picks the value up. Keep the specifics to whatever GitHub token your setup expects and do not paste secrets into the repo. The mechanics of that fallback are the same for every agent; only the credential differs.&lt;/p&gt;
&lt;p&gt;If your tasks run a dev server you want to reach from the host, publish the configured port to the Copilot sandbox. The port comes from the dev-server URL you set during install:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--ports&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;copilot&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That expands to an &lt;code dir=&quot;auto&quot;&gt;sbx ports&lt;/code&gt; call against the Copilot sandbox, for example &lt;code dir=&quot;auto&quot;&gt;sbx ports ralph-copilot-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt; --publish 3000:3000&lt;/code&gt;. Per-agent names keep state separate: your Copilot sandbox and your Claude sandbox never share credentials or installed tools, so you can run both against the same repo without one clobbering the other.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-4-run-the-loop-with-copilot&quot;&gt;Step 4: Run the loop with Copilot&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;With a task list in place and Copilot authenticated, start the loop. The default agent is Claude, so name Copilot explicitly:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 10 iterations (the default)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;copilot&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 50 iterations for a longer unattended run&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;copilot&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# exactly one iteration, good for a smoke test&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;copilot&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--once&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The short form &lt;code dir=&quot;auto&quot;&gt;-a&lt;/code&gt; works too: &lt;code dir=&quot;auto&quot;&gt;./ralph.sh -a copilot -n 5&lt;/code&gt;. The &lt;code dir=&quot;auto&quot;&gt;--max-iterations 5&lt;/code&gt; long form is equivalent to &lt;code dir=&quot;auto&quot;&gt;-n 5&lt;/code&gt;. Whatever number you pick is a safety budget, not a target. The loop stops the moment the work is done.&lt;/p&gt;
&lt;p&gt;Under the hood, Ralph builds a Copilot CLI command and runs it inside the sandbox. For &lt;code dir=&quot;auto&quot;&gt;./ralph.sh --agent copilot&lt;/code&gt;, the expansion is:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-copilot-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;copilot&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-p&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$PROMPT_CONTENT&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;-p&lt;/code&gt; flag hands Copilot the Ralph prompt for this iteration. That prompt is the whole contract: it tells the agent to read the current state from disk, pick one task, do it, prove it works, and commit.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;pass-a-model-and-flags-after-the-separator&quot;&gt;Pass a model and flags after the separator&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Everything to the right of Ralph’s own &lt;code dir=&quot;auto&quot;&gt;--&lt;/code&gt; separator is forwarded to Copilot. For Copilot specifically, Ralph inserts those extra args right before &lt;code dir=&quot;auto&quot;&gt;-p&lt;/code&gt;, not after it. So this:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;copilot&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--model&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x3C;your-model&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;expands to:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-copilot-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;copilot&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--model&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x3C;your-model&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-p&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$PROMPT_CONTENT&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Notice the ordering: &lt;code dir=&quot;auto&quot;&gt;--model &amp;#x3C;your-model&gt;&lt;/code&gt; lands before &lt;code dir=&quot;auto&quot;&gt;-p &quot;$PROMPT_CONTENT&quot;&lt;/code&gt;. Do not guess at model identifiers. Pass one your Copilot plan actually supports, and check the Copilot CLI’s own help inside the sandbox if you are unsure which strings it accepts. The rule to remember: everything left of &lt;code dir=&quot;auto&quot;&gt;--&lt;/code&gt; configures Ralph (agent, iteration count, login), and everything right of &lt;code dir=&quot;auto&quot;&gt;--&lt;/code&gt; configures Copilot.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-happens-each-iteration&quot;&gt;What happens each iteration&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Every pass is mechanical and identical. The agent reorients from disk, does one task, proves it works, and commits.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Find the highest-priority incomplete task in &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Work the steps in &lt;code dir=&quot;auto&quot;&gt;.agent/tasks/TASK-{ID}.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run tests, linting, and type checking.&lt;/li&gt;
&lt;li&gt;Complete the task, take a screenshot, update the task status, and commit.&lt;/li&gt;
&lt;li&gt;Repeat until all tasks pass or the iteration cap is reached.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The key detail is that each iteration spawns a fresh Copilot process with a clean context window. The agent does not carry an hours-long transcript from one task to the next. It reads the current state, does one task, and exits. Continuity is a liability in a long run, not a feature, so the loop deliberately starts cold every time.&lt;/p&gt;
&lt;pre dir=&quot;ltr&quot;&gt;flowchart TD
    Start([&quot;./ralph.sh --agent copilot -n 50&quot;]) --&gt; Pick[&quot;Pick top task from .agent/tasks.json&quot;]
    Pick --&gt; Spawn[&quot;sbx run copilot . -- -p (fresh context)&quot;]
    Spawn --&gt; Work[&quot;Copilot reads state, edits files, runs commands&quot;]
    Work --&gt; Verify[&quot;Run tests, lint, type check, screenshot&quot;]
    Verify --&gt; Commit[&quot;Commit and update task status&quot;]
    Commit --&gt; Check{&quot;Promise tag emitted?&quot;}
    Check --&gt;|&quot;none&quot;| Pick
    Check --&gt;|&quot;COMPLETE&quot;| Done([&quot;exit 0, all tasks done&quot;])
    Check --&gt;|&quot;BLOCKED or DECIDE&quot;| Stop([&quot;exit 2 or 3, wants a human&quot;])&lt;/pre&gt;
&lt;p&gt;The filesystem and git history are the memory layer. Progress lives in &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;.agent/logs/LOG.md&lt;/code&gt;, the per-task specs, and the git log, not in a chat transcript. The prompt that drives every pass lives in &lt;code dir=&quot;auto&quot;&gt;.agent/PROMPT.md&lt;/code&gt;, and its default mode is implementation. You can swap it for a refactor, review, or test mode by editing that one file.&lt;/p&gt;
&lt;p&gt;One rule keeps the whole thing reliable: one task per invocation. Copilot completes exactly one task, commits, and stops. It never batches several tasks into a single iteration, which is what keeps each commit small and each diff reviewable.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-the-loop-knows-when-to-stop&quot;&gt;How the loop knows when to stop&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A loop that never ends is a money fire, so Ralph stops on an explicit signal rather than a guess. Copilot emits a semantic promise tag in its final message and Ralph reads it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;COMPLETE&amp;#x3C;/promise&gt;&lt;/code&gt; means every task is finished.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;BLOCKED:reason&amp;#x3C;/promise&gt;&lt;/code&gt; means the agent needs human help.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;DECIDE:question&amp;#x3C;/promise&gt;&lt;/code&gt; means it needs a decision you have to make.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those map to exit codes, which is what makes the loop scriptable:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;copilot&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;echo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;$?&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt;# 0 COMPLETE, 1 MAX_ITERATIONS, 2 BLOCKED, 3 DECIDE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;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. Exit codes 2 and 3 mean Copilot wants a human: either a blocker it cannot clear or a decision only you can make. Wiring those into a CI job or a wrapper script lets you fire off an overnight run and get paged only when the agent genuinely needs you.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;steer-debug-and-review-a-copilot-loop&quot;&gt;Steer, debug, and review a Copilot loop&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Three things keep a long Copilot run under control.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;steer-without-stopping&quot;&gt;Steer without stopping&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;If you notice the agent heading the wrong way, you do not have to kill the loop. Edit &lt;code dir=&quot;auto&quot;&gt;.agent/STEERING.md&lt;/code&gt; while it runs. The agent checks that file on each pass and handles the critical work there before resuming the normal task order. That is steering, not stopping, and it keeps momentum while you correct course.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;get-inside-the-sandbox&quot;&gt;Get inside the sandbox&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The sandbox is a normal container you can poke at. List what exists, then open a shell in the Copilot box:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ls&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;exec&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-it&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-copilot-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bash&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;From there you can re-run a failing test by hand, confirm your GitHub session with &lt;code dir=&quot;auto&quot;&gt;gh auth status&lt;/code&gt;, or read &lt;code dir=&quot;auto&quot;&gt;.agent/logs/LOG.md&lt;/code&gt; and the per-iteration logs in &lt;code dir=&quot;auto&quot;&gt;.agent/history/&lt;/code&gt;. When an install fails, the deny-by-default network is usually the cause. Allow the domain it needs:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;policy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;allow&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;network&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-copilot-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;registry.npmjs.org&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That is a feature, not a bug. The agent installs what the task needs without a path to exfiltrate your source. Reattaching later is safe too: run &lt;code dir=&quot;auto&quot;&gt;sbx run ralph-copilot-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/code&gt; (no agent slug, no path) to drop back into the same box.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;review-the-diff-in-the-morning&quot;&gt;Review the diff in the morning&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Because every task is committed separately, the morning review is a git review, not an archaeology dig:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--oneline&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;diff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main...HEAD&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;You read the commits in order, eyeball the screenshots the agent captured, and accept or revert. A single bad task is one revert, not a tangled mess. This is the whole premise of &lt;a href=&quot;https://ralphloop.sh/blog/run-ai-coding-agent-overnight/&quot;&gt;running an AI coding agent overnight&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;should-you-use-copilot-or-another-agent&quot;&gt;Should you use Copilot or another agent?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Ralph is agent agnostic. The supported agents are &lt;code dir=&quot;auto&quot;&gt;claude&lt;/code&gt; (default), &lt;code dir=&quot;auto&quot;&gt;codex&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;copilot&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;cursor&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;gemini&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;opencode&lt;/code&gt;, and switching is a single flag. The loop machinery is identical across all of them: same task list, same promise tags, same exit codes, same sandbox boundary. The only things that change per agent are the exact CLI invocation and the login flow.&lt;/p&gt;
&lt;p&gt;Copilot is the strong pick when your organization already runs on GitHub and you want the agent to inherit that identity instead of standing up a new one. The GitHub-native auth is the differentiator: &lt;code dir=&quot;auto&quot;&gt;gh auth login&lt;/code&gt; is a command your engineers already know, your GitHub seats already cover the access, and there is no separate account to manage. If you are choosing between backends on the basis of which CLI holds up over a multi-hour run, the head-to-head is in &lt;a href=&quot;https://ralphloop.sh/blog/best-agentic-cli-for-long-running-tasks/&quot;&gt;the best agentic CLI for long-running tasks&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Prefer a different backend? The same end-to-end recipe applies. The setup guide for &lt;a href=&quot;https://ralphloop.sh/blog/ralph-loop-with-cursor-cli/&quot;&gt;running a Ralph loop with the Cursor CLI&lt;/a&gt; covers the Cursor path, and if you want to understand the &lt;code dir=&quot;auto&quot;&gt;ralph.sh&lt;/code&gt; script itself before you trust it with your repo, read &lt;a href=&quot;https://ralphloop.sh/blog/ralph-loop-shell-script/&quot;&gt;the Ralph loop shell script&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A real Copilot loop, start to finish, is four commands:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 1. install Ralph and scaffolding&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@pageai/ralph-loop&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 2. authenticate once (creates the sandbox, you run gh auth login inside it)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--login&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;copilot&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 3. smoke test a single pass&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;copilot&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--once&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 4. run the loop for real, optionally with a model your plan supports&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;copilot&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--model&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x3C;your-model&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Define your tasks in &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt;, write a clear &lt;code dir=&quot;auto&quot;&gt;.agent/PROMPT.md&lt;/code&gt;, sign in with &lt;code dir=&quot;auto&quot;&gt;gh&lt;/code&gt;, start the loop, and read the commits in the morning.&lt;/p&gt;
&lt;section aria-label=&quot;Frequently asked questions&quot;&gt; &lt;h2&gt;Frequently asked questions&lt;/h2&gt; &lt;div&gt; &lt;h3&gt;How do I run a Ralph loop with Copilot?&lt;/h3&gt; &lt;p&gt;Install Ralph with npx @pageai/ralph-loop, create a task list, then authenticate with ./ralph.sh --login --agent copilot and run gh auth login inside the sandbox shell. Start the loop with ./ralph.sh --agent copilot. Ralph runs the GitHub Copilot CLI inside a Docker sandbox, starts a fresh context each iteration, and repeats until every task in .agent/tasks.json is done or the iteration cap is reached.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;How does Copilot authenticate inside the sandbox?&lt;/h3&gt; &lt;p&gt;Through the GitHub CLI. Copilot has no agent-specific login, so you run gh auth login inside the sandbox and Copilot acts as your GitHub identity. Ralph creates the box with ./ralph.sh --login --agent copilot and drops you into a shell to sign in. The credential persists in the named sandbox, so future runs attach to the same authenticated box.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;Does ralph.sh support the GitHub Copilot CLI?&lt;/h3&gt; &lt;p&gt;Yes. Copilot is one of the supported agents, alongside claude, codex, cursor, gemini, and opencode. Select it with --agent copilot or the short form -a copilot. Ralph builds a Copilot command and runs it inside a deterministic sandbox named ralph-copilot-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;How do I pass a model to Copilot through Ralph?&lt;/h3&gt; &lt;p&gt;Put it after the -- separator. Anything to the right of -- is forwarded to Copilot, and for Copilot those args are inserted right before -p. So ./ralph.sh --agent copilot -- --model your-model expands to sbx run ... copilot . -- --model your-model -p with the Ralph prompt. Pass a model your Copilot plan supports rather than guessing at an identifier.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;Why choose Copilot over another agent for the loop?&lt;/h3&gt; &lt;p&gt;Pick Copilot when your team already standardized on GitHub. Auth rides on the same gh login your engineers use, your GitHub seats cover access, and there is no separate vendor account or key to manage. The loop mechanics are identical across agents, so the choice comes down to which account and CLI fit your setup best.&lt;/p&gt; &lt;/div&gt; &lt;/section&gt; 
&lt;aside aria-label=&quot;Get started with Ralph Loop&quot;&gt; &lt;h2&gt;Run your own Ralph loop&lt;/h2&gt; &lt;p&gt;Ralph is a hackable script you point at your project. Install it and let an agent work through your task list.&lt;/p&gt; &lt;pre&gt;&lt;code&gt;npx @pageai/ralph-loop&lt;/code&gt;&lt;/pre&gt; &lt;div&gt; &lt;a href=&quot;https://www.npmjs.com/package/@pageai/ralph-loop&quot;&gt;
Install from npm
&lt;/a&gt; &lt;a href=&quot;https://github.com/pageai-pro/ralph-loop&quot;&gt;Star on GitHub&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=3TL8Ez66I3o&quot;&gt;Watch the walkthrough&lt;/a&gt; &lt;/div&gt; &lt;/aside&gt;</content:encoded><category>ralph-technique</category></item><item><title>How to Run a Ralph Loop With opencode</title><link>https://ralphloop.sh/blog/ralph-loop-with-opencode/</link><guid isPermaLink="true">https://ralphloop.sh/blog/ralph-loop-with-opencode/</guid><description>A full end-to-end walkthrough for running the Ralph loop with the open-source opencode CLI: install, log in inside the sandbox, loop on your task list with your own model provider, and review the commits.

</description><pubDate>Sun, 14 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;To run a Ralph loop opencode setup, install Ralph into your project, log in to opencode once inside its sandbox, then run &lt;code dir=&quot;auto&quot;&gt;./ralph.sh --agent opencode&lt;/code&gt;. Ralph drives the open-source opencode CLI through its &lt;code dir=&quot;auto&quot;&gt;run&lt;/code&gt; subcommand, hands it a fresh context window every iteration, and keeps re-running it against your task list until the work is done or you hit the iteration cap.&lt;/p&gt;
&lt;p&gt;The reason this pairing is worth its own page: opencode is the open-source, provider-agnostic agent in the lineup, and Ralph is a hackable open Bash script. An open CLI on top of an open loop means you own both ends. You pick the model. You pick the provider. Nothing about the loop is locked to a vendor. This post is the end-to-end recipe, from an empty repo to a reviewable diff.&lt;/p&gt;
&lt;p&gt;If the loop itself is new to you, start with &lt;a href=&quot;https://ralphloop.sh/blog/what-is-the-ralph-technique/&quot;&gt;what the Ralph technique is&lt;/a&gt;. If you want to compare terminal agents before you commit, the field guide is &lt;a href=&quot;https://ralphloop.sh/blog/agentic-coding-clis/&quot;&gt;agentic coding CLIs&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-run-opencode-in-a-ralph-loop&quot;&gt;Why run opencode in a Ralph loop?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;opencode is a terminal-first coding agent with no editor attached. You give it a prompt, it reads files, edits them, runs shell commands, prints a result, and exits. That clean exit is the hook Ralph needs. Each iteration becomes a discrete unit of work: spawn opencode, let it do one task, read what it emitted, then spawn it again.&lt;/p&gt;
&lt;p&gt;Two properties make opencode a natural fit for a hackable loop.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It is open source. You can read exactly what the agent does, fork it, and audit its behavior. That matches the spirit of &lt;code dir=&quot;auto&quot;&gt;ralph.sh&lt;/code&gt;, which is a script you are meant to open and edit, not a black box.&lt;/li&gt;
&lt;li&gt;It is provider-agnostic. opencode does not force one model vendor on you. You configure the model and provider opencode supports, and the loop runs identically regardless of which one you chose.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ralph then supplies the three things a single opencode run cannot give you on its own:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A loop, so the agent gets another pass after every commit instead of stopping.&lt;/li&gt;
&lt;li&gt;Fresh context per iteration, so a long run never rots into a confused, drifting session.&lt;/li&gt;
&lt;li&gt;A Docker sandbox boundary, so the agent can edit files and run commands in bypass-permissions mode without that blast radius reaching your real machine.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The state lives on disk (the task list, the logs, the git history), not in a chat transcript. That is what lets a stateless opencode invocation reorient on every pass and still make forward progress across dozens of iterations. The Ralph technique itself was popularized by Geoffrey Huntley, and his original writeup on &lt;a href=&quot;https://ghuntley.com/ralph/&quot;&gt;Ralph as a Bash loop&lt;/a&gt; is the shortest path to the core idea.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-1-install-ralph&quot;&gt;Step 1: Install Ralph&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Run the installer in your project root. It drops the &lt;code dir=&quot;auto&quot;&gt;ralph.sh&lt;/code&gt; script and the &lt;code dir=&quot;auto&quot;&gt;.agent/&lt;/code&gt; directory into the repo.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@pageai/ralph-loop&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The installer asks for your dev server URL and port and your default agent, then writes a default implementation-mode setup. Pick &lt;code dir=&quot;auto&quot;&gt;opencode&lt;/code&gt; when prompted, or set it per run with &lt;code dir=&quot;auto&quot;&gt;--agent opencode&lt;/code&gt; later. After install, make the script executable if it is not already:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;chmod&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+x&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph.sh&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Ralph runs each agent inside a Docker Sandbox, so install and sign in to Docker Sandbox first. See the &lt;a href=&quot;https://docs.docker.com/ai/sandboxes/&quot;&gt;Docker Sandboxes docs&lt;/a&gt; for the official setup flow. The loop drives the standalone &lt;code dir=&quot;auto&quot;&gt;sbx&lt;/code&gt; CLI under the hood.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-2-create-a-prd-and-task-list&quot;&gt;Step 2: Create a PRD and task list&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The loop executes a task list. It does not invent one. Before you run anything, turn your requirements into a PRD and a set of task specs with the &lt;code dir=&quot;auto&quot;&gt;prd-creator&lt;/code&gt; skill.&lt;/p&gt;
&lt;p&gt;Open any agent in plan mode and prompt it with your requirements:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Use the prd-creator skill to help me create a PRD and task list for the below requirements.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;An app is already set up with React, Tailwind CSS and TypeScript.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Requirements:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- A SaaS product that helps users manage their finances.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- Track income and expenses, create invoices, generate reports.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;- Use shadcn/ui for components and Supabase for the database.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// etc.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The skill writes &lt;code dir=&quot;auto&quot;&gt;.agent/prd/PRD.md&lt;/code&gt;, a short &lt;code dir=&quot;auto&quot;&gt;.agent/prd/SUMMARY.md&lt;/code&gt;, and per-task specs under &lt;code dir=&quot;auto&quot;&gt;.agent/tasks/&lt;/code&gt;, indexed by &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt;. Review each task individually before you start. A clear task list with real acceptance criteria is the single biggest factor in whether the loop succeeds.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-3-log-in-to-opencode-inside-the-sandbox&quot;&gt;Step 3: Log in to opencode inside the sandbox&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;opencode runs inside an isolated sandbox, not on your host, so it needs credentials in that environment. Authenticate once with the login action:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--login&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;opencode&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Ralph prints the login command for every supported agent, highlights the opencode one, and drops you into the sandbox shell. The command it runs to create the box looks like this:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-opencode-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;opencode&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Inside the sandbox, authenticate opencode once. Run &lt;code dir=&quot;auto&quot;&gt;opencode auth login&lt;/code&gt; and follow the prompt to connect your model provider. That is the exact command Ralph points you to if it detects an auth failure at the start of a run, so it never thrashes on a box that can never make progress. The credential persists in that named sandbox, so later runs attach to the same box already logged in.&lt;/p&gt;
&lt;p&gt;If your provider prefers a key over an interactive login, Docker Sandboxes forward environment variables at the moment a sandbox is created. Export the provider key opencode expects in your shell, then create the sandbox with &lt;code dir=&quot;auto&quot;&gt;./ralph.sh --login --agent opencode&lt;/code&gt; so it picks the value up. Configure the model and provider opencode supports through its own config; Ralph does not hardcode any provider identifiers, which is the whole point of the bring-your-own-provider design.&lt;/p&gt;
&lt;p&gt;Each agent gets its own deterministic sandbox name, built from the agent slug, the project directory, and an 8-character hash of the absolute path:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ralph-opencode-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Print the exact name for your project without starting a run:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--print-name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;opencode&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If your tasks run a dev server you want to reach from the host, publish the configured port to the opencode sandbox. The port comes from the dev-server URL you set during install:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--ports&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;opencode&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That expands to an &lt;code dir=&quot;auto&quot;&gt;sbx ports&lt;/code&gt; call against the opencode sandbox, for example &lt;code dir=&quot;auto&quot;&gt;sbx ports ralph-opencode-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt; --publish 3000:3000&lt;/code&gt;. Per-agent names keep state separate: your opencode sandbox and your Claude sandbox never share credentials or installed tools, so you can run both against the same repo without one clobbering the other.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;step-4-run-the-loop-with-opencode&quot;&gt;Step 4: Run the loop with opencode&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;With a task list in place and opencode authenticated, start the loop. The default agent is Claude, so name opencode explicitly:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 10 iterations (the default)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;opencode&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 50 iterations for a longer unattended run&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;opencode&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# exactly one iteration, good for a smoke test&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;opencode&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--once&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The short form &lt;code dir=&quot;auto&quot;&gt;-a&lt;/code&gt; works too: &lt;code dir=&quot;auto&quot;&gt;./ralph.sh -a opencode -n 5&lt;/code&gt;. There is also &lt;code dir=&quot;auto&quot;&gt;./ralph.sh --agent opencode --max-iterations 5&lt;/code&gt; if you prefer the long flag. The iteration count is a safety budget, not a target. The loop stops the moment the work is done.&lt;/p&gt;
&lt;p&gt;Under the hood, Ralph builds an opencode command and runs it inside the sandbox. This is the detail that sets opencode apart from the other agents: Ralph drives it through the &lt;code dir=&quot;auto&quot;&gt;run&lt;/code&gt; subcommand, not a &lt;code dir=&quot;auto&quot;&gt;-p&lt;/code&gt; print flag. For &lt;code dir=&quot;auto&quot;&gt;./ralph.sh --agent opencode&lt;/code&gt;, the expansion is:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-opencode-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;opencode&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$PROMPT_CONTENT&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;run&lt;/code&gt; subcommand is opencode’s non-interactive path: it takes a prompt, does the work with full tool access including file writes and shell, and exits. That is exactly what a loop needs, an agent that can do real work without a person approving each step.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;pass-a-model-and-flags-after-the-separator&quot;&gt;Pass a model and flags after the separator&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Everything to the right of Ralph’s own &lt;code dir=&quot;auto&quot;&gt;--&lt;/code&gt; separator is forwarded straight to opencode, inserted right after &lt;code dir=&quot;auto&quot;&gt;run&lt;/code&gt;. So this:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;opencode&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x3C;EXTRA&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;expands to:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-opencode-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;opencode&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x3C;EXTRA&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$PROMPT_CONTENT&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Use that slot to select the model and provider opencode supports. Because opencode is provider-agnostic, the exact identifiers depend on which provider you configured, so check opencode’s own help for the flags it accepts rather than guessing. The rule to remember: everything left of &lt;code dir=&quot;auto&quot;&gt;--&lt;/code&gt; configures Ralph (agent, iteration count, login), and everything right of &lt;code dir=&quot;auto&quot;&gt;--&lt;/code&gt; configures opencode.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-happens-each-iteration&quot;&gt;What happens each iteration&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Every pass is mechanical and identical. The agent reorients from disk, does one task, proves it works, and commits.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Find the highest-priority incomplete task in &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Work the steps in &lt;code dir=&quot;auto&quot;&gt;.agent/tasks/TASK-{ID}.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run tests, linting, and type checking.&lt;/li&gt;
&lt;li&gt;Complete the task, take a screenshot, update the task status, and commit.&lt;/li&gt;
&lt;li&gt;Repeat until all tasks pass or the iteration cap is reached.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The key detail is that each iteration spawns a fresh &lt;code dir=&quot;auto&quot;&gt;opencode run&lt;/code&gt; with a clean context window. The agent does not carry an hours-long transcript from one task to the next. It reads the current state from disk, does one task, and exits. Continuity is a liability here, not a feature: a clean context on every pass is what avoids the context rot that sinks long single-session runs.&lt;/p&gt;
&lt;pre dir=&quot;ltr&quot;&gt;flowchart TD
    Start([&quot;./ralph.sh --agent opencode -n 50&quot;]) --&gt; Pick[&quot;Pick top task from .agent/tasks.json&quot;]
    Pick --&gt; Spawn[&quot;sbx run opencode . -- run (fresh context)&quot;]
    Spawn --&gt; Work[&quot;opencode reads state, edits files, runs commands&quot;]
    Work --&gt; Verify[&quot;Run tests, lint, type check, screenshot&quot;]
    Verify --&gt; Commit[&quot;Commit and update task status&quot;]
    Commit --&gt; Check{&quot;Promise tag emitted?&quot;}
    Check --&gt;|&quot;none&quot;| Pick
    Check --&gt;|&quot;COMPLETE&quot;| Done([&quot;exit 0, all tasks done&quot;])
    Check --&gt;|&quot;BLOCKED or DECIDE&quot;| Stop([&quot;exit 2 or 3, wants a human&quot;])&lt;/pre&gt;
&lt;p&gt;The filesystem and git history are the memory layer. Progress lives in &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;.agent/logs/LOG.md&lt;/code&gt;, the per-task specs under &lt;code dir=&quot;auto&quot;&gt;.agent/tasks/&lt;/code&gt;, the per-iteration logs in &lt;code dir=&quot;auto&quot;&gt;.agent/history/&lt;/code&gt;, and the git log, not in a chat transcript. The prompt that drives every pass lives in &lt;code dir=&quot;auto&quot;&gt;.agent/PROMPT.md&lt;/code&gt;, alongside &lt;code dir=&quot;auto&quot;&gt;.agent/prd/SUMMARY.md&lt;/code&gt;, which gives opencode a short project overview on each iteration.&lt;/p&gt;
&lt;p&gt;One rule keeps the whole thing reliable: one task per invocation. opencode completes exactly one task, commits, and stops. It never batches several tasks into a single iteration, which is what keeps each commit small and each diff reviewable.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-the-loop-knows-when-to-stop&quot;&gt;How the loop knows when to stop&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A loop that never ends is a money fire, so Ralph stops on an explicit signal rather than a guess. opencode emits a semantic promise tag in its final message and Ralph reads it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;COMPLETE&amp;#x3C;/promise&gt;&lt;/code&gt; means every task is finished.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;BLOCKED:reason&amp;#x3C;/promise&gt;&lt;/code&gt; means the agent needs human help.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;DECIDE:question&amp;#x3C;/promise&gt;&lt;/code&gt; means it needs a decision you have to make.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those map to exit codes, which is what makes the loop scriptable:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;opencode&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;echo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;$?&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt;# 0 COMPLETE, 1 MAX_ITERATIONS, 2 BLOCKED, 3 DECIDE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;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. If opencode is not authenticated when the loop starts, Ralph detects the auth failure, stops, and tells you to run &lt;code dir=&quot;auto&quot;&gt;opencode auth login&lt;/code&gt; inside the sandbox (via &lt;code dir=&quot;auto&quot;&gt;./ralph.sh --login --agent opencode&lt;/code&gt;), so a broken box never burns iterations.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;steer-debug-and-review&quot;&gt;Steer, debug, and review&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Three things keep a long opencode run under control.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;steer-without-stopping&quot;&gt;Steer without stopping&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;If you notice the agent heading the wrong way, you do not have to kill the loop. Edit &lt;code dir=&quot;auto&quot;&gt;.agent/STEERING.md&lt;/code&gt; while it runs. The agent checks that file on each pass and handles the critical work there before resuming the normal task order. That is steering, not stopping, and it keeps momentum while you correct course.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;get-inside-the-sandbox&quot;&gt;Get inside the sandbox&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The sandbox is a normal container you can poke at. List what exists, then open a shell in the opencode box:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ls&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;exec&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-it&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-opencode-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bash&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;From there you can re-run a failing test by hand, confirm auth by running &lt;code dir=&quot;auto&quot;&gt;opencode&lt;/code&gt; directly, or read &lt;code dir=&quot;auto&quot;&gt;.agent/logs/LOG.md&lt;/code&gt; and the per-iteration logs in &lt;code dir=&quot;auto&quot;&gt;.agent/history/&lt;/code&gt;. When an install fails, the deny-by-default network is usually the cause. Allow the domain it needs:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;policy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;allow&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;network&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-opencode-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;registry.npmjs.org&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That is a feature, not a bug. The agent installs what the task needs without a path to exfiltrate your source. To reattach to a running box later, &lt;code dir=&quot;auto&quot;&gt;sbx run ralph-opencode-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;&lt;/code&gt; drops you back in without recreating it.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;review-the-diff-in-the-morning&quot;&gt;Review the diff in the morning&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Because every task is committed separately, the morning review is a git review, not an archaeology dig:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--oneline&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;diff&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main...HEAD&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;You read the commits in order, eyeball the screenshots the agent captured, and accept or revert. A single bad task is one revert, not a tangled mess. This is the whole premise of &lt;a href=&quot;https://ralphloop.sh/blog/run-ai-coding-agent-overnight/&quot;&gt;running an AI coding agent overnight&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;should-you-use-opencode-or-another-agent&quot;&gt;Should you use opencode or another agent?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Ralph is agent agnostic. The supported agents are &lt;code dir=&quot;auto&quot;&gt;claude&lt;/code&gt; (default), &lt;code dir=&quot;auto&quot;&gt;codex&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;copilot&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;cursor&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;gemini&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;opencode&lt;/code&gt;, and switching is a single flag. opencode earns its place when you want an open-source agent and the freedom to route through whatever model provider you already use. The loop machinery is identical across agents, so the choice comes down to which CLI holds up over a multi-hour run.&lt;/p&gt;
&lt;p&gt;The verification stack the loop assumes is the same regardless of agent: Playwright for end-to-end tests, Vitest for unit tests, TypeScript for types, ESLint for linting, and Prettier for formatting. The repo mantra is blunt about it: if you didn’t test it, it doesn’t work. That comparison across CLIs lives in &lt;a href=&quot;https://ralphloop.sh/blog/best-agentic-cli-for-long-running-tasks/&quot;&gt;the best agentic CLI for long-running tasks&lt;/a&gt;. If you want to strip the loop down to its essentials, read &lt;a href=&quot;https://ralphloop.sh/blog/ralph-loop-shell-script/&quot;&gt;the Ralph loop shell script&lt;/a&gt;, and if you would rather drive a proprietary CLI, the companion recipe is &lt;a href=&quot;https://ralphloop.sh/blog/ralph-loop-with-cursor-cli/&quot;&gt;running a Ralph loop with the Cursor CLI&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A real opencode loop, start to finish, is four commands:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 1. install Ralph and scaffolding&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@pageai/ralph-loop&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 2. authenticate once (creates the sandbox, you log in inside it)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--login&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;opencode&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 3. smoke test a single pass&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;opencode&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--once&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 4. run the loop for real&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;opencode&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Define your tasks in &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt;, write a clear &lt;code dir=&quot;auto&quot;&gt;.agent/PROMPT.md&lt;/code&gt;, configure the model and provider opencode supports, start the loop, and read the commits in the morning.&lt;/p&gt;
&lt;section aria-label=&quot;Frequently asked questions&quot;&gt; &lt;h2&gt;Frequently asked questions&lt;/h2&gt; &lt;div&gt; &lt;h3&gt;How do I run a Ralph loop with opencode?&lt;/h3&gt; &lt;p&gt;Install Ralph with npx @pageai/ralph-loop, create a task list, log in with ./ralph.sh --login --agent opencode, then run ./ralph.sh --agent opencode. Ralph drives the open-source opencode CLI through its run subcommand inside a Docker sandbox, starts a fresh context each iteration, and repeats until every task in .agent/tasks.json is done or the iteration cap is reached.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;Does ralph.sh support opencode?&lt;/h3&gt; &lt;p&gt;Yes. opencode is one of the supported agents, alongside claude, codex, copilot, cursor, and gemini. Select it with --agent opencode or the short form -a opencode. Ralph builds an opencode run command and executes it inside a deterministic sandbox named ralph-opencode-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt;.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;How does Ralph invoke opencode under the hood?&lt;/h3&gt; &lt;p&gt;Ralph uses opencode run rather than a print flag. For ./ralph.sh --agent opencode the expansion is sbx run --name ralph-opencode-&amp;#x3C;project&gt;-&amp;#x3C;hash8&gt; opencode . -- run &quot;$PROMPT_CONTENT&quot;. Anything you pass after Ralph&apos;s own -- separator is inserted right after run, so you can select the model and provider opencode supports.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;How do I log in to opencode for the loop?&lt;/h3&gt; &lt;p&gt;Run ./ralph.sh --login --agent opencode. It drops you into the sandbox shell where you run opencode auth login and connect your model provider. The credential persists in that named sandbox, so future runs attach to the same box already authenticated. You can also forward a provider key as an environment variable at sandbox creation time.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;Can I choose my own model and provider with opencode?&lt;/h3&gt; &lt;p&gt;Yes. opencode is provider-agnostic, so you configure the model and provider it supports through its own config or by passing flags after Ralph&apos;s -- separator. Ralph does not hardcode any provider identifiers, which keeps the loop portable across whatever backend you already pay for.&lt;/p&gt; &lt;/div&gt; &lt;/section&gt; 
&lt;aside aria-label=&quot;Get started with Ralph Loop&quot;&gt; &lt;h2&gt;Run your own Ralph loop&lt;/h2&gt; &lt;p&gt;Ralph is a hackable script you point at your project. Install it and let an agent work through your task list.&lt;/p&gt; &lt;pre&gt;&lt;code&gt;npx @pageai/ralph-loop&lt;/code&gt;&lt;/pre&gt; &lt;div&gt; &lt;a href=&quot;https://www.npmjs.com/package/@pageai/ralph-loop&quot;&gt;
Install from npm
&lt;/a&gt; &lt;a href=&quot;https://github.com/pageai-pro/ralph-loop&quot;&gt;Star on GitHub&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=3TL8Ez66I3o&quot;&gt;Watch the walkthrough&lt;/a&gt; &lt;/div&gt; &lt;/aside&gt;</content:encoded><category>ralph-technique</category></item><item><title>How to Inspect and Debug Inside an AI Agent Sandbox</title><link>https://ralphloop.sh/blog/debug-inside-an-agent-sandbox/</link><guid isPermaLink="true">https://ralphloop.sh/blog/debug-inside-an-agent-sandbox/</guid><description>A sandbox is not a black box. Here is how to find the running microVM, shell into it with sbx exec, reproduce the failure by hand, and read the network log.

</description><pubDate>Wed, 27 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When an autonomous agent gets stuck, do not guess from the outside. Open a shell in the running sandbox and look. A Ralph sandbox is a normal container once you are inside it, so you find it with &lt;code dir=&quot;auto&quot;&gt;sbx ls&lt;/code&gt;, shell in with &lt;code dir=&quot;auto&quot;&gt;sbx exec -it &amp;#x3C;name&gt; bash&lt;/code&gt;, and debug the agent the same way you debug any other process. This is the field guide to inspecting and debugging an AI agent sandbox: locating the microVM, getting an interactive shell, reproducing the failure by hand, and reading the network log.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;when-to-get-inside-the-sandbox&quot;&gt;When to get inside the sandbox&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A loop driven by &lt;a href=&quot;https://github.com/pageai-pro/ralph-loop&quot;&gt;Ralph&lt;/a&gt; does not fail silently. The agent emits an explicit signal when it cannot proceed: &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;BLOCKED:reason&amp;#x3C;/promise&gt;&lt;/code&gt; when it needs human help (exit code &lt;code dir=&quot;auto&quot;&gt;2&lt;/code&gt;) or &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;DECIDE:question&amp;#x3C;/promise&gt;&lt;/code&gt; when it needs a decision (exit code &lt;code dir=&quot;auto&quot;&gt;3&lt;/code&gt;). A run that hits the iteration cap exits with &lt;code dir=&quot;auto&quot;&gt;1&lt;/code&gt;, and a clean finish exits with &lt;code dir=&quot;auto&quot;&gt;0&lt;/code&gt;. The promise text tells you what the agent thinks went wrong. It does not always tell you why.&lt;/p&gt;
&lt;p&gt;That gap is what a shell closes. You get inside when a task keeps failing the same way, when an install hangs, when tests pass for you but not for the agent, or when you just want to see the state the agent left behind. Because the agent runs in bypass-permissions mode (see &lt;a href=&quot;https://ralphloop.sh/blog/bypass-permissions-yolo-mode-safely/&quot;&gt;running agents in YOLO mode safely&lt;/a&gt; for why that is fine inside a microVM), the inside of the sandbox holds the real evidence: the working tree, the logs, the installed packages, and the exact environment the agent saw.&lt;/p&gt;
&lt;p&gt;The sandbox is the same boundary described in the pillar guide, &lt;a href=&quot;https://ralphloop.sh/blog/run-ai-agents-in-docker-sandboxes-safely/&quot;&gt;how to run AI coding agents in Docker Sandboxes safely&lt;/a&gt;. Debugging it does not weaken that boundary. You are stepping into the contained space on purpose, doing your work, and stepping back out.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;find-the-sandbox-you-need-to-debug&quot;&gt;Find the sandbox you need to debug&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;You cannot shell into a sandbox you cannot name. Ralph names every sandbox deterministically, so the name is predictable, but you still want to confirm what is actually running.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;list-everything-with-sbx-ls&quot;&gt;List everything with sbx ls&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Start with the list:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ls&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This prints every sandbox on the machine. The one you want follows the Ralph format:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ralph-&amp;#x3C;agent&gt;-&amp;#x3C;current-dir&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;A project at &lt;code dir=&quot;auto&quot;&gt;/Users/me/Work/My App&lt;/code&gt; running Claude shows up as &lt;code dir=&quot;auto&quot;&gt;ralph-claude-my-app-a1b2c3d4&lt;/code&gt;. If you ran more than one agent against the same project, you will see one sandbox per agent, because the agent slug is part of the name. That is deliberate, so a Claude run and a Codex run never stomp on each other’s state.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;ask-ralph-for-the-exact-name&quot;&gt;Ask Ralph for the exact name&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;You do not have to read the name off a list. Ralph prints it on the &lt;code dir=&quot;auto&quot;&gt;Starting Ralph&lt;/code&gt; line at launch, and you can ask for it any time:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--print-name&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--print-name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cursor&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;--agent&lt;/code&gt; flag targets a specific agent’s sandbox for the same project. Capture the value once and reuse it for every &lt;code dir=&quot;auto&quot;&gt;sbx&lt;/code&gt; command below, so you are not retyping a hash.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;decode-the-deterministic-name&quot;&gt;Decode the deterministic name&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The name carries information, which helps when you have several sandboxes open. The pieces are documented in the repo’s sandbox naming notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;agent&gt;&lt;/code&gt; is the agent slug, lowercased: &lt;code dir=&quot;auto&quot;&gt;claude&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;codex&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;copilot&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;cursor&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;gemini&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;opencode&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;current-dir&gt;&lt;/code&gt; is the basename of the project directory, sanitized to &lt;code dir=&quot;auto&quot;&gt;[a-z0-9-]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;hash8&gt;&lt;/code&gt; is the first eight hex characters of &lt;code dir=&quot;auto&quot;&gt;sha256&lt;/code&gt; of the absolute project path.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The path hash is the part that prevents collisions. Two directories both named &lt;code dir=&quot;auto&quot;&gt;app&lt;/code&gt;, one at &lt;code dir=&quot;auto&quot;&gt;~/Work/app&lt;/code&gt; and one at &lt;code dir=&quot;auto&quot;&gt;/tmp/app&lt;/code&gt;, get different hashes and therefore different sandboxes. So when &lt;code dir=&quot;auto&quot;&gt;sbx ls&lt;/code&gt; shows two &lt;code dir=&quot;auto&quot;&gt;ralph-claude-app-...&lt;/code&gt; entries, the trailing hash is how you tell them apart.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;shell-into-the-sandbox-with-sbx-exec&quot;&gt;Shell into the sandbox with sbx exec&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This is the command you came for. Open an interactive shell:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;exec&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-it&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-claude-my-app-a1b2c3d4&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bash&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;-it&lt;/code&gt; flags allocate an interactive terminal. Now you are inside the microVM with full control, the same as any container. You can install packages, run the test suite, read files, and poke at the environment the agent ran in.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;navigate-to-the-project-path&quot;&gt;Navigate to the project path&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The project is mounted at the same absolute path it has on your host. If your project lives at &lt;code dir=&quot;auto&quot;&gt;/Users/you/Work/my-app&lt;/code&gt; on the host, it lives at &lt;code dir=&quot;auto&quot;&gt;/Users/you/Work/my-app&lt;/code&gt; inside the sandbox too. That is on purpose, so tooling, config files, and lockfiles resolve identically and the agent never trips over a path difference.&lt;/p&gt;
&lt;p&gt;So the first move after shelling in is usually:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/Users/you/Work/my-app&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Now your working directory matches the agent’s. Any relative path the agent used resolves the same way for you.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;run-the-agent-cli-by-hand&quot;&gt;Run the agent CLI by hand&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The most useful trick is driving the agent yourself, interactively, from inside the sandbox. You see exactly what it sees, including the permission mode and the network reality:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;exec&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-it&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-claude-my-app-a1b2c3d4&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bash&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/Users/you/Work/my-app&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;claude&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Swap &lt;code dir=&quot;auto&quot;&gt;claude&lt;/code&gt; for &lt;code dir=&quot;auto&quot;&gt;codex&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;copilot&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;cursor&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;gemini&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;opencode&lt;/code&gt; depending on which agent that sandbox was built for. Running the CLI by hand is how you tell apart a prompt problem from an environment problem. If the agent stalls the same way interactively, the issue is the task, the prompt, or the repo state. If it works by hand but fails in the loop, the issue is in how the loop set things up, and you can compare the two.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;reattach-a-stopped-sandbox-with-sbx-run&quot;&gt;Reattach a stopped sandbox with sbx run&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;sbx exec&lt;/code&gt; works while the sandbox is running. To reattach the exact sandbox Ralph uses, for a manual login or a longer debugging session, use the attach form of &lt;code dir=&quot;auto&quot;&gt;sbx run&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-claude-my-app-a1b2c3d4&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;There is one sharp edge here, and it trips people up, so state it plainly. The create form, &lt;code dir=&quot;auto&quot;&gt;sbx run --name &amp;#x3C;name&gt; &amp;#x3C;agent&gt; .&lt;/code&gt;, is create-only. Passing &lt;code dir=&quot;auto&quot;&gt;--name&lt;/code&gt; for a sandbox that already exists fails with an error that says &lt;code dir=&quot;auto&quot;&gt;--name can only be used when creating a new sandbox&lt;/code&gt;. So you use the &lt;code dir=&quot;auto&quot;&gt;--name&lt;/code&gt; create form exactly once, before the sandbox exists, and the bare &lt;code dir=&quot;auto&quot;&gt;sbx run &amp;#x3C;name&gt;&lt;/code&gt; attach form every time after.&lt;/p&gt;
&lt;p&gt;Ralph handles this branching for you. It probes &lt;code dir=&quot;auto&quot;&gt;sbx ls&lt;/code&gt; before every iteration and emits the create form when the sandbox is missing and the attach form when it exists. That probe is also why a loop self-heals: if you &lt;code dir=&quot;auto&quot;&gt;sbx rm&lt;/code&gt; a sandbox mid-run while debugging, the next iteration simply creates it again. Re-running &lt;code dir=&quot;auto&quot;&gt;./ralph.sh&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;./ralph.sh --login&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;./ralph.sh --ports&lt;/code&gt; is therefore safe at any point.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;debug-inside-logs-reproduction-and-missing-tools&quot;&gt;Debug inside: logs, reproduction, and missing tools&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Once you have a shell, the sandbox is just an environment. Three moves cover most debugging sessions.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;read-the-agents-own-memory-layer&quot;&gt;Read the agent’s own memory layer&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Ralph keeps its state on disk, not in chat history, because the filesystem and git history are the memory layer. That means everything the agent knows is sitting in the project, ready to read:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cat&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.agent/logs/LOG.md&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ls&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.agent/history/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cat&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.agent/tasks.json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--oneline&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-20&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;.agent/logs/LOG.md&lt;/code&gt; is the running log. &lt;code dir=&quot;auto&quot;&gt;.agent/history/&lt;/code&gt; holds per-iteration logs, so you can see what happened on the iteration that broke. &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt; is the task lookup table, and the git log shows what the agent actually committed. Reading these in order usually tells you whether the agent thrashed on one task, committed something broken, or stalled waiting on a decision. For the wider picture of making a run auditable from the outside, including live output and screenshots, see &lt;a href=&quot;https://ralphloop.sh/blog/observability-for-ai-coding-agents/&quot;&gt;observability for autonomous coding agents&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;reproduce-the-failing-command&quot;&gt;Reproduce the failing command&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The fastest way to understand a failure is to run the thing that failed. Inside the sandbox, at the project path, run the verification stack the loop assumes:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;test&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tsc&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--noEmit&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;eslint&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;playwright&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;test&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If the agent reported a failing test, run that test directly and read the full output without the loop’s framing. The repo mantra is “if you didn’t test it, it doesn’t work,” and the same applies to your debugging: reproduce the exact failure before you theorize about it. Because you are inside the same microVM the agent used, a failure you reproduce here is the real failure, not a near miss caused by a different machine.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;install-missing-tooling&quot;&gt;Install missing tooling&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Sometimes the agent is blocked because a binary it needs is not installed. You have full root-style control inside the sandbox, so install it and confirm the theory:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;apt-get&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;update&lt;/span&gt;&lt;span&gt; &amp;#x26;&amp;#x26; &lt;/span&gt;&lt;span&gt;apt-get&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-y&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x3C;package&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If an install itself fails, that is almost never a package manager bug. It is the network gate, which is the next section.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;debug-the-network-with-sbx-policy-log&quot;&gt;Debug the network with sbx policy log&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Docker Sandboxes are deny-by-default on outbound HTTP and HTTPS. So the single most common “the agent is stuck” cause is a blocked connection: &lt;code dir=&quot;auto&quot;&gt;npm install&lt;/code&gt; fails, an API call is refused, or a package download hangs. The gate is doing its job, but you need to see what it blocked.&lt;/p&gt;
&lt;p&gt;Read the connection history:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;policy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This shows which outbound connections the sandbox attempted and which the policy allowed or denied. List the active rules to see what is currently permitted:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;policy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ls&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;When the log shows a denied host the task legitimately needs, allow that specific domain rather than opening everything:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;policy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;allow&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;network&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-claude-my-app-a1b2c3d4&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;registry.npmjs.org&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Allow several at once with a comma-separated list, which is the usual case when an install is blocked:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;policy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;allow&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;network&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-claude-my-app-a1b2c3d4&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;*.npmjs.org,*.pypi.org,files.pythonhosted.org,github.com&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Changes take effect immediately and persist across sandbox restarts. Resist the urge to debug with the full-open &lt;code dir=&quot;auto&quot;&gt;&quot;**&quot;&lt;/code&gt; rule and leave it there, because that throws away half the boundary. The disciplined way to build an allowlist that lets installs through while keeping exfiltration out is covered in &lt;a href=&quot;https://ralphloop.sh/blog/ai-agent-sandbox-network-policies/&quot;&gt;network policies for AI agent sandboxes&lt;/a&gt;. The primary reference for the policy engine is the &lt;a href=&quot;https://docs.docker.com/ai/sandboxes/&quot;&gt;Docker Sandboxes documentation&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-map-of-debug-entry-points&quot;&gt;A map of debug entry points&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;There are only a handful of ways into and out of a sandbox, and they compose cleanly. This is the whole surface you work with.&lt;/p&gt;
&lt;pre dir=&quot;ltr&quot;&gt;flowchart LR
  Host[&quot;Your machine (host)&quot;]
  subgraph Entry[&quot;Debug entry points&quot;]
    List[&quot;sbx ls: find the name&quot;]
    Shell[&quot;sbx exec -it name bash: interactive shell&quot;]
    Attach[&quot;sbx run name: reattach the loop sandbox&quot;]
    NetLog[&quot;sbx policy log: connection history&quot;]
    Stop[&quot;sbx stop name: end the session&quot;]
  end
  subgraph Sandbox[&quot;Sandbox: ralph-claude-my-app-a1b2c3d4&quot;]
    Proj[&quot;Project at the same path as the host&quot;]
    Logs[&quot;.agent/logs, .agent/history, git log&quot;]
    CLI[&quot;Agent CLI you can run by hand&quot;]
    Net[&quot;Network gate: deny-by-default&quot;]
  end
  Host --&gt; List
  Host --&gt; Shell
  Host --&gt; Attach
  Host --&gt; NetLog
  Host --&gt; Stop
  Shell --&gt; Proj
  Shell --&gt; Logs
  Shell --&gt; CLI
  Attach --&gt; CLI
  NetLog --&gt; Net&lt;/pre&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;sbx ls&lt;/code&gt; gives you the name. &lt;code dir=&quot;auto&quot;&gt;sbx exec&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;sbx run&lt;/code&gt; put you inside. &lt;code dir=&quot;auto&quot;&gt;sbx policy log&lt;/code&gt; explains network failures from the outside. &lt;code dir=&quot;auto&quot;&gt;sbx stop&lt;/code&gt; ends the session. You rarely need anything else.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;clean-up-with-sbx-stop&quot;&gt;Clean up with sbx stop&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;When you finish a manual debugging session, stop the sandbox you were poking at:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stop&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-claude-my-app-a1b2c3d4&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Stopping targets only that one name. Sandboxes you started for other agents in the same project are left alone. Ralph runs this same command for you through its exit trap when a loop ends, by normal exit, by a double Ctrl+C, or by any path that fires the trap, and the cleanup is guarded so it runs at most once.&lt;/p&gt;
&lt;p&gt;Stopping is not deleting. A stopped sandbox can be reattached later with &lt;code dir=&quot;auto&quot;&gt;sbx run &amp;#x3C;name&gt;&lt;/code&gt;, which is handy when you want to come back to the same state. When you are truly done and want the microVM gone, remove it explicitly:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-claude-my-app-a1b2c3d4&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If &lt;code dir=&quot;auto&quot;&gt;sbx&lt;/code&gt; is not installed at all, Ralph fails fast before any of this with a clear pointer to the &lt;a href=&quot;https://docs.docker.com/ai/sandboxes/get-started/&quot;&gt;Docker Sandboxes getting-started guide&lt;/a&gt; rather than a confusing downstream error.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-debugging-session-end-to-end&quot;&gt;A debugging session, end to end&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Putting the moves in order, here is what a real session looks like when a loop reports &lt;code dir=&quot;auto&quot;&gt;BLOCKED&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;First, get the name and confirm the sandbox is there:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--print-name&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ls&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Shell in and go to the project path:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;exec&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-it&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-claude-my-app-a1b2c3d4&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bash&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/Users/you/Work/my-app&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Read what the agent recorded, then reproduce the failure it hit:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cat&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.agent/logs/LOG.md&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--oneline&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-10&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;test&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If the failure is a blocked install, check the network log from your host in another terminal, allow the domain, and retry the install inside the sandbox:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;policy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;policy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;allow&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;network&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-claude-my-app-a1b2c3d4&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;registry.npmjs.org&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If the failure is a task or prompt problem, run the agent CLI by hand and watch it work the task interactively. When you understand the cause, exit the shell, fix the task spec or the prompt on the host, and let the loop pick up where it left off. Ralph re-probes the sandbox on the next iteration, so your fix runs in the same contained environment.&lt;/p&gt;
&lt;p&gt;This is the Ralph technique applied to its own failures. The loop, in the tradition of Geoffrey Huntley’s original &lt;a href=&quot;https://ghuntley.com/ralph/&quot;&gt;Ralph writeup&lt;/a&gt;, keeps its state on disk so you can always step in, read the evidence, reproduce the problem, and step back out. The sandbox is contained, so debugging it costs you nothing on the host. The worst case stays cheap, and the agent gets back to work.&lt;/p&gt;
&lt;section aria-label=&quot;Frequently asked questions&quot;&gt; &lt;h2&gt;Frequently asked questions&lt;/h2&gt; &lt;div&gt; &lt;h3&gt;How do I open a shell inside a running AI agent sandbox?&lt;/h3&gt; &lt;p&gt;Find the sandbox name with sbx ls or with ./ralph.sh --print-name, then run sbx exec with the interactive flags and bash, for example sbx exec -it ralph-claude-my-app-a1b2c3d4 bash. You now have full control inside the microVM, so you can navigate to the project path, run tests, install packages, and inspect files like any container.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;What is the difference between sbx exec and sbx run for debugging?&lt;/h3&gt; &lt;p&gt;Use sbx exec with -it and bash to open an interactive shell in a sandbox that is already running. Use sbx run with the name to reattach the sandbox Ralph uses for a manual login or a longer session. The create form sbx run --name name agent dot is create-only and only works the first time, before the sandbox exists.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;Why does an install or API call fail inside the sandbox?&lt;/h3&gt; &lt;p&gt;Docker Sandboxes block outbound network by default. Run sbx policy log to see which connections were denied, then allow the specific domain with sbx policy allow network using the sandbox name. Allow only the hosts the task needs, such as registry.npmjs.org or the comma-separated npm, PyPI, and GitHub domains, rather than opening all traffic.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;Where does the agent store the logs I should read when it gets stuck?&lt;/h3&gt; &lt;p&gt;Ralph keeps its state on disk inside the project, not in chat history. Read .agent/logs/LOG.md for the running log, list .agent/history for per-iteration logs, check .agent/tasks.json for the task table, and run git log to see what was committed. Together these usually show whether the agent thrashed on a task or stalled waiting on a decision.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;How do I clean up a sandbox after debugging?&lt;/h3&gt; &lt;p&gt;Run sbx stop with the sandbox name to stop only that sandbox; sandboxes for other agents are untouched. Stopping is not deleting, so you can reattach later with sbx run and the name. When you want the microVM gone for good, remove it with sbx rm. Ralph also stops its own sandbox automatically through its exit trap when a loop ends.&lt;/p&gt; &lt;/div&gt; &lt;/section&gt; 
&lt;aside aria-label=&quot;Get started with Ralph Loop&quot;&gt; &lt;h2&gt;Run your own Ralph loop&lt;/h2&gt; &lt;p&gt;Ralph is a hackable script you point at your project. Install it and let an agent work through your task list.&lt;/p&gt; &lt;pre&gt;&lt;code&gt;npx @pageai/ralph-loop&lt;/code&gt;&lt;/pre&gt; &lt;div&gt; &lt;a href=&quot;https://www.npmjs.com/package/@pageai/ralph-loop&quot;&gt;
Install from npm
&lt;/a&gt; &lt;a href=&quot;https://github.com/pageai-pro/ralph-loop&quot;&gt;Star on GitHub&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=3TL8Ez66I3o&quot;&gt;Watch the walkthrough&lt;/a&gt; &lt;/div&gt; &lt;/aside&gt;</content:encoded><category>sandboxing</category></item><item><title>Network Policies for AI Agent Sandboxes</title><link>https://ralphloop.sh/blog/ai-agent-sandbox-network-policies/</link><guid isPermaLink="true">https://ralphloop.sh/blog/ai-agent-sandbox-network-policies/</guid><description>A sandboxed agent should start with no outbound network and earn each domain it reaches. Here is how to allowlist exactly what a task needs with sbx policy, so package installs work and exfiltration does not.

</description><pubDate>Thu, 21 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;An agent needs some network and not all of it. A Docker Sandbox blocks outbound HTTP and HTTPS by default, so the agent starts with zero reach and you grant exactly the domains a task needs with &lt;code dir=&quot;auto&quot;&gt;sbx policy allow network&lt;/code&gt;. That is the whole &lt;code dir=&quot;auto&quot;&gt;ai agent sandbox network policy&lt;/code&gt; model: deny everything, then allowlist the package registries, code hosts, and APIs the work requires, and nothing else. This guide shows the commands, the matching rules, and a practical allowlist you can paste for a normal project. It is the network half of the &lt;a href=&quot;https://ralphloop.sh/blog/run-ai-agents-in-docker-sandboxes-safely/&quot;&gt;Docker Sandboxes pillar guide&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-a-sandbox-blocks-the-network-by-default&quot;&gt;Why a sandbox blocks the network by default&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Filesystem isolation is only half of a sandbox boundary. The other half is the network. An agent with unrestricted outbound access can fetch arbitrary code, run a &lt;code dir=&quot;auto&quot;&gt;curl | bash&lt;/code&gt; it found in a README, and in the worst case send your source or secrets to a host you never approved. That last risk is the one that should keep you honest. The agent does not need to be malicious. It is a probabilistic system running shell commands, and a shell command that uploads a file looks exactly like a shell command that downloads one.&lt;/p&gt;
&lt;p&gt;So Docker Sandboxes ship with a deny-by-default network posture. All outbound HTTP and HTTPS to domains not on the allowlist is blocked, and all non-HTTP protocols (raw TCP, UDP including DNS, and ICMP) are blocked at the network layer, as documented in the &lt;a href=&quot;https://docs.docker.com/ai/sandboxes/security/defaults/&quot;&gt;default security posture reference&lt;/a&gt;. Traffic to private IP ranges, loopback, and link-local addresses is blocked too, so the agent cannot reach back into your LAN or a cloud metadata endpoint.&lt;/p&gt;
&lt;p&gt;The practical symptom is that &lt;code dir=&quot;auto&quot;&gt;npm install&lt;/code&gt; fails, a &lt;code dir=&quot;auto&quot;&gt;pip install&lt;/code&gt; hangs, or an API call is refused inside the sandbox. That is not a bug. That is the gate doing its job. You fix it by allowlisting the specific domains the task needs, not by opening everything. The point of the sandbox was to make the agent fearless inside a contained blast radius, and a wide-open network puts a hole in the wall you built.&lt;/p&gt;
&lt;p&gt;Docker offers three starting policies, described in the &lt;a href=&quot;https://docs.docker.com/ai/sandboxes/security/policy/&quot;&gt;policies documentation&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Open: all outbound traffic allowed, equivalent to a global allow-all rule. No restrictions.&lt;/li&gt;
&lt;li&gt;Balanced: default deny with a baseline allowlist covering common AI provider APIs, package managers, code hosts, container registries, and cloud services. You extend it with &lt;code dir=&quot;auto&quot;&gt;sbx policy allow&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Locked Down: everything blocked, including model provider APIs like &lt;code dir=&quot;auto&quot;&gt;api.anthropic.com&lt;/code&gt;. You allow each host explicitly.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Most Ralph users sit on Balanced or stricter. If you chose Balanced, package installs from the big registries often work out of the box and you only add what is missing. If you chose Locked Down, you allow even the model API the agent talks to. Check which one you are on before debugging a blocked request.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-a-request-flows-through-the-network-gate&quot;&gt;How a request flows through the network gate&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Every outbound connection the agent makes hits the policy gate before it leaves the sandbox. The gate compares the destination against your allow and deny rules, and if nothing matches, the default policy decides. On a deny-by-default install, nothing matching means blocked.&lt;/p&gt;
&lt;pre dir=&quot;ltr&quot;&gt;flowchart TD
  Agent[&quot;Agent in YOLO mode&quot;] --&gt;|&quot;outbound request to a host&quot;| Gate{&quot;Network policy gate&quot;}
  Gate --&gt;|&quot;matches a deny rule&quot;| Block[&quot;Blocked: connection refused&quot;]
  Gate --&gt;|&quot;matches an allow rule&quot;| Pass[&quot;Allowed: leaves the sandbox&quot;]
  Gate --&gt;|&quot;matches nothing (deny-by-default)&quot;| Block
  Pass --&gt; Net[&quot;Internet: only allowlisted hosts&quot;]
  Block --&gt; Log[&quot;Recorded in sbx policy log&quot;]&lt;/pre&gt;
&lt;p&gt;The diagram is the mental model. The agent never talks to the internet directly. It talks to a gate, and the gate enforces a list you control from the host. You set that list once for a project and stop thinking about individual requests.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-to-allow-a-domain-with-sbx-policy&quot;&gt;How to allow a domain with sbx policy&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Grant access with &lt;code dir=&quot;auto&quot;&gt;sbx policy&lt;/code&gt;. Changes take effect immediately and persist across sandbox restarts. You target a single sandbox by passing its name, or every sandbox on the machine with the global flag &lt;code dir=&quot;auto&quot;&gt;-g&lt;/code&gt;. Get the name from &lt;code dir=&quot;auto&quot;&gt;sbx ls&lt;/code&gt; or from Ralph directly:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--print-name&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--print-name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;codex&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Ralph builds a deterministic sandbox name in the form &lt;code dir=&quot;auto&quot;&gt;ralph-&amp;#x3C;agent&gt;-&amp;#x3C;current-dir&gt;-&amp;#x3C;hash8&gt;&lt;/code&gt;, so a project at &lt;code dir=&quot;auto&quot;&gt;/Users/me/Work/my-app&lt;/code&gt; running Claude becomes &lt;code dir=&quot;auto&quot;&gt;ralph-claude-my-app-a1b2c3d4&lt;/code&gt;. Use that string wherever a command below shows a name.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;allow-one-domain-for-one-sandbox&quot;&gt;Allow one domain for one sandbox&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The base command takes a sandbox name and a domain:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;policy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;allow&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;network&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-claude-my-app-a1b2c3d4&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;api.example.com&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That sandbox can now reach &lt;code dir=&quot;auto&quot;&gt;api.example.com&lt;/code&gt;. Nothing else changed, and no other sandbox was affected.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;allow-several-domains-at-once&quot;&gt;Allow several domains at once&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Pass a comma-separated list instead of a single host. This is the common case when a package install is blocked and you want to fix it in one command:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;policy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;allow&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;network&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-claude-my-app-a1b2c3d4&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;*.npmjs.org,*.pypi.org,files.pythonhosted.org,github.com&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Quote the list so your shell does not try to glob the asterisks. Each entry is matched independently against outbound requests.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;apply-a-rule-to-every-sandbox-with--g&quot;&gt;Apply a rule to every sandbox with -g&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;When a domain is something every project on your machine needs (a model provider API, your internal package mirror), set it globally:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;policy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;allow&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;network&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-g&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;api.anthropic.com,*.npmjs.org,*.pypi.org&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Global rules are convenient and also a bigger commitment, because they widen the network for sandboxes you have not thought about yet. Prefer per-sandbox rules for anything task-specific and reserve &lt;code dir=&quot;auto&quot;&gt;-g&lt;/code&gt; for genuinely universal hosts.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;the-allow-all-escape-hatch&quot;&gt;The allow-all escape hatch&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;You can open a single sandbox completely with the double-asterisk wildcard, which opts it out of network filtering:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;policy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;allow&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;network&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-claude-my-app-a1b2c3d4&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;**&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Use &lt;code dir=&quot;auto&quot;&gt;&quot;**&quot;&lt;/code&gt; sparingly and on purpose. It is the right tool when you genuinely cannot enumerate the domains a task needs and you accept the tradeoff for that one sandbox. It is the wrong default, because it throws away the network half of the boundary you set up the sandbox to get. If you find yourself reaching for &lt;code dir=&quot;auto&quot;&gt;&quot;**&quot;&lt;/code&gt; on every run, that is a signal to capture the real allowlist instead, which the practical section below walks through.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;inspect-log-and-revoke&quot;&gt;Inspect, log, and revoke&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;You do not have to guess what the gate is doing. List the active rules:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;policy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ls&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Watch what the agent actually tried to reach, which is the fastest way to turn a blocked install into a precise allow rule:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;policy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;And remove access with the mirror of &lt;code dir=&quot;auto&quot;&gt;allow&lt;/code&gt;. A deny rule blocks a host even inside a broader allow:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;policy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;deny&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;network&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-claude-my-app-a1b2c3d4&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ads.example.com&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;policy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;deny&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;network&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-g&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;telemetry.example.com&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The workflow that scales is: run the task, read &lt;code dir=&quot;auto&quot;&gt;sbx policy log&lt;/code&gt; for the refused hosts, add a tight allow rule for the ones the task legitimately needs, and re-run. After two iterations you usually have the exact allowlist a project needs, and you never touched &lt;code dir=&quot;auto&quot;&gt;&quot;**&quot;&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-domain-matching-works&quot;&gt;How domain matching works&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The matching rules are simple once you have seen them, and getting them wrong is the usual reason an allow rule appears to do nothing. The &lt;a href=&quot;https://docs.docker.com/ai/sandboxes/security/policy/&quot;&gt;policies reference&lt;/a&gt; is the source of truth, and here is the working summary.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;exact-domains-versus-wildcard-subdomains&quot;&gt;Exact domains versus wildcard subdomains&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;A bare domain matches only itself:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;example.com&lt;/code&gt; matches &lt;code dir=&quot;auto&quot;&gt;example.com&lt;/code&gt; on any port. It does NOT match &lt;code dir=&quot;auto&quot;&gt;api.example.com&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;*.example.com&lt;/code&gt; matches subdomains like &lt;code dir=&quot;auto&quot;&gt;api.example.com&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;cdn.example.com&lt;/code&gt;. It does NOT match the bare &lt;code dir=&quot;auto&quot;&gt;example.com&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To cover both the root and its subdomains, list both patterns:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;policy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;allow&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;network&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-claude-my-app-a1b2c3d4&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;example.com,*.example.com&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This trips people up constantly. A registry that serves its index from the apex domain and its tarballs from a CDN subdomain needs both entries, or installs stall halfway through.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;ports&quot;&gt;Ports&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Append a port to scope a rule to one port:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;example.com:443&lt;/code&gt; matches requests to &lt;code dir=&quot;auto&quot;&gt;example.com&lt;/code&gt; on port 443, the default HTTPS port.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;*.example.com:443&lt;/code&gt; matches any subdomain on port 443.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;example.com&lt;/code&gt; with no port matches any port.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Non-HTTP TCP traffic such as SSH is blocked by default and can be allowed by adding a rule for the destination IP address and port, for example &lt;code dir=&quot;auto&quot;&gt;sbx policy allow network -g &quot;10.1.2.3:22&quot;&lt;/code&gt;. UDP and ICMP are blocked at the network layer and cannot be unblocked with a policy rule, so do not expect to ping out or run a custom UDP protocol from inside the sandbox.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;most-specific-wins-and-deny-beats-allow&quot;&gt;Most specific wins, and deny beats allow&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;When several patterns could match one request, the most specific pattern decides. The order runs from an exact hostname with a port, to an exact hostname on any port, to wildcard patterns (longest match first), to catch-all wildcards, and finally the default policy. That specificity is what lets you block a broad pattern while allowing a narrow exception. You can deny &lt;code dir=&quot;auto&quot;&gt;example.com&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;*.example.com&lt;/code&gt;, then allow &lt;code dir=&quot;auto&quot;&gt;api.example.com:443&lt;/code&gt;, and only that one host and port gets through.&lt;/p&gt;
&lt;p&gt;For the case people ask about most: when an allow rule and a deny rule both match a request at the same specificity, the deny rule wins. Deny is the safer default to lose to, and it means you can layer a tight deny over a broad allow without worrying that the allow quietly re-opens the host.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-this-matters-installs-versus-exfiltration&quot;&gt;Why this matters: installs versus exfiltration&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The reason to bother with any of this is that the two things you want from the network point in opposite directions.&lt;/p&gt;
&lt;p&gt;You want package installs to work. An autonomous agent that cannot run &lt;code dir=&quot;auto&quot;&gt;npm install&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;pip install&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;cargo fetch&lt;/code&gt; is an agent that stalls on its first dependency and burns iterations doing nothing useful. Installs need the registries, the CDNs that serve the actual artifacts, and usually a code host for git dependencies.&lt;/p&gt;
&lt;p&gt;You do not want exfiltration to work. The same outbound channel that pulls a tarball in can push a file out. An agent that has read your project (the whole point of the shared workspace) and unrestricted network can, in principle, POST your source or a leaked credential to any host. You are not assuming the model is hostile. You are removing the capability so a confused or prompt-injected agent cannot do the damage even by accident.&lt;/p&gt;
&lt;p&gt;A tight allowlist resolves the tension. Installs flow to the registries and code hosts you named, and there is no open path to an arbitrary collection endpoint. The narrower the list, the smaller the surface. This is the same argument the pillar makes for the filesystem, applied to the wire: enforce the boundary from the outside, and keep the grant minimal. For the broader case on why every autonomous run belongs behind a boundary you control rather than a permission prompt the agent can blow past, the &lt;a href=&quot;https://ralphloop.sh/blog/run-ai-agents-in-docker-sandboxes-safely/&quot;&gt;Docker Sandboxes pillar&lt;/a&gt; is the place to start.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-practical-allowlist-for-a-typical-project&quot;&gt;A practical allowlist for a typical project&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Here is a concrete starting point for a JavaScript or Python project that pulls dependencies and clones a few git repos. Get the sandbox name, then grant the registries and code host in one command.&lt;/p&gt;
&lt;p&gt;For an npm-based project:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--print-name&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;policy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;allow&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;network&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-claude-my-app-a1b2c3d4&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;registry.npmjs.org,*.npmjs.org,github.com,*.githubusercontent.com,codeload.github.com&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;For a Python project add the PyPI hosts:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;policy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;allow&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;network&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-claude-my-app-a1b2c3d4&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;pypi.org,*.pypi.org,files.pythonhosted.org&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;A few notes on why those specific entries:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;registry.npmjs.org&lt;/code&gt; is the index, and &lt;code dir=&quot;auto&quot;&gt;*.npmjs.org&lt;/code&gt; covers the related hosts npm reaches during an install. Listing both the apex and the wildcard avoids the half-finished install problem.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;github.com&lt;/code&gt; handles git operations, &lt;code dir=&quot;auto&quot;&gt;codeload.github.com&lt;/code&gt; serves tarball downloads for git dependencies, and &lt;code dir=&quot;auto&quot;&gt;*.githubusercontent.com&lt;/code&gt; serves raw files and release assets. Git installs that work for the clone but fail on the download usually need &lt;code dir=&quot;auto&quot;&gt;codeload.github.com&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;pypi.org&lt;/code&gt; is the index and &lt;code dir=&quot;auto&quot;&gt;files.pythonhosted.org&lt;/code&gt; is where the wheels and source distributions actually live, so PyPI needs both.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If your agent talks to a model API from inside the sandbox (you are on Locked Down, or you run the CLI by hand in there), allow that host too, for example &lt;code dir=&quot;auto&quot;&gt;api.anthropic.com&lt;/code&gt; for Claude. On a Balanced policy these are often already covered by the baseline allowlist, so check &lt;code dir=&quot;auto&quot;&gt;sbx policy ls&lt;/code&gt; before adding duplicates.&lt;/p&gt;
&lt;p&gt;Then run the loop and walk away. The agent works in bypass-permissions mode, but the network gate is one of the walls that makes that safe:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;When the loop finishes, Ralph stops the sandbox through its exit trap. Your allowlist persists with the sandbox, so the next run starts from the same known-good network posture instead of a blank deny.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-network-policy-fits-the-rest-of-the-boundary&quot;&gt;Where network policy fits the rest of the boundary&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A network policy is one wall, not the whole house. The sandbox also keeps the agent out of your home directory and off your host Docker daemon, which is the filesystem side of the same idea. If you are weighing a Docker Sandbox against a container you wire up yourself, the network gate is a good example of the difference, and &lt;a href=&quot;https://ralphloop.sh/blog/docker-sandboxes-vs-containers-for-agents/&quot;&gt;Docker Sandboxes versus plain containers for AI agents&lt;/a&gt; covers where a hand-rolled setup tends to leak.&lt;/p&gt;
&lt;p&gt;When a request is blocked and you are not sure why, get inside and look. The sandbox is a normal container from the inside, so you can reproduce the failing request, read DNS, and watch &lt;code dir=&quot;auto&quot;&gt;sbx policy log&lt;/code&gt; from the host at the same time. The full routine for that lives in &lt;a href=&quot;https://ralphloop.sh/blog/debug-inside-an-agent-sandbox/&quot;&gt;how to inspect and debug inside an AI agent sandbox&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Finally, the network you allow depends on which agent CLI you run and what it phones home to. Each tool reaches a slightly different set of hosts for auth, models, and telemetry, so the allowlist for a Codex run is not identical to a Gemini run. The &lt;a href=&quot;https://ralphloop.sh/blog/agentic-coding-clis/&quot;&gt;agentic coding CLIs&lt;/a&gt; guide is the cross-hub companion for picking a CLI and knowing what it expects from the wire. Ralph itself is a hackable Bash loop in the tradition of Geoffrey Huntley’s original &lt;a href=&quot;https://ghuntley.com/ralph/&quot;&gt;Ralph technique&lt;/a&gt;, so if a policy rule needs to live somewhere repeatable, it is plain shell you can script next to the loop.&lt;/p&gt;
&lt;p&gt;Set the allowlist once, keep it minimal, and let the agent run. Installs go through, exfiltration does not, and the gate is something you can read in one &lt;code dir=&quot;auto&quot;&gt;sbx policy ls&lt;/code&gt;.&lt;/p&gt;
&lt;section aria-label=&quot;Frequently asked questions&quot;&gt; &lt;h2&gt;Frequently asked questions&lt;/h2&gt; &lt;div&gt; &lt;h3&gt;What is the default network policy for a Docker Sandbox?&lt;/h3&gt; &lt;p&gt;Deny-by-default for outbound HTTP and HTTPS. Any domain not on the allowlist is blocked, and non-HTTP protocols like raw TCP, UDP, DNS, and ICMP are blocked at the network layer. Docker also offers an Open policy that allows everything and a Locked Down policy that blocks even model provider APIs, so check which one your install uses before debugging a refused request.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;How do I allow a domain for an AI agent sandbox?&lt;/h3&gt; &lt;p&gt;Use sbx policy allow network with the sandbox name and the domain, for example sbx policy allow network ralph-claude-my-app-a1b2c3d4 api.example.com. Pass a comma-separated list to allow several at once, add the -g flag to apply the rule to every sandbox on the machine, and quote any pattern that contains an asterisk so your shell does not expand it.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;Why does my allow rule for example.com not match api.example.com?&lt;/h3&gt; &lt;p&gt;Because a bare domain matches only the exact host. example.com does not match its subdomains, and the wildcard form star-dot-example.com does not match the bare root domain. To cover both the apex and its subdomains, allow both patterns in one rule, such as example.com and star-dot-example.com.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;What happens when an allow rule and a deny rule both match a request?&lt;/h3&gt; &lt;p&gt;The most specific matching pattern wins overall, running from an exact hostname with a port, to an exact hostname on any port, to wildcards, to catch-all rules, and finally the default policy. When an allow and a deny match at the same specificity, the deny rule wins, so you can layer a narrow deny over a broad allow safely.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;How do I see which hosts the agent tried to reach?&lt;/h3&gt; &lt;p&gt;Run sbx policy ls to list the active allow and deny rules, and sbx policy log to see the connection history including blocked attempts. Reading the log is the fastest way to turn a failed install into a precise allow rule, because it tells you exactly which hosts were refused so you can add only those.&lt;/p&gt; &lt;/div&gt; &lt;/section&gt; 
&lt;aside aria-label=&quot;Get started with Ralph Loop&quot;&gt; &lt;h2&gt;Run your own Ralph loop&lt;/h2&gt; &lt;p&gt;Ralph is a hackable script you point at your project. Install it and let an agent work through your task list.&lt;/p&gt; &lt;pre&gt;&lt;code&gt;npx @pageai/ralph-loop&lt;/code&gt;&lt;/pre&gt; &lt;div&gt; &lt;a href=&quot;https://www.npmjs.com/package/@pageai/ralph-loop&quot;&gt;
Install from npm
&lt;/a&gt; &lt;a href=&quot;https://github.com/pageai-pro/ralph-loop&quot;&gt;Star on GitHub&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=3TL8Ez66I3o&quot;&gt;Watch the walkthrough&lt;/a&gt; &lt;/div&gt; &lt;/aside&gt;</content:encoded><category>sandboxing</category></item><item><title>Running Agents in YOLO Mode (--dangerously-skip-permissions) Safely</title><link>https://ralphloop.sh/blog/bypass-permissions-yolo-mode-safely/</link><guid isPermaLink="true">https://ralphloop.sh/blog/bypass-permissions-yolo-mode-safely/</guid><description>Bypass-permissions mode hands an agent full shell access with no prompts. That is reckless on your host and a non-event inside a Docker Sandbox. Here is how to run YOLO mode when the blast radius is contained.

</description><pubDate>Fri, 15 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Yes, you can run an agent with &lt;code dir=&quot;auto&quot;&gt;--dangerously-skip-permissions&lt;/code&gt; safely, but only when the agent is locked inside a sandbox. The exact same flag is reckless on your laptop and a non-event inside a Docker Sandbox microVM. The flag never changes. What changes is what the agent can reach when it stops asking permission. This post explains what YOLO mode does, why you want it for unattended loops, and the one rule that makes &lt;code dir=&quot;auto&quot;&gt;dangerously skip permissions safely&lt;/code&gt; an honest phrase instead of a contradiction.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-yolo-mode-actually-means&quot;&gt;What “YOLO mode” actually means&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;YOLO mode is any configuration where the agent stops asking you to approve commands and just executes them. No “can I run this?” prompt, no confirmation step, no human in the loop on each shell call. The agent reads a task, decides what to do, and does it.&lt;/p&gt;
&lt;p&gt;That is the entire appeal and the entire risk in one sentence. The appeal is speed and autonomy. The risk is that you have removed the only gate between a probabilistic model and a real shell.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;claude-code-dangerously-skip-permissions&quot;&gt;Claude Code: —dangerously-skip-permissions&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;In Claude Code the flag is &lt;code dir=&quot;auto&quot;&gt;--dangerously-skip-permissions&lt;/code&gt;. There is an equivalent longer form, &lt;code dir=&quot;auto&quot;&gt;--permission-mode bypassPermissions&lt;/code&gt;, documented in the &lt;a href=&quot;https://code.claude.com/docs&quot;&gt;Claude Code docs&lt;/a&gt;. Both do the same thing: the agent skips the per-action approval prompt for the rest of the session.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;claude&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--dangerously-skip-permissions&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;claude&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--permission-mode&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bypassPermissions&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The name is refreshingly honest. The word “dangerously” is in the flag because Anthropic wants you to feel a little sick typing it on a machine that has your credentials on it. That instinct is correct. The fix is not to avoid the flag. The fix is to change where you run it.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;the-same-idea-in-other-agents&quot;&gt;The same idea in other agents&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Every serious agent CLI has its own version of this switch, because every long autonomous run needs it. The Codex CLI has a full-auto mode. Gemini, Cursor, Copilot, and opencode each expose an auto-accept or yolo style setting that turns off interactive approvals. The spelling differs per tool, and you do not have to memorize each one, because &lt;a href=&quot;https://github.com/pageai-pro/ralph-loop&quot;&gt;Ralph Loop&lt;/a&gt; launches each supported agent in bypass-permissions mode for you. Pass any extra agent-specific flags after a &lt;code dir=&quot;auto&quot;&gt;--&lt;/code&gt; separator:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;codex&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--model&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gpt-5.5&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gemini&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--model&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pro&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Supported agents are &lt;code dir=&quot;auto&quot;&gt;claude&lt;/code&gt; (the default), &lt;code dir=&quot;auto&quot;&gt;codex&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;copilot&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;cursor&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;gemini&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;opencode&lt;/code&gt;. The common thread across all of them is the same trade. You give up the approval prompt to get an agent that can work without you.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-you-want-yolo-mode-for-unattended-loops&quot;&gt;Why you want YOLO mode for unattended loops&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Here is the case for turning the prompts off, because it is easy to read “dangerous” and conclude you should always approve commands by hand. You should not, and the reason is structural.&lt;/p&gt;
&lt;p&gt;An autonomous loop runs the agent many times. A single iteration of a Ralph loop does real work: it finds the highest-priority incomplete task in &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt;, works the steps in the task spec, runs tests, linting, and type checks, commits, and repeats. Across &lt;code dir=&quot;auto&quot;&gt;./ralph.sh -n 50&lt;/code&gt; that is fifty iterations, each one issuing dozens of shell commands. If every command needs your approval, the loop is not autonomous. It is a very slow pair-programming session where you are the bottleneck and you are asleep.&lt;/p&gt;
&lt;p&gt;The whole point of running an agent overnight is that you are not there. A permission prompt at 3am is a permission prompt that blocks forever. The agent stalls on the first &lt;code dir=&quot;auto&quot;&gt;npm install&lt;/code&gt; waiting for a yes that never comes, and you wake up to a loop that did nothing for eight hours.&lt;/p&gt;
&lt;p&gt;So unattended runs and per-command approval are mutually exclusive. You either babysit, or you go YOLO. For a long loop, babysitting defeats the purpose, which means YOLO mode is not an edge case. It is the normal operating mode for any agent meant to run while you are away. If you want the deeper version of this argument applied to a single CLI, the &lt;a href=&quot;https://ralphloop.sh/blog/run-claude-code-in-a-loop/&quot;&gt;run Claude Code in a loop&lt;/a&gt; guide walks through the same trade for autonomous Claude Code runs.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-one-rule-only-go-yolo-when-the-blast-radius-is-contained&quot;&gt;The one rule: only go YOLO when the blast radius is contained&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Now the rule that makes all of this safe. Run YOLO mode only when the blast radius is contained. The blast radius is everything the agent can touch when it stops asking. Your job is to make that set small, disposable, and external to anything you care about.&lt;/p&gt;
&lt;p&gt;A permission prompt is not a security boundary. When the agent asks “can I run this command?” and you click yes, you are the boundary, and you are slow, distracted, and often not awake. When you tell the agent to stop asking, you have not made the agent safe. You have removed the only thing standing between the model and your filesystem. The real fix is to put a wall around the agent that the agent cannot reach through.&lt;/p&gt;
&lt;p&gt;On your host, that wall does not exist. An agent running with your user account can read your SSH keys, your &lt;code dir=&quot;auto&quot;&gt;~/.aws/credentials&lt;/code&gt;, your browser session cookies, and every other git repository you have checked out. It can push to remotes, delete files, and run a &lt;code dir=&quot;auto&quot;&gt;curl | bash&lt;/code&gt; it found in a README. None of that requires malice. It is a probabilistic system executing shell commands, and shell commands do not have an undo. For the longer version of why every autonomous run belongs behind a wall, read &lt;a href=&quot;https://ralphloop.sh/blog/why-sandbox-ai-coding-agents/&quot;&gt;why you should sandbox every autonomous coding agent&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Inside a Docker Sandbox, the wall is real. A Docker Sandbox is not a plain &lt;code dir=&quot;auto&quot;&gt;docker run&lt;/code&gt;. It is a lightweight microVM with its own guest kernel, documented in the &lt;a href=&quot;https://docs.docker.com/ai/sandboxes/&quot;&gt;Docker Sandboxes docs&lt;/a&gt;. The agent sees your project directory and a network gate. It does not see the rest of your machine. Run &lt;code dir=&quot;auto&quot;&gt;--dangerously-skip-permissions&lt;/code&gt; in there and “anything the agent can do” now means “anything it can do inside a microVM that only contains one project directory and a locked-down network.” The dangerous flag becomes a non-event, because the danger had a target on the host and the sandbox removed the target.&lt;/p&gt;
&lt;p&gt;This is the inversion worth internalizing. You are not making the agent trustworthy. You are making trust unnecessary by shrinking the blast radius to something you can reset with git or throw away entirely.&lt;/p&gt;
&lt;pre dir=&quot;ltr&quot;&gt;flowchart TB
  subgraph Laptop[&quot;YOLO on your laptop (host)&quot;]
    HAgent[&quot;Agent: --dangerously-skip-permissions&quot;]
    HAgent --&gt; Keys[&quot;SSH keys, cloud creds, cookies&quot;]
    HAgent --&gt; Repos[&quot;Every other git repo&quot;]
    HAgent --&gt; Net1[&quot;Open network: fetch any URL&quot;]
  end
  subgraph Box[&quot;YOLO inside a Docker Sandbox&quot;]
    SAgent[&quot;Agent: --dangerously-skip-permissions&quot;]
    SAgent --&gt; Proj[&quot;Shared project directory only&quot;]
    SAgent --&gt; Gate[&quot;Network gate: deny-by-default&quot;]
    SAgent -. &quot;no path to&quot; .-&gt; HostHome[&quot;Host home directory&quot;]
  end&lt;/pre&gt;
&lt;p&gt;Same flag in both boxes. The only difference is the box. On the left, “skip permissions” means skip the last protection your machine had. On the right, it means move fast inside a container that contains nothing precious.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-ralph-runs-yolo-mode-inside-a-named-sandbox&quot;&gt;How Ralph runs YOLO mode inside a named sandbox&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;You can wire this up by hand: create a microVM, launch the agent with the bypass flag, and tear it down when you are done. The point of Ralph is that you do not have to. It computes the sandbox name, checks that the &lt;code dir=&quot;auto&quot;&gt;sbx&lt;/code&gt; CLI exists, decides whether to create or attach, launches the agent in bypass-permissions mode, and stops the sandbox on exit. Ralph is a hackable Bash loop in the tradition of Geoffrey Huntley’s original &lt;a href=&quot;https://ghuntley.com/ralph/&quot;&gt;Ralph technique&lt;/a&gt;, and the YOLO plumbing is part of what it automates.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;answering-yes-to-bypass-permissions-inside-the-sandbox&quot;&gt;Answering “Yes” to bypass permissions inside the sandbox&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The first time you launch an agent in bypass mode, the CLI shows a one-time warning that asks you to confirm you really want to skip permission prompts. With Ralph, you hit that confirmation inside the sandbox, not on your host, which is exactly where you want to be saying yes. Authenticate and accept the bypass warning in one step with the login action:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--login&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--login&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;codex&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This opens the selected agent inside its correctly named sandbox. You answer the bypass-permissions prompt once, in the microVM, and from then on the agent runs without prompts inside that contained environment. Saying yes to “skip permissions” feels very different when the worst case is a disposable container instead of your home directory.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;the-deterministic-sandbox-name-is-the-boundary&quot;&gt;The deterministic sandbox name is the boundary&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Ralph names every sandbox deterministically so the same project and agent pair always reuses the same microVM. The format is:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ralph-&amp;#x3C;agent&gt;-&amp;#x3C;current-dir&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;agent&gt;&lt;/code&gt; slug is the lowercased agent name, &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;current-dir&gt;&lt;/code&gt; is the sanitized basename of your project directory, and &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;hash8&gt;&lt;/code&gt; is the first 8 hex characters of a &lt;code dir=&quot;auto&quot;&gt;sha256&lt;/code&gt; of the absolute project path. A project at &lt;code dir=&quot;auto&quot;&gt;/Users/me/Work/My App&lt;/code&gt; running Claude becomes &lt;code dir=&quot;auto&quot;&gt;ralph-claude-my-app-a1b2c3d4&lt;/code&gt;. The path hash keeps two same-named directories on different paths from colliding, and the agent slug in the name means switching &lt;code dir=&quot;auto&quot;&gt;--agent&lt;/code&gt; gives you a separate sandbox rather than a surprising state swap.&lt;/p&gt;
&lt;p&gt;You never have to memorize it. Ralph prints the name on its startup line, and you can ask for it directly:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--print-name&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--print-name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cursor&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That name is the handle for everything else: the sandbox you shell into, the sandbox you allowlist a domain for, and the sandbox Ralph stops on exit.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;create-versus-attach-every-iteration&quot;&gt;Create versus attach, every iteration&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Each iteration of the loop probes whether the deterministic sandbox already exists. Iteration one usually creates it. Iteration two and onward attach to it. If you manually &lt;code dir=&quot;auto&quot;&gt;sbx rm&lt;/code&gt; the sandbox mid-run, the next probe simply creates it again. That re-probe is what makes a long YOLO run resilient to you poking at the sandbox by hand.&lt;/p&gt;
&lt;pre dir=&quot;ltr&quot;&gt;flowchart TD
  Start[&quot;./ralph.sh -n 50&quot;] --&gt; Name[&quot;Compute name: ralph-agent-dir-hash8&quot;]
  Name --&gt; CheckSbx{&quot;sbx installed?&quot;}
  CheckSbx --&gt;|&quot;no&quot;| Fail[&quot;Exit with docs link&quot;]
  CheckSbx --&gt;|&quot;yes&quot;| Iter[&quot;Start iteration&quot;]
  Iter --&gt; Probe{&quot;sandbox exists?&quot;}
  Probe --&gt;|&quot;no&quot;| Create[&quot;sbx run --name ... agent .&quot;]
  Probe --&gt;|&quot;yes&quot;| Attach[&quot;sbx run name&quot;]
  Create --&gt; Work[&quot;Agent works one task in YOLO mode&quot;]
  Attach --&gt; Work
  Work --&gt; Signal{&quot;completion signal?&quot;}
  Signal --&gt;|&quot;COMPLETE&quot;| Done[&quot;Exit 0, then sbx stop&quot;]
  Signal --&gt;|&quot;keep going&quot;| Iter&lt;/pre&gt;
&lt;p&gt;The loop stops on an explicit signal, not a vibe. The agent emits a promise tag: &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;COMPLETE&amp;#x3C;/promise&gt;&lt;/code&gt; when all tasks are done, &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;BLOCKED:reason&amp;#x3C;/promise&gt;&lt;/code&gt; when it needs human help, or &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;DECIDE:question&amp;#x3C;/promise&gt;&lt;/code&gt; when it needs a decision. Those map to exit codes &lt;code dir=&quot;auto&quot;&gt;0&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;2&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;3&lt;/code&gt;, with &lt;code dir=&quot;auto&quot;&gt;1&lt;/code&gt; reserved for hitting the iteration cap. When the loop ends, Ralph stops the sandbox through its exit trap and hands you back a contained microVM you can inspect or discard.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-safe-yolo-run-end-to-end&quot;&gt;A safe YOLO run, end to end&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Putting the pieces in order, here is what a clean bypass-permissions run looks like. None of these steps put the agent’s hands on your host.&lt;/p&gt;
&lt;p&gt;First, install Ralph in your project:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@pageai/ralph-loop&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Next, authenticate the agent inside its sandbox and accept the bypass-permissions warning once, in the microVM:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--login&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If the task needs network access for installs, allowlist only the domains it needs instead of opening everything. Get the name, then add a rule:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--print-name&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;policy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;allow&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;network&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-claude-my-app-a1b2c3d4&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;*.npmjs.org,github.com&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The network gate matters as much as the filesystem boundary when the agent runs unattended in YOLO mode. A tight allowlist lets installs through and keeps exfiltration out. The full treatment of building that allowlist lives in &lt;a href=&quot;https://ralphloop.sh/blog/ai-agent-sandbox-network-policies/&quot;&gt;network policies for AI agent sandboxes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Then run the loop and walk away. The agent runs with permissions bypassed, but the sandbox is the boundary, so fast and contained are the same thing:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If something looks off mid-run, shell in and look. The sandbox is a normal container from the inside:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;exec&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-it&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-claude-my-app-a1b2c3d4&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bash&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;When the loop finishes, Ralph stops the sandbox for you. The agent skipped every prompt and ran at full speed, and your host never had its hands on it. This is the default posture Ralph ships, and the broader setup is covered in the pillar guide on &lt;a href=&quot;https://ralphloop.sh/blog/run-ai-agents-in-docker-sandboxes-safely/&quot;&gt;running AI coding agents in Docker Sandboxes safely&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-yolo-mode-still-bites&quot;&gt;Where YOLO mode still bites&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A sandbox is a strong boundary and not a magic one. Two limits are worth naming so you do not over-trust the setup.&lt;/p&gt;
&lt;p&gt;First, the project directory is shared, which is the entire point, so the agent can absolutely wreck your working tree. The protection there is git, not the sandbox. Commit often, work on a branch, and treat the sandbox as protection for everything outside the project rather than a substitute for version control inside it. A YOLO agent that thrashes on a bad task can still produce a messy diff. The fix is the same as for any agent: clean tasks, frequent commits, and a verification gate of tests, type checks, and linting on every iteration.&lt;/p&gt;
&lt;p&gt;Second, whatever you allowlist on the network is genuinely reachable. If you grant a domain that can receive uploads, an agent in YOLO mode could in principle send data there. A &lt;code dir=&quot;auto&quot;&gt;&quot;**&quot;&lt;/code&gt; allow-all rule throws the whole network surface wide open and undoes half the reason you built the sandbox. Treat network grants like firewall rules: minimal, specific, and reviewed.&lt;/p&gt;
&lt;p&gt;Inside those limits, the model is simple and it holds. Skip permissions only when the blast radius is a disposable microVM, enforce the boundary from the outside, and let the agent be fearless where fearless is cheap.&lt;/p&gt;
&lt;section aria-label=&quot;Frequently asked questions&quot;&gt; &lt;h2&gt;Frequently asked questions&lt;/h2&gt; &lt;div&gt; &lt;h3&gt;Is it safe to run an agent with --dangerously-skip-permissions?&lt;/h3&gt; &lt;p&gt;It is unsafe on your host and safe inside a Docker Sandbox. On the host the flag gives the agent full shell access with no confirmation, so a single bad command can touch your SSH keys, credentials, or other repositories. Inside a microVM sandbox the same flag only grants access to the shared project directory and an allowlisted network, so the worst case is something you can reset with git or throw away.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;What is YOLO mode for an AI coding agent?&lt;/h3&gt; &lt;p&gt;YOLO mode is any configuration where the agent stops asking you to approve each command and just executes. In Claude Code it is the flag --dangerously-skip-permissions, also written as --permission-mode bypassPermissions. Other agents have their own full-auto or auto-accept setting that does the same thing.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;Why do unattended agent loops need bypass-permissions mode?&lt;/h3&gt; &lt;p&gt;A long loop issues many shell commands per iteration. If every command needs your approval, the loop blocks the moment you step away and does nothing until you return. Bypass-permissions mode is what lets an agent run overnight without a human approving each action.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;Does Ralph Loop run agents in YOLO mode by default?&lt;/h3&gt; &lt;p&gt;Yes. Ralph launches each supported agent in bypass-permissions mode because the Docker Sandbox is the boundary. You accept the one-time bypass warning inside the sandbox during ./ralph.sh --login, and every iteration after that runs without prompts inside the contained microVM.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;Can a sandboxed YOLO agent still cause damage?&lt;/h3&gt; &lt;p&gt;It can damage the shared project directory, since that directory is shared on purpose, so commit often and work on a branch. It can also reach any network domain you allowlist, so keep the allowlist minimal and avoid the allow-all double-star rule. It cannot reach the rest of your host, which is the boundary the sandbox enforces.&lt;/p&gt; &lt;/div&gt; &lt;/section&gt; 
&lt;aside aria-label=&quot;Get started with Ralph Loop&quot;&gt; &lt;h2&gt;Run your own Ralph loop&lt;/h2&gt; &lt;p&gt;Ralph is a hackable script you point at your project. Install it and let an agent work through your task list.&lt;/p&gt; &lt;pre&gt;&lt;code&gt;npx @pageai/ralph-loop&lt;/code&gt;&lt;/pre&gt; &lt;div&gt; &lt;a href=&quot;https://www.npmjs.com/package/@pageai/ralph-loop&quot;&gt;
Install from npm
&lt;/a&gt; &lt;a href=&quot;https://github.com/pageai-pro/ralph-loop&quot;&gt;Star on GitHub&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=3TL8Ez66I3o&quot;&gt;Watch the walkthrough&lt;/a&gt; &lt;/div&gt; &lt;/aside&gt;</content:encoded><category>sandboxing</category></item><item><title>Spec-Driven Development vs Vibe Coding: When Each One Wins</title><link>https://ralphloop.sh/blog/spec-driven-development-vs-vibe-coding/</link><guid isPermaLink="true">https://ralphloop.sh/blog/spec-driven-development-vs-vibe-coding/</guid><description>Vibe coding is fast for throwaways and dangerous for production. Here is an honest comparison with spec-driven development, why autonomous agents need a spec, and how to pick per task.

</description><pubDate>Sun, 10 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Vibe coding wins when the code is disposable. Spec-driven development wins when the code has to survive: production systems, code other people maintain, and anything an autonomous agent builds while you are asleep. The split is not about taste. It is about whether the work has verifiable acceptance criteria or just a feeling that the output looks right.&lt;/p&gt;
&lt;p&gt;This post defines both honestly, including where vibe coding is genuinely the correct choice. Then it covers why an autonomous loop has no choice but to go spec-driven, what vibe coding actually costs once it scales past one person and one afternoon, and how to pick per task instead of picking a religion. It ends with how Ralph leans spec-driven through a PRD and a task list, without forcing you to spec a five line script.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-vibe-coding-actually-is&quot;&gt;What vibe coding actually is&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Vibe coding is writing code by feel. You describe what you want in loose terms, you read the output, you eyeball whether it looks correct, and you keep nudging until it runs. The acceptance test is your gut. The spec lives in your head and changes as you go.&lt;/p&gt;
&lt;p&gt;For an AI workflow, vibe coding means prompting an agent with intent and judging the result by reading it. “Add a settings page.” “Make this faster.” “Fix the layout on mobile.” You accept the diff because it looks plausible and the page renders, not because a test confirmed a specific condition.&lt;/p&gt;
&lt;p&gt;This is not an insult. Vibe coding is the fastest way to explore an idea you do not fully understand yet. When the goal is to learn the shape of a problem, writing a spec first is premature: you do not know enough to write a good one. You vibe a spike, see what the problem actually wants, then throw the spike away.&lt;/p&gt;
&lt;p&gt;Where vibe coding is genuinely fine:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Spikes and prototypes you intend to delete.&lt;/li&gt;
&lt;li&gt;One off scripts: a data migration you run once, a quick scraper, a throwaway chart.&lt;/li&gt;
&lt;li&gt;Demos and hackathon code where shipping in an hour beats shipping correctly.&lt;/li&gt;
&lt;li&gt;Exploratory work where the spec would just be a guess anyway.&lt;/li&gt;
&lt;li&gt;Personal tools with exactly one user who is also the author.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The common thread is that nobody inherits the code. There is no future maintainer, no on call engineer, no agent that has to extend it next week. When the blast radius of a mistake is your own afternoon, the overhead of a spec is not worth it. Vibe away.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-spec-driven-development-actually-is&quot;&gt;What spec-driven development actually is&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Spec-driven development inverts the order. You write the specification first, then the code exists to satisfy it. The spec is not a wishlist. It is goals (what to build and why), constraints (the stack, the boundaries, what is out of scope), and verifiable acceptance criteria (conditions something can check by running a command and reading the output).&lt;/p&gt;
&lt;p&gt;The phases come from &lt;a href=&quot;https://github.com/github/spec-kit&quot;&gt;GitHub Spec Kit&lt;/a&gt;: specify the intent, plan the approach, break it into tasks, then implement and verify. Each phase produces an artifact the next phase consumes. The full version of this workflow, with PRDs, task lists, and breakdown, is laid out in &lt;a href=&quot;https://ralphloop.sh/blog/spec-driven-development-with-ai/&quot;&gt;spec-driven development with AI&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The defining property is that “done” is not a feeling. A spec-driven task is done when a specific, checkable condition is true. “Login works” is a vibe. “POST /api/login with a wrong password returns 401 and the body &lt;code dir=&quot;auto&quot;&gt;{ error: &apos;Invalid credentials&apos; }&lt;/code&gt;” is a criterion a machine can confirm without your opinion. That single difference is what makes the rest of this comparison fall out.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-an-autonomous-agent-needs-a-spec&quot;&gt;Why an autonomous agent needs a spec&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Here is the part that decides the whole debate for anyone running agents in a loop. A human can vibe code because a human carries the unwritten spec. You know the system. You remember the edge case from last month. You ask a teammate when something is ambiguous. An autonomous agent does none of that.&lt;/p&gt;
&lt;p&gt;An agent reads what is on disk. When the spec is silent, it does not pause and ask. It invents an answer and proceeds with full confidence. The guess looks fine in the diff and breaks on the case nobody wrote down. Multiply that across a long run and you get a pile of confident, untested code that drifts further from your intent on every iteration.&lt;/p&gt;
&lt;p&gt;The Ralph technique makes this concrete. Each iteration starts the agent with a fresh context window, which is what keeps a long run from rotting (the agent losing the plot over a marathon session). The mechanics are covered in &lt;a href=&quot;https://ralphloop.sh/blog/what-is-the-ralph-technique/&quot;&gt;what is the Ralph technique&lt;/a&gt;, the loop popularized by Geoffrey Huntley in his &lt;a href=&quot;https://ghuntley.com/ralph/&quot;&gt;original Ralph writeup&lt;/a&gt;. Fresh context is the reason the loop survives. It is also the reason vibe coding cannot drive it.&lt;/p&gt;
&lt;p&gt;Think about what fresh context implies. The agent that starts iteration 30 has no memory of the conversation from iterations 1 through 29. It rebuilds its entire understanding from files: the PRD, the task list, the logs, the git history. There is no “you know what I meant” to fall back on. If the intent is not written down, it does not exist for that iteration’s agent.&lt;/p&gt;
&lt;p&gt;So the loop needs the spec for two reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No guessing.&lt;/strong&gt; Written goals and constraints mean the fresh-context agent reorients to the same target every pass instead of inventing a new one.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A stop condition.&lt;/strong&gt; The loop ends on an explicit completion signal, not a vibe. The agent emits &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;COMPLETE&amp;#x3C;/promise&gt;&lt;/code&gt; when every task passes its criteria, and &lt;code dir=&quot;auto&quot;&gt;ralph.sh&lt;/code&gt; exits with code &lt;code dir=&quot;auto&quot;&gt;0&lt;/code&gt;. Without verifiable criteria there is nothing for the loop to check, so it can never honestly say it is done.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That is the core argument. You cannot run an agent unattended against “make it good.” You can run it against a task list where every task carries acceptance criteria the verification stack can confirm.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-vibe-coding-costs-at-scale&quot;&gt;What vibe coding costs at scale&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Vibe coding feels free because the cost is deferred. It shows up later, somewhere else, and usually larger. Three costs dominate once the work outgrows a single afternoon.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rework.&lt;/strong&gt; Code accepted on a vibe gets rejected on a test you write three weeks later, or worse, by an incident. The fix is rarely a one line change, because the original code encoded a misunderstanding, not a typo. You are not patching a bug. You are re-deriving the requirement that was never written down, then rewriting against it. Specifying first front-loads that thinking when it is cheap.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Drift.&lt;/strong&gt; Every implicit decision is a decision someone, or some agent, makes for you. Vibe coding scatters those decisions across files and weeks. One place validates email with a regex, another with a library, a third not at all. There was never a source of truth, so the codebase has three answers to one question. With a spec, the answer is written once and every task inherits it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Unverifiable output.&lt;/strong&gt; This is the expensive one for AI workflows. If you cannot state how to check that the work is correct, you cannot automate the check, which means a person has to read every diff and judge it by hand. That person becomes the bottleneck. The whole promise of an autonomous loop is that the machine verifies its own work. Vibe coding removes the thing the machine would verify against, so you are back to manual review at the exact moment you wanted to step away.&lt;/p&gt;
&lt;p&gt;The repo mantra is blunt: if you didn’t test it, it doesn’t work. Vibe coding at scale is a bet that you will remember every unwritten assumption and that nobody else will touch the code. Both halves of that bet lose over time.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-to-pick-per-task&quot;&gt;How to pick per task&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The honest answer is that you do not pick one approach for your whole life. You pick per task, and the deciding questions are quick.&lt;/p&gt;
&lt;pre dir=&quot;ltr&quot;&gt;flowchart TD
  Start[&quot;New piece of work&quot;] --&gt; Survive{&quot;Does the code survive past today?&quot;}
  Survive --&gt;|&quot;No, it is a spike or demo&quot;| Vibe[&quot;Vibe code it, delete it later&quot;]
  Survive --&gt;|&quot;Yes&quot;| Criteria{&quot;Can you write pass or fail acceptance criteria?&quot;}
  Criteria --&gt;|&quot;Not yet, too fuzzy&quot;| Spike[&quot;Vibe a spike first, then spec what you learned&quot;]
  Criteria --&gt;|&quot;Yes&quot;| Agent{&quot;Will an autonomous agent build it unattended?&quot;}
  Agent --&gt;|&quot;No, you are at the keyboard&quot;| Mixed[&quot;Spec the risky parts, vibe the glue&quot;]
  Agent --&gt;|&quot;Yes&quot;| Spec[&quot;Spec-driven: PRD, tasks, criteria, verify&quot;]&lt;/pre&gt;
&lt;p&gt;Walk the branches:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Does it survive past today?&lt;/strong&gt; If the answer is no, stop reading and vibe it. A migration script you run once does not need a PRD.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Can you write pass or fail criteria?&lt;/strong&gt; If the problem is still fuzzy, you do not know enough to spec well. Vibe a spike to learn the shape, then write the spec from what you learned. The spike was reconnaissance, not the deliverable.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Will an agent build it unattended?&lt;/strong&gt; This is the hard cutoff. The moment a fresh-context agent is doing the work without you watching, you need the spec. There is no in between, because the agent has no judgment to fall back on and no way to ask you mid-iteration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You at the keyboard, code that survives?&lt;/strong&gt; This is the common case, and it is mixed. Spec the parts where a wrong guess is expensive (auth, money, data integrity, public APIs). Vibe the glue where a mistake is cheap and obvious. You do not need acceptance criteria for a button’s hover color.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The mistake is treating this as ideology. Spec-driven purists waste hours writing criteria for code they will delete. Vibe coding diehards ship confident nonsense to production. The discipline is matching the rigor to the stakes.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-ralph-leans-spec-driven&quot;&gt;How Ralph leans spec-driven&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Ralph is built for the unattended case, so it leans spec-driven by design. It does not force you to write a PRD for a one line fix, but the loop architecture assumes a spec exists when you run it for real.&lt;/p&gt;
&lt;p&gt;The spec lives in two files under &lt;code dir=&quot;auto&quot;&gt;.agent/prd/&lt;/code&gt;. &lt;code dir=&quot;auto&quot;&gt;PRD.md&lt;/code&gt; is the full document: goals, constraints, core features, technical stack, security considerations, and assumptions. &lt;code dir=&quot;auto&quot;&gt;SUMMARY.md&lt;/code&gt; is the short executive overview sent to the agent every iteration so it reorients fast without rereading the entire PRD. Long document for depth, short summary for the working context on each pass.&lt;/p&gt;
&lt;p&gt;You do not write all of this by hand. The &lt;code dir=&quot;auto&quot;&gt;prd-creator&lt;/code&gt; skill, run in plan mode, interviews you one question at a time, researches the problem, and writes both files plus a task list. The details of writing that document, and acceptance criteria an agent can actually verify, are in &lt;a href=&quot;https://ralphloop.sh/blog/how-to-write-prd-for-ai-agent/&quot;&gt;how to write a PRD an AI agent can actually build from&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The PRD then decomposes into a task lookup table. &lt;code dir=&quot;auto&quot;&gt;tasks.json&lt;/code&gt; holds the list, and each &lt;code dir=&quot;auto&quot;&gt;tasks/TASK-{ID}.json&lt;/code&gt; carries its own description, dependencies, and an &lt;code dir=&quot;auto&quot;&gt;acceptanceCriteria&lt;/code&gt; array. This is the structure that lets a loop grind through hundreds of tasks without losing track, covered in &lt;a href=&quot;https://ralphloop.sh/blog/task-lookup-table-for-agents/&quot;&gt;task lookup tables for agents&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@pageai/ralph-loop&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Each iteration the loop finds the highest-priority incomplete task, works its steps, runs tests and linting and type checking, takes a screenshot, flips the task to passing only when the gate is green, and commits. The verification stack is Playwright, Vitest, TypeScript, ESLint, and Prettier. The agent does not get to declare victory on a vibe. It declares victory when the criteria pass, and the loop stops on the completion promise.&lt;/p&gt;
&lt;p&gt;That is the whole point of the comparison in one workflow. The spec is what lets the machine verify its own output, which is what lets you close the laptop. Vibe coding is the right tool when nobody inherits the code. The moment an agent inherits it, the spec stops being overhead and starts being the only thing keeping the loop honest.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-to-go-next&quot;&gt;Where to go next&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you are deciding how much structure a piece of work deserves, read across the spec-driven cluster:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ralphloop.sh/blog/spec-driven-development-with-ai/&quot;&gt;Spec-driven development with AI&lt;/a&gt; for the full Specify, Plan, Tasks, Implement workflow.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ralphloop.sh/blog/how-to-write-prd-for-ai-agent/&quot;&gt;How to write a PRD an AI agent can actually build from&lt;/a&gt; for the document that sits at the top of it.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ralphloop.sh/blog/task-lookup-table-for-agents/&quot;&gt;Task lookup tables for agents&lt;/a&gt; for scaling a spec to hundreds of tasks.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the loop that reads your spec on every pass and why fresh context changes the rules, read &lt;a href=&quot;https://ralphloop.sh/blog/what-is-the-ralph-technique/&quot;&gt;what is the Ralph technique&lt;/a&gt;.&lt;/p&gt;
&lt;section aria-label=&quot;Frequently asked questions&quot;&gt; &lt;h2&gt;Frequently asked questions&lt;/h2&gt; &lt;div&gt; &lt;h3&gt;What is the difference between spec-driven development and vibe coding?&lt;/h3&gt; &lt;p&gt;Vibe coding writes code by feel: you prompt with loose intent, read the output, and accept it because it looks plausible and runs. Spec-driven development writes the specification first, so the code exists to satisfy concrete goals, explicit constraints, and verifiable acceptance criteria. The defining difference is whether done is a feeling or a checkable condition a machine can confirm by running a command and reading the output.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;Is vibe coding ever the right choice?&lt;/h3&gt; &lt;p&gt;Yes, when the code is disposable and nobody inherits it. Spikes you intend to delete, one off scripts, demos, hackathon code, and personal tools with one user are all fine to vibe. The overhead of a spec is not worth it when the blast radius of a mistake is your own afternoon. Vibe a spike to learn a fuzzy problem, then write the spec from what you learned.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;Why do autonomous AI agents need a spec instead of vibe coding?&lt;/h3&gt; &lt;p&gt;An autonomous agent reads only what is on disk, and a Ralph loop starts each iteration with a fresh context window that has no memory of previous iterations. When the spec is silent, the agent invents an answer and proceeds with full confidence, and that guess compounds across a long run. Verifiable acceptance criteria give the loop something to check and a real stop condition, so it can honestly signal completion instead of guessing it is done.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;What does vibe coding cost once a project scales?&lt;/h3&gt; &lt;p&gt;Three things: rework, because code accepted on a vibe encodes a misunderstanding you later re-derive and rewrite; drift, because every implicit decision gets answered differently across files with no source of truth; and unverifiable output, because if you cannot state how to check correctness, a person has to read every diff by hand. That manual review is the exact bottleneck an autonomous loop is supposed to remove.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;How does Ralph use spec-driven development?&lt;/h3&gt; &lt;p&gt;Ralph keeps the spec in .agent/prd/PRD.md and a short SUMMARY.md sent to the agent each iteration, generated by the prd-creator skill in plan mode. The PRD decomposes into a task lookup table where each TASK-ID.json carries acceptance criteria. Every iteration the loop works one task, runs Playwright, Vitest, TypeScript, ESLint, and Prettier, and only marks the task passing when the gate is green, then commits and stops on a completion promise.&lt;/p&gt; &lt;/div&gt; &lt;/section&gt; 
&lt;aside aria-label=&quot;Get started with Ralph Loop&quot;&gt; &lt;h2&gt;Run your own Ralph loop&lt;/h2&gt; &lt;p&gt;Ralph is a hackable script you point at your project. Install it and let an agent work through your task list.&lt;/p&gt; &lt;pre&gt;&lt;code&gt;npx @pageai/ralph-loop&lt;/code&gt;&lt;/pre&gt; &lt;div&gt; &lt;a href=&quot;https://www.npmjs.com/package/@pageai/ralph-loop&quot;&gt;
Install from npm
&lt;/a&gt; &lt;a href=&quot;https://github.com/pageai-pro/ralph-loop&quot;&gt;Star on GitHub&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=3TL8Ez66I3o&quot;&gt;Watch the walkthrough&lt;/a&gt; &lt;/div&gt; &lt;/aside&gt;</content:encoded><category>spec-driven</category></item><item><title>Docker Sandboxes vs Plain Containers for AI Agents</title><link>https://ralphloop.sh/blog/docker-sandboxes-vs-containers-for-agents/</link><guid isPermaLink="true">https://ralphloop.sh/blog/docker-sandboxes-vs-containers-for-agents/</guid><description>A plain Docker container is a start, not a boundary. Here is how Docker Sandboxes microVMs compare to a hand-rolled container for isolating an autonomous coding agent.

</description><pubDate>Tue, 05 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A plain container is a start, not a boundary. Running an autonomous coding agent inside &lt;code dir=&quot;auto&quot;&gt;docker run&lt;/code&gt; does isolate it from your host filesystem, which is already better than running the agent directly on your laptop. The gap is that a normal container shares your host kernel, so the wall between an agent executing arbitrary shell commands and your machine is thinner than most people assume. Docker Sandboxes close that gap by giving each sandbox its own microVM with a separate guest kernel, which is the difference this post is about when you compare a &lt;code dir=&quot;auto&quot;&gt;docker sandbox vs container ai agent&lt;/code&gt; setup.&lt;/p&gt;
&lt;p&gt;If you want the full safety argument first, the pillar guide on &lt;a href=&quot;https://ralphloop.sh/blog/run-ai-agents-in-docker-sandboxes-safely/&quot;&gt;how to run AI coding agents in Docker Sandboxes safely&lt;/a&gt; covers the whole boundary. This post zooms in on one question: what does a hand-rolled container actually give you, where does it leak, and why does a microVM hold where a container does not.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;container-vs-sandbox-the-short-answer&quot;&gt;Container vs sandbox, the short answer&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The decision comes down to what you are isolating from and how much you trust the workload.&lt;/p&gt;
&lt;p&gt;A container is the right tool for software you trust. You built it, you reviewed it, and you are shipping it. Namespaces and cgroups keep processes tidy and resource bounded, and the shared kernel is a feature because it is fast and cheap.&lt;/p&gt;
&lt;p&gt;A microVM is the right tool for software you explicitly do not trust to behave. An autonomous agent running in bypass-permissions mode is exactly that. It decides which commands to run, it can &lt;code dir=&quot;auto&quot;&gt;curl | bash&lt;/code&gt; something it found in a README, and it does not have an undo. For that workload you want a virtualization boundary, not just a kernel namespace.&lt;/p&gt;
&lt;p&gt;So the short answer: use a container when the code is yours and reviewed, use a sandbox when a probabilistic system is executing shell commands on your behalf. The rest of this post is the detail behind that rule.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-a-hand-rolled-container-actually-gives-you&quot;&gt;What a hand-rolled container actually gives you&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Start with the honest version of the container story, because containers are useful and a &lt;code dir=&quot;auto&quot;&gt;docker run&lt;/code&gt; is genuinely a step up from nothing.&lt;/p&gt;
&lt;p&gt;A container gives the agent its own process namespace, its own mount namespace, its own network namespace, and a cgroup that can cap CPU and memory. The agent sees its own process tree, not yours. It sees the filesystem you mounted, not your whole home directory by default. That isolation is real, and for a lot of workloads it is enough.&lt;/p&gt;
&lt;p&gt;Here is a minimal version of what people reach for first:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;docker&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-it&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--rm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-v&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$PWD&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;:/work&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-w&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/work&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;node:22&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bash&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This drops you into a container with your project mounted at &lt;code dir=&quot;auto&quot;&gt;/work&lt;/code&gt;. The agent can edit those files, run installs, and run tests. Your &lt;code dir=&quot;auto&quot;&gt;~/.ssh&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;~/.aws&lt;/code&gt; are not mounted, so they are not visible. For a quick experiment that is a reasonable boundary.&lt;/p&gt;
&lt;p&gt;The problem is that the defaults of a plain container were designed for running trusted software conveniently, not for caging an untrusted process. So the moment you run a real agent for hours, the soft spots start to matter.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;where-the-container-leaks&quot;&gt;Where the container leaks&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;There are four leaks worth naming, because each one is a place a long autonomous run can go wrong.&lt;/p&gt;
&lt;p&gt;First, root by default. Unless you say otherwise, the process inside the container runs as root (uid 0). It is namespaced root, not host root, but it is still root inside the container, which widens what a container escape or a misconfigured mount can reach. An agent that runs &lt;code dir=&quot;auto&quot;&gt;chmod&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;chown&lt;/code&gt;, or installs system packages as root is one surprised step closer to your host than it needs to be.&lt;/p&gt;
&lt;p&gt;Second, the shared kernel. This is the big one. Every container on the machine, including the agent’s, talks to the same host kernel you are using. Namespaces are a kernel feature isolating a process from other processes, not a separate operating system. A kernel level bug, a privileged syscall, or a known container escape has your actual kernel as its target. You are trusting the kernel boundary to hold against code you specifically decided not to trust.&lt;/p&gt;
&lt;p&gt;Third, bind mounts. The convenient &lt;code dir=&quot;auto&quot;&gt;-v &quot;$PWD&quot;:/work&lt;/code&gt; is a two way door. The agent writes to your real working tree on the host, which is the point, but it also means a bad &lt;code dir=&quot;auto&quot;&gt;rm -rf&lt;/code&gt; or an overzealous “let me clean this up” reaches your real files, not a copy. People also tend to mount more than they should over time (a config directory here, a credentials file there) and each added mount is another path out of the box. Mounting the Docker socket (&lt;code dir=&quot;auto&quot;&gt;/var/run/docker.sock&lt;/code&gt;) is the worst case, because a process that can talk to the Docker daemon can start a new container as host root and own the machine.&lt;/p&gt;
&lt;p&gt;Fourth, the network. By default a container gets full outbound network access through the default bridge. An agent with unrestricted egress can fetch arbitrary code and, in the worst case, send data out. There is no allowlist unless you build one, and building one with raw containers means writing firewall rules by hand.&lt;/p&gt;
&lt;p&gt;None of these are reasons to never use a container. They are reasons to not assume &lt;code dir=&quot;auto&quot;&gt;docker run&lt;/code&gt; is a security boundary for an untrusted agent. For the broader case on why every autonomous run needs a wall around it, see &lt;a href=&quot;https://ralphloop.sh/blog/why-sandbox-ai-coding-agents/&quot;&gt;why you should sandbox every autonomous coding agent&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-docker-sandboxes-differ&quot;&gt;How Docker Sandboxes differ&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Docker Sandboxes (the &lt;code dir=&quot;auto&quot;&gt;sbx&lt;/code&gt; CLI) are built for the untrusted workload case, and the &lt;a href=&quot;https://docs.docker.com/ai/sandboxes/&quot;&gt;Docker Sandboxes documentation&lt;/a&gt; is the primary source for the internals. Three differences matter most for agents.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;a-microvm-per-sandbox&quot;&gt;A microVM per sandbox&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Each sandbox runs inside its own lightweight virtual machine with its own guest kernel, not a namespaced process sharing your host kernel. That single change is what upgrades the boundary from “trust the kernel namespace” to “defeat a virtualization layer.” A process that escapes the agent’s view inside the sandbox lands in a guest kernel that is not your kernel, with a virtual machine monitor between it and your host.&lt;/p&gt;
&lt;p&gt;For the agent this is invisible. It still gets a Linux environment, a shell, a filesystem, and the tools it expects. The isolation is underneath, where the agent cannot see it and does not need to.&lt;/p&gt;
&lt;pre dir=&quot;ltr&quot;&gt;flowchart TB
  subgraph PlainPath[&quot;Plain container&quot;]
    direction TB
    PApp[&quot;Agent process, often root&quot;]
    PNS[&quot;Namespaces + cgroups&quot;]
    PKernel[&quot;Shared host kernel&quot;]
    PHost[&quot;Host: files, keys, network&quot;]
    PApp --&gt; PNS --&gt; PKernel --&gt; PHost
  end
  subgraph SbxPath[&quot;Docker Sandbox (sbx)&quot;]
    direction TB
    SApp[&quot;Agent process in YOLO mode&quot;]
    SGuest[&quot;Guest kernel (microVM)&quot;]
    SVMM[&quot;Virtual machine monitor&quot;]
    SHost[&quot;Host: isolated, project dir shared only&quot;]
    SApp --&gt; SGuest --&gt; SVMM --&gt; SHost
  end&lt;/pre&gt;
&lt;p&gt;Read the two stacks top to bottom. In the plain container, the agent process reaches the host across one shared kernel. In the sandbox, the agent process reaches the host only after getting through a guest kernel and a virtual machine monitor, and the host shares nothing but the one project directory you pointed it at.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;standalone-no-docker-desktop-seconds-to-spin-up&quot;&gt;Standalone, no Docker Desktop, seconds to spin up&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Docker Sandboxes run as a standalone CLI. You do not need Docker Desktop running to use &lt;code dir=&quot;auto&quot;&gt;sbx&lt;/code&gt;, which keeps the dependency surface small and makes it easy to script. A sandbox spins up in seconds, so the microVM is not a heavy thing you provision once and babysit. It is closer to the ergonomics of &lt;code dir=&quot;auto&quot;&gt;docker run&lt;/code&gt;: you ask for it, you get it quickly, you throw it away when you are done.&lt;/p&gt;
&lt;p&gt;That speed matters for a loop. A tool like &lt;a href=&quot;https://github.com/pageai-pro/ralph-loop&quot;&gt;Ralph Loop&lt;/a&gt; creates or reattaches a sandbox on every iteration, so a slow boundary would tax every pass. Fast startup is what makes “isolate every run” practical instead of aspirational.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;deterministic-names-and-a-network-gate&quot;&gt;Deterministic names and a network gate&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Two more things come for free with the sandbox model that you would otherwise build by hand.&lt;/p&gt;
&lt;p&gt;Sandboxes get deterministic names so the same project and agent pair reuses the same microVM. Ralph uses the form &lt;code dir=&quot;auto&quot;&gt;ralph-&amp;#x3C;agent&gt;-&amp;#x3C;current-dir&gt;-&amp;#x3C;hash8&gt;&lt;/code&gt;, for example &lt;code dir=&quot;auto&quot;&gt;ralph-claude-my-app-a1b2c3d4&lt;/code&gt;. You list them with &lt;code dir=&quot;auto&quot;&gt;sbx ls&lt;/code&gt;, shell into one with &lt;code dir=&quot;auto&quot;&gt;sbx exec -it &amp;#x3C;name&gt; bash&lt;/code&gt;, and reattach with &lt;code dir=&quot;auto&quot;&gt;sbx run &amp;#x3C;name&gt;&lt;/code&gt;. No tracking container IDs by hand.&lt;/p&gt;
&lt;p&gt;The network is a gate, not an open door. Docker Sandboxes default to deny-by-default egress, and you allowlist exactly what a task needs:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;policy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;allow&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;network&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-claude-my-app-a1b2c3d4&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;*.npmjs.org,github.com&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;You can apply a rule globally with &lt;code dir=&quot;auto&quot;&gt;-g&lt;/code&gt;, and the &lt;code dir=&quot;auto&quot;&gt;&quot;**&quot;&lt;/code&gt; pattern opens a single sandbox fully when you genuinely cannot enumerate the domains. The full treatment of building an allowlist that lets installs through while keeping exfiltration out lives in &lt;a href=&quot;https://ralphloop.sh/blog/ai-agent-sandbox-network-policies/&quot;&gt;network policies for AI agent sandboxes&lt;/a&gt;. The point versus a plain container: you get this gate without writing iptables rules.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;if-you-must-use-a-plain-container-harden-it&quot;&gt;If you must use a plain container, harden it&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Sometimes a microVM is not available in your environment and a container is what you have. You can close most of the obvious leaks with flags. This will not give you a separate kernel, so it is not equivalent to a microVM, but it is a real improvement over a naked &lt;code dir=&quot;auto&quot;&gt;docker run&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Run as a non-root user instead of root:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;docker&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--user&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1000:1000&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Drop every Linux capability and add back only what you actually need:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;docker&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--cap-drop=ALL&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Block privilege escalation so a setuid binary cannot regain capabilities:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;docker&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--security-opt=no-new-privileges&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Make the root filesystem read-only and give the agent a scratch space that is not your host:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;docker&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--read-only&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--tmpfs&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/tmp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Cut the network entirely when a task does not need it, or attach a tightly scoped network when it does:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;docker&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--network&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;none&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Cap resources so a runaway loop cannot starve the host:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;docker&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--memory&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;4g&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--cpus&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--pids-limit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;512&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Put together, a hardened run looks like this:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;docker&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-it&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--rm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--user&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1000:1000&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--cap-drop=ALL&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--security-opt=no-new-privileges&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--read-only&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--tmpfs&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/tmp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--memory&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;4g&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--cpus&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--pids-limit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;512&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--network&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;none&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;-v&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;$PWD&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;:/work&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-w&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/work&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;\&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;node:22&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bash&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Two things to keep in mind. Never mount the Docker socket into an agent container, because that hands the agent the daemon and therefore the host. And remember that all of this still rides on your shared kernel, so a kernel level escape defeats every flag above at once. Hardened containers raise the cost of an escape. A microVM changes what an escape even targets.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-ralph-defaults-to-sbx&quot;&gt;Why Ralph defaults to sbx&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Ralph runs every agent inside a Docker Sandbox by default, and the reasoning is the whole post in one line: the sandbox is the boundary, so the agent is free to move fast inside it. Because the boundary is external and real, Ralph runs agents in bypass-permissions mode (Claude Code’s &lt;code dir=&quot;auto&quot;&gt;--dangerously-skip-permissions&lt;/code&gt;, with equivalents for other CLIs) without the usual dread. The danger of YOLO mode comes from the target on the host, and the microVM removes the target. The deeper version of that argument is in &lt;a href=&quot;https://ralphloop.sh/blog/bypass-permissions-yolo-mode-safely/&quot;&gt;running agents in YOLO mode safely&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The loop wires the sandbox lifecycle so you never manage it by hand. Each iteration computes the deterministic name, checks that &lt;code dir=&quot;auto&quot;&gt;sbx&lt;/code&gt; exists, creates the sandbox on the first pass and attaches on later passes, runs the agent on exactly one task, and stops the sandbox through an exit trap when the run ends. The loop stops on an explicit completion promise (&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;COMPLETE&amp;#x3C;/promise&gt;&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;BLOCKED:reason&amp;#x3C;/promise&gt;&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;DECIDE:question&amp;#x3C;/promise&gt;&lt;/code&gt;), not on a vibe, and those map to exit codes &lt;code dir=&quot;auto&quot;&gt;0&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;2&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;3&lt;/code&gt; with &lt;code dir=&quot;auto&quot;&gt;1&lt;/code&gt; reserved for hitting the iteration cap.&lt;/p&gt;
&lt;p&gt;Starting a run is the same regardless of which agent you pick:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;codex&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--model&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gpt-5.5&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Supported agents are &lt;code dir=&quot;auto&quot;&gt;claude&lt;/code&gt; (the default), &lt;code dir=&quot;auto&quot;&gt;codex&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;copilot&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;cursor&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;gemini&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;opencode&lt;/code&gt;. For the field guide on which CLI behaves best inside a long sandboxed loop, the &lt;a href=&quot;https://ralphloop.sh/blog/agentic-coding-clis/&quot;&gt;agentic coding CLIs&lt;/a&gt; pillar is the cross-hub companion to this one. The takeaway stands on its own: a plain container is where you start, and a per-sandbox microVM is the boundary you actually want around an autonomous agent.&lt;/p&gt;
&lt;section aria-label=&quot;Frequently asked questions&quot;&gt; &lt;h2&gt;Frequently asked questions&lt;/h2&gt; &lt;div&gt; &lt;h3&gt;Is a Docker container enough to sandbox an AI coding agent?&lt;/h3&gt; &lt;p&gt;A plain container is a start, not a full boundary. It isolates the process and the filesystem you mount, which is better than running the agent on the host, but it shares your host kernel, often runs as root, and has open outbound network by default. For an untrusted agent running arbitrary commands, a microVM that gives each sandbox its own guest kernel is a stronger boundary.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;What is the difference between a Docker Sandbox and a plain Docker container?&lt;/h3&gt; &lt;p&gt;A plain container shares the host kernel and isolates processes with namespaces and cgroups. A Docker Sandbox, run with the sbx CLI, gives each sandbox its own lightweight virtual machine with a separate guest kernel and a virtual machine monitor between it and the host. The sandbox also defaults to deny-by-default network egress, which a plain container does not.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;Where does a hand-rolled container leak when running an agent?&lt;/h3&gt; &lt;p&gt;Four common places: the process runs as root by default, the container shares your host kernel so a kernel level escape reaches your machine, bind mounts write straight to your real files and can expose more than intended, and the default network gives the agent full outbound access. Mounting the Docker socket is the worst case because it hands the agent control of the host.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;How can I harden a plain container if I cannot use a microVM?&lt;/h3&gt; &lt;p&gt;Run as a non-root user, drop all capabilities with cap-drop ALL, set no-new-privileges, make the root filesystem read-only with a tmpfs scratch space, cap memory, CPU, and pids, and restrict the network. Never mount the Docker socket. These flags raise the cost of an escape but still rely on the shared kernel, so they are weaker than a separate guest kernel.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;Why does Ralph default to Docker Sandboxes instead of plain containers?&lt;/h3&gt; &lt;p&gt;Because the sandbox is the boundary that makes bypass-permissions mode safe. The microVM removes the target an agent could hit on the host, so the agent can run fast and autonomously while the worst case stays contained. Ralph also automates the lifecycle, computing a deterministic sandbox name and creating, attaching, and stopping the sandbox across loop iterations.&lt;/p&gt; &lt;/div&gt; &lt;/section&gt; 
&lt;aside aria-label=&quot;Get started with Ralph Loop&quot;&gt; &lt;h2&gt;Run your own Ralph loop&lt;/h2&gt; &lt;p&gt;Ralph is a hackable script you point at your project. Install it and let an agent work through your task list.&lt;/p&gt; &lt;pre&gt;&lt;code&gt;npx @pageai/ralph-loop&lt;/code&gt;&lt;/pre&gt; &lt;div&gt; &lt;a href=&quot;https://www.npmjs.com/package/@pageai/ralph-loop&quot;&gt;
Install from npm
&lt;/a&gt; &lt;a href=&quot;https://github.com/pageai-pro/ralph-loop&quot;&gt;Star on GitHub&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=3TL8Ez66I3o&quot;&gt;Watch the walkthrough&lt;/a&gt; &lt;/div&gt; &lt;/aside&gt;</content:encoded><category>sandboxing</category></item><item><title>Task Lookup Tables: Scaling Autonomous Agents to Hundreds of Tasks</title><link>https://ralphloop.sh/blog/task-lookup-table-for-agents/</link><guid isPermaLink="true">https://ralphloop.sh/blog/task-lookup-table-for-agents/</guid><description>A flat task list stops working past a few dozen entries. A lookup table that indexes per-task spec files lets an autonomous agent grind through hundreds of tasks without losing track.

</description><pubDate>Thu, 30 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A flat task list does not scale. Once you push past a few dozen entries, a single file that holds every task plus every step, criterion, and note becomes too big to load into an agent’s context window on every iteration. The fix is a task lookup table: a lean index of tasks that points to a separate detailed spec file per task. The agent reads the small index to decide what to do next, then loads exactly one spec file to do it. That separation is what lets an autonomous loop work through hundreds of tasks without choking on its own backlog.&lt;/p&gt;
&lt;p&gt;This post shows how Ralph models that pattern with &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;.agent/tasks/TASK-{ID}.json&lt;/code&gt;, why the split scales, how status and priority selection work each iteration, and how you grow the table over time. It assumes you already understand &lt;a href=&quot;https://ralphloop.sh/blog/spec-driven-development-with-ai/&quot;&gt;spec-driven development with AI&lt;/a&gt; and want the data structure that makes a long run survivable.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-a-task-lookup-table-is&quot;&gt;What a task lookup table is&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A lookup table is an index. Instead of one giant document that mixes the list of work with the detail of each item, you keep two layers.&lt;/p&gt;
&lt;p&gt;The first layer is a flat array where each entry is small: an id, a title, a category, a pointer to the detail file, and a status flag. That is the lookup table. It answers one question fast: what is left to do, and which thing is next.&lt;/p&gt;
&lt;p&gt;The second layer is one spec file per task. Each file is the full contract for a single unit of work: description, acceptance criteria, ordered steps, dependencies, complexity, and technical notes. The agent only opens this when it has already decided to work that task.&lt;/p&gt;
&lt;p&gt;The reason to split is the same reason a database keeps an index separate from the rows. You scan the cheap thing to find the right record, then you read the expensive thing once. An agent that has to load 200 fully detailed tasks just to pick the next one burns its context window before it writes a line of code.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-ralph-models-it-tasksjson-plus-per-task-specs&quot;&gt;How Ralph models it: tasks.json plus per-task specs&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Ralph keeps the index in &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt; and the detail in &lt;code dir=&quot;auto&quot;&gt;.agent/tasks/TASK-{ID}.json&lt;/code&gt;. The root file is deliberately thin. Each entry carries only what the loop needs to choose a task.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;TASK-1&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;title&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Verify project prerequisites and access&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;category&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;setup&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;specFilePath&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;.agent/tasks/TASK-1.json&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;passes&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;TASK-2&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;title&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;User table with authentication fields&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;category&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;data-model&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;specFilePath&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;.agent/tasks/TASK-2.json&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;passes&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;TASK-3&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;title&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;POST /api/auth/register creates new user account&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;category&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;api-endpoint&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;specFilePath&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;.agent/tasks/TASK-3.json&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;passes&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Five fields, nothing more. The &lt;code dir=&quot;auto&quot;&gt;specFilePath&lt;/code&gt; is the pointer that turns this flat list into a lookup table. The &lt;code dir=&quot;auto&quot;&gt;passes&lt;/code&gt; flag is the status. Everything heavy lives behind the pointer, in the per-task file.&lt;/p&gt;
&lt;p&gt;Here is what one of those spec files holds. This is &lt;code dir=&quot;auto&quot;&gt;TASK-3.json&lt;/code&gt;, the detail behind the one-line index entry above.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;TASK-3&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;title&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;POST /api/auth/register creates new user account&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;category&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;api-endpoint&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;description&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Validate input, hash the password, store the user, and return a success response.&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;acceptanceCriteria&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;POST with a valid email and password returns 201 with the user id and email&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Invalid email format returns 400 with the error text Please enter a valid email&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Password shorter than 8 characters returns 400&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Duplicate email returns 409, not a generic 500&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;The stored password starts with $2b$ and is never the plaintext value&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;steps&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;step&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;description&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Add the register route handler&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;details&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Validate with a zod schema, hash with bcrypt, insert into the users table.&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;pass&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;step&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;description&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Write Vitest cases for every acceptance criterion&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;details&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Cover valid registration, invalid email, short password, duplicate email, and the stored hash prefix.&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;pass&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;dependencies&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;TASK-1&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;TASK-2&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;estimatedComplexity&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;medium&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;technicalNotes&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Never log passwords, even in error branches&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Return 409 on duplicate email rather than a generic 500&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Notice the asymmetry. The index entry for &lt;code dir=&quot;auto&quot;&gt;TASK-3&lt;/code&gt; is five lines. The spec file is forty. Multiply that across a real project and the savings are obvious: a 200 task project has a &lt;code dir=&quot;auto&quot;&gt;tasks.json&lt;/code&gt; of roughly a thousand lines of thin entries, while the detail sits in 200 separate files the agent never loads all at once.&lt;/p&gt;
&lt;p&gt;The diagram below is the whole idea. The loop scans the lean table, picks one id, follows its &lt;code dir=&quot;auto&quot;&gt;specFilePath&lt;/code&gt;, and loads only that file into the working context.&lt;/p&gt;
&lt;pre dir=&quot;ltr&quot;&gt;flowchart LR
  Index[&quot;tasks.json: lean index, one line per task&quot;]
  Index --&gt; Scan[&quot;Scan for highest-priority task where passes is false&quot;]
  Scan --&gt; Pick[&quot;Selected: TASK-3&quot;]
  Pick --&gt; Path[&quot;Follow specFilePath&quot;]
  Path --&gt; Spec[&quot;tasks/TASK-3.json: full spec loaded into context&quot;]
  Spec --&gt; Work[&quot;Work the steps, run the gate&quot;]
  Work --&gt; Flip[&quot;Set passes true in tasks.json, commit&quot;]
  Index -. &quot;not loaded&quot; .-&gt; Rest[&quot;tasks/TASK-4.json ... TASK-200.json&quot;]&lt;/pre&gt;
&lt;p&gt;The dotted edge is the point. Every other spec file stays on disk, unread, until its turn comes. The agent never pays the token cost of a task it is not working.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-this-scales-to-hundreds-of-tasks&quot;&gt;Why this scales to hundreds of tasks&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The scaling property comes from one fact: the agent only loads the one task it needs. Every iteration of &lt;a href=&quot;https://ralphloop.sh/blog/spec-driven-development-with-ai/&quot;&gt;the Ralph loop&lt;/a&gt; starts the agent with a fresh context window. It does not carry the previous iteration in chat history. It rebuilds its understanding from files. So the cost of each iteration is whatever the agent reads off disk, not the size of the whole project.&lt;/p&gt;
&lt;p&gt;Walk through the math. Suppose the average per-task spec is 40 lines and the project has 200 tasks. If the agent had to load every detailed task to orient itself, that is 8000 lines on every single iteration, repeated 200 times. With a lookup table, each iteration loads the thin index (around 1000 lines of one-line entries) plus exactly one spec file (40 lines). The detail you load grows by one file, not by the entire backlog, no matter how large the table gets.&lt;/p&gt;
&lt;p&gt;That is why a flat, fully detailed task file hits a wall. It works fine at ten tasks. At a hundred it crowds out the actual code in the context window, and the agent starts skimming, missing acceptance criteria, and contradicting earlier decisions. Splitting the index from the detail keeps the working context flat as the project grows.&lt;/p&gt;
&lt;p&gt;The token economics reinforce this. Tokens in the context window cost money and attention on every pass. You do not want to spend that budget reloading 199 tasks the agent will not touch this iteration. A lean index plus one spec is the minimum the agent needs to choose work and do it correctly. This is the same instinct behind keeping a short &lt;code dir=&quot;auto&quot;&gt;SUMMARY.md&lt;/code&gt; for the PRD instead of resending the full document every iteration: load the index constantly, load the detail on demand.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;status-tracking-with-the-passes-flag&quot;&gt;Status tracking with the passes flag&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Status lives in two places, and the split mirrors the index-and-detail structure.&lt;/p&gt;
&lt;p&gt;At the index level, each entry in &lt;code dir=&quot;auto&quot;&gt;tasks.json&lt;/code&gt; has a single &lt;code dir=&quot;auto&quot;&gt;passes&lt;/code&gt; boolean. It starts &lt;code dir=&quot;auto&quot;&gt;false&lt;/code&gt;. The agent only flips it to &lt;code dir=&quot;auto&quot;&gt;true&lt;/code&gt; after the work is built and verified. The loop reads this flag to decide what is finished and what remains. Scanning for incomplete work is a pass over the lean index, which stays cheap even at hundreds of entries.&lt;/p&gt;
&lt;p&gt;At the detail level, each step inside a spec file has its own &lt;code dir=&quot;auto&quot;&gt;pass&lt;/code&gt; boolean. These track progress within a single task: step one done, step two not yet. A task is not complete until every step passes and the acceptance criteria hold. Only then does the top-level &lt;code dir=&quot;auto&quot;&gt;passes&lt;/code&gt; in the index flip.&lt;/p&gt;
&lt;p&gt;The rule that protects this system is strict: never flip &lt;code dir=&quot;auto&quot;&gt;passes&lt;/code&gt; to &lt;code dir=&quot;auto&quot;&gt;true&lt;/code&gt; until the work is verified. Tasks are generated with &lt;code dir=&quot;auto&quot;&gt;passes: false&lt;/code&gt; and stay false until the agent runs the verification stack the loop assumes, which is Playwright for end to end, Vitest for unit tests, TypeScript for types, ESLint for lint, and Prettier for format. The repo mantra is blunt: if you didn’t test it, it doesn’t work. A status flag that flips on a vibe instead of a passing gate turns the lookup table into a lie, and a fresh-context agent will trust that lie on the next iteration.&lt;/p&gt;
&lt;p&gt;Because status is just a flag in a file, the run is resumable and auditable. Any fresh agent on any machine can read &lt;code dir=&quot;auto&quot;&gt;tasks.json&lt;/code&gt;, see which entries are still &lt;code dir=&quot;auto&quot;&gt;false&lt;/code&gt;, and pick up exactly where the last one stopped. Watching those flags flip across a run, alongside the per-iteration history and screenshots, is the basis of &lt;a href=&quot;https://ralphloop.sh/blog/observability-for-ai-coding-agents/&quot;&gt;observability for autonomous coding agents&lt;/a&gt;. The table is both the work queue and the progress report.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;priority-selection-each-iteration&quot;&gt;Priority selection each iteration&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Picking the next task is a scan over the index, and the order is not arbitrary. Each iteration, the loop finds the highest-priority incomplete task in &lt;code dir=&quot;auto&quot;&gt;tasks.json&lt;/code&gt;, then loads that one spec file and works its steps.&lt;/p&gt;
&lt;p&gt;Two things shape the selection.&lt;/p&gt;
&lt;p&gt;First, dependencies. Each spec file carries a &lt;code dir=&quot;auto&quot;&gt;dependencies&lt;/code&gt; array of task ids that must finish before it can start. &lt;code dir=&quot;auto&quot;&gt;TASK-3&lt;/code&gt; (the register endpoint) lists &lt;code dir=&quot;auto&quot;&gt;[&quot;TASK-1&quot;, &quot;TASK-2&quot;]&lt;/code&gt;, so the loop will not select it until the prerequisite gate and the users table are both &lt;code dir=&quot;auto&quot;&gt;passes: true&lt;/code&gt;. The agent never builds on a foundation that is not there yet.&lt;/p&gt;
&lt;p&gt;Second, the prerequisite gate. &lt;code dir=&quot;auto&quot;&gt;TASK-1&lt;/code&gt; is always reserved for prerequisite verification: environment variable placeholders exist, database access works, required tools are authenticated, and any open gaps have an explicit proceed or block decision. Every downstream task that needs those prerequisites lists &lt;code dir=&quot;auto&quot;&gt;TASK-1&lt;/code&gt; as a dependency. You do not want an agent discovering halfway through a 200 task run that it never had database credentials.&lt;/p&gt;
&lt;pre dir=&quot;ltr&quot;&gt;flowchart TD
  Start[&quot;Fresh context&quot;] --&gt; Read[&quot;Read tasks.json index&quot;]
  Read --&gt; Filter[&quot;Filter to passes false&quot;]
  Filter --&gt; Deps{&quot;Dependencies satisfied?&quot;}
  Deps --&gt;|&quot;no, skip&quot;| Next[&quot;Try next candidate&quot;]
  Next --&gt; Deps
  Deps --&gt;|&quot;yes&quot;| Select[&quot;Select highest-priority task&quot;]
  Select --&gt; Load[&quot;Load its TASK-ID.json spec&quot;]
  Load --&gt; Build[&quot;Work the steps&quot;]
  Build --&gt; Gate[&quot;Tests, lint, types, screenshot&quot;]
  Gate --&gt;|&quot;fail&quot;| Build
  Gate --&gt;|&quot;pass&quot;| Update[&quot;Set passes true, commit&quot;]
  Update --&gt; Stop[&quot;Stop. Next iteration starts clean&quot;]&lt;/pre&gt;
&lt;p&gt;The loop completes exactly one task per invocation, commits, and stops. It never batches. That discipline is what keeps a long run reliable, and the reasoning behind it is covered in &lt;a href=&quot;https://ralphloop.sh/blog/one-task-per-iteration/&quot;&gt;one task per iteration&lt;/a&gt;. The lookup table is what makes one-task-per-iteration cheap to execute: selection is a quick scan of flags and dependencies, not a re-read of the entire project.&lt;/p&gt;
&lt;p&gt;You control how many of these iterations run. The default is 10. Run more with a flag.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@pageai/ralph-loop&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If the table has 200 tasks and you cap the loop at 50 iterations, the run stops at the cap with exit code 1 (MAX_ITERATIONS) and the remaining tasks stay &lt;code dir=&quot;auto&quot;&gt;false&lt;/code&gt; in the index, ready for the next run to resume. Nothing is lost. The lookup table is the durable record.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;adding-tasks-over-time-with-the-prd-creator-skill&quot;&gt;Adding tasks over time with the prd-creator skill&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A lookup table is not a frozen document. You grow it as the project grows, and you do not hand-edit a 200 entry JSON file to do that.&lt;/p&gt;
&lt;p&gt;Ralph ships a &lt;code dir=&quot;auto&quot;&gt;prd-creator&lt;/code&gt; skill that turns unstructured requirements into a PRD plus a task list. The first time you run it, it interviews you, writes &lt;code dir=&quot;auto&quot;&gt;.agent/prd/PRD.md&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;.agent/prd/SUMMARY.md&lt;/code&gt;, then generates &lt;code dir=&quot;auto&quot;&gt;tasks.json&lt;/code&gt; with one &lt;code dir=&quot;auto&quot;&gt;TASK-{ID}.json&lt;/code&gt; spec per task. &lt;code dir=&quot;auto&quot;&gt;TASK-1&lt;/code&gt; is always the prerequisite gate, and every task is initialized with &lt;code dir=&quot;auto&quot;&gt;passes: false&lt;/code&gt;. For a typical project that is dozens to hundreds of entries, not five, because the skill keeps tasks small: anything too complex to finish in a short sitting gets split.&lt;/p&gt;
&lt;p&gt;When you want to add a feature or fix a bug later, you run the skill again. It updates the PRD and appends new tasks to the index, each with its own spec file, each starting &lt;code dir=&quot;auto&quot;&gt;false&lt;/code&gt;. The completed entries keep their &lt;code dir=&quot;auto&quot;&gt;passes: true&lt;/code&gt; status, so the loop ignores them and works only the new &lt;code dir=&quot;auto&quot;&gt;false&lt;/code&gt; ones. The table grows without disturbing the finished work.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Use the prd-creator skill in plan mode. Add a password reset flow to the&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;existing PRD and append the new tasks to .agent/tasks.json with one spec&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;file each under .agent/tasks/.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Run it in plan mode, where the agent is read-only and asks questions instead of writing code. The skill decomposes the new feature into atomic tasks the same way it did the first batch, which is its own discipline covered in &lt;a href=&quot;https://ralphloop.sh/blog/break-prd-into-agent-tasks/&quot;&gt;breaking a PRD into atomic agent tasks&lt;/a&gt;. The result is a lookup table that accretes work over the life of the project while staying scannable, because the index entries stay thin no matter how many you add.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-to-go-next&quot;&gt;Where to go next&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you are building the spec that drives a long run, read across the spec-driven cluster:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ralphloop.sh/blog/spec-driven-development-with-ai/&quot;&gt;Spec-driven development with AI&lt;/a&gt; for the full Specify, Plan, Tasks, Implement workflow the lookup table sits inside.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ralphloop.sh/blog/break-prd-into-agent-tasks/&quot;&gt;Breaking a PRD into atomic agent tasks&lt;/a&gt; for decomposing work into the packets that fill the table.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ralphloop.sh/blog/one-task-per-iteration/&quot;&gt;One task per iteration&lt;/a&gt; for the rule that makes the lookup table cheap to execute.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For watching the table fill in across a run, with logs, history, and screenshots, read &lt;a href=&quot;https://ralphloop.sh/blog/observability-for-ai-coding-agents/&quot;&gt;observability for autonomous coding agents&lt;/a&gt;.&lt;/p&gt;
&lt;section aria-label=&quot;Frequently asked questions&quot;&gt; &lt;h2&gt;Frequently asked questions&lt;/h2&gt; &lt;div&gt; &lt;h3&gt;What is a task lookup table for an AI agent?&lt;/h3&gt; &lt;p&gt;It is a two-layer structure that separates the list of work from the detail of each item. The first layer is a lean index where each entry has an id, title, category, a pointer to a detail file, and a status flag. The second layer is one spec file per task with the full description, acceptance criteria, steps, and dependencies. The agent scans the cheap index to pick the next task, then loads only that one spec file to do the work.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;Why does a flat task list stop scaling for autonomous agents?&lt;/h3&gt; &lt;p&gt;A flat list that holds every task plus all of its detail gets too large to load into the context window on every iteration. At ten tasks it is fine. At a hundred it crowds out the actual code, so the agent skims, misses acceptance criteria, and contradicts earlier decisions. A lookup table keeps the working context flat because each iteration loads the thin index plus exactly one detailed spec, no matter how many tasks exist.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;How does Ralph store tasks on disk?&lt;/h3&gt; &lt;p&gt;The index is .agent/tasks.json, a flat array where each entry has id, title, category, specFilePath, and a passes flag. The detail for each task lives in .agent/tasks/TASK-{ID}.json with description, acceptance criteria, ordered steps, dependencies, estimated complexity, and technical notes. The agent reads these files fresh on every iteration, so the filesystem is the memory rather than the chat history.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;How does the agent choose which task to work next?&lt;/h3&gt; &lt;p&gt;Each iteration it scans tasks.json for the highest-priority entry where passes is false and whose dependencies are already satisfied. TASK-1 is always prerequisite verification, and downstream tasks list it as a dependency, so feature work never starts before access and environment are confirmed. The agent loads that one spec file, works the steps, runs the verification gate, then flips passes to true and commits.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;How do I add more tasks to the lookup table over time?&lt;/h3&gt; &lt;p&gt;Run the prd-creator skill again in plan mode. It updates the PRD and appends new tasks to tasks.json, each with its own spec file under .agent/tasks/ and each initialized with passes set to false. Completed entries keep their passes true status, so the loop ignores them and works only the new false ones. The table grows without disturbing finished work and stays scannable because the index entries stay thin.&lt;/p&gt; &lt;/div&gt; &lt;/section&gt; 
&lt;aside aria-label=&quot;Get started with Ralph Loop&quot;&gt; &lt;h2&gt;Run your own Ralph loop&lt;/h2&gt; &lt;p&gt;Ralph is a hackable script you point at your project. Install it and let an agent work through your task list.&lt;/p&gt; &lt;pre&gt;&lt;code&gt;npx @pageai/ralph-loop&lt;/code&gt;&lt;/pre&gt; &lt;div&gt; &lt;a href=&quot;https://www.npmjs.com/package/@pageai/ralph-loop&quot;&gt;
Install from npm
&lt;/a&gt; &lt;a href=&quot;https://github.com/pageai-pro/ralph-loop&quot;&gt;Star on GitHub&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=3TL8Ez66I3o&quot;&gt;Watch the walkthrough&lt;/a&gt; &lt;/div&gt; &lt;/aside&gt;</content:encoded><category>spec-driven</category></item><item><title>Why You Should Sandbox Every Autonomous Coding Agent</title><link>https://ralphloop.sh/blog/why-sandbox-ai-coding-agents/</link><guid isPermaLink="true">https://ralphloop.sh/blog/why-sandbox-ai-coding-agents/</guid><description>An autonomous coding agent runs with your full user permissions, which means it can read your SSH keys and push to your remotes. A sandbox is the only blast radius cheap enough to lose.

</description><pubDate>Sat, 25 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Sandbox every autonomous coding agent because the agent runs with your permissions. When you start an agent on your laptop, it does not run as some restricted service account. It runs as you, with your user, which means it can read your SSH keys, your cloud credentials, and your full git history, and it can execute any shell command those permissions allow. A sandbox moves all of that out of reach, so the worst case becomes a throwaway environment you reset instead of a credential leak you cannot undo. That is the entire argument for &lt;code dir=&quot;auto&quot;&gt;why sandbox ai agent&lt;/code&gt; work, and the rest of this post is the detail behind it.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;an-agent-runs-as-you-with-everything-you-can-touch&quot;&gt;An agent runs as you, with everything you can touch&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Think about what your own user account can do on a normal dev machine. You can read &lt;code dir=&quot;auto&quot;&gt;~/.ssh/id_ed25519&lt;/code&gt;, the private key that authenticates you to GitHub and every server you SSH into. You can read &lt;code dir=&quot;auto&quot;&gt;~/.aws/credentials&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;~/.config/gh/hosts.yml&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;.env&lt;/code&gt; files scattered across projects, and the browser session cookies sitting in your home directory. You can &lt;code dir=&quot;auto&quot;&gt;git push --force&lt;/code&gt;, delete directories, and run a &lt;code dir=&quot;auto&quot;&gt;curl | bash&lt;/code&gt; line you copied from a README.&lt;/p&gt;
&lt;p&gt;An autonomous agent inherits all of it. The model is not malicious. It is a probabilistic system that emits shell commands, and shell commands do not ask for intent. When the agent decides to “clean up some old files” or “reset the environment to a known state,” there is no separate permission layer protecting your home directory. The agent is you, and you have access to everything.&lt;/p&gt;
&lt;p&gt;This is fine when a human drives each command and reviews it. Autonomy removes the human from that loop on purpose. The whole point of a Ralph-style loop is that the agent works for hours without you watching, picking up the next task in &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt;, running commands, and committing. The thing that made interactive use safe (you reading each command before approving it) is exactly the thing autonomy deletes.&lt;/p&gt;
&lt;p&gt;So the question is not whether the agent will eventually run a command you would not have approved. Over a long enough run, it will. The question is what that command can reach when it does.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-blast-radius-problem-with-yolo-mode-on-your-laptop&quot;&gt;The blast radius problem with YOLO mode on your laptop&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;To run unattended, an agent has to stop asking permission. In Claude Code that switch is &lt;code dir=&quot;auto&quot;&gt;--dangerously-skip-permissions&lt;/code&gt; (also &lt;code dir=&quot;auto&quot;&gt;--permission-mode bypassPermissions&lt;/code&gt;, documented in the &lt;a href=&quot;https://code.claude.com/docs&quot;&gt;Claude Code docs&lt;/a&gt;). Other CLIs ship their own version of the same idea, usually called YOLO mode. The naming is honest. You are telling the agent to execute whatever it decides, with no confirmation, for the entire run.&lt;/p&gt;
&lt;p&gt;On your host that is a genuinely bad idea, and the danger scales with the length of the run. One iteration is a handful of commands. A fifty-iteration overnight run is hundreds of commands, each one a chance for a hallucinated path, a destructive cleanup, or a misread instruction. You will not be awake to catch the one that matters.&lt;/p&gt;
&lt;p&gt;The damage is not limited to the project either. Because the agent runs as you, a single bad command can reach far outside the directory you pointed it at:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It can read secrets from other projects and from your home directory, then paste them into a file, a log, or an outbound request.&lt;/li&gt;
&lt;li&gt;It can push to remotes you have credentials for, including repositories that have nothing to do with the current task.&lt;/li&gt;
&lt;li&gt;It can delete or rewrite files anywhere your user can write, with no undo.&lt;/li&gt;
&lt;li&gt;It can install and execute arbitrary code pulled from the network.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of these require the model to “go rogue.” They are the normal capabilities of your shell, exposed to a system running without a gate. Running bypass-permissions mode directly on a host is the failure mode, not a feature. For the safe version of that same flag, see &lt;a href=&quot;https://ralphloop.sh/blog/bypass-permissions-yolo-mode-safely/&quot;&gt;running agents in YOLO mode safely&lt;/a&gt;.&lt;/p&gt;
&lt;pre dir=&quot;ltr&quot;&gt;flowchart LR
  subgraph NoSandbox[&quot;Without a sandbox: blast radius is your whole machine&quot;]
    AgentA[&quot;Agent in YOLO mode (runs as you)&quot;]
    AgentA --&gt; Keys[&quot;SSH keys, cloud creds, cookies&quot;]
    AgentA --&gt; Repos[&quot;Every other git repo&quot;]
    AgentA --&gt; Net[&quot;Unrestricted network&quot;]
    AgentA --&gt; Proj1[&quot;Project directory&quot;]
  end
  subgraph WithSandbox[&quot;With a sandbox: blast radius is one disposable microVM&quot;]
    AgentB[&quot;Agent in YOLO mode (contained)&quot;]
    AgentB --&gt; Proj2[&quot;Project directory (only this)&quot;]
    AgentB --&gt; Gate[&quot;Network gate: deny-by-default&quot;]
    AgentB -. &quot;no path to&quot; .-&gt; HostKeys[&quot;Host keys and creds&quot;]
    AgentB -. &quot;no path to&quot; .-&gt; HostRepos[&quot;Other repos&quot;]
  end&lt;/pre&gt;
&lt;p&gt;The two pictures use the same agent in the same mode. The only thing that changes is what “anything it can do” resolves to. On the left it resolves to your machine. On the right it resolves to a microVM you can throw away.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-a-sandbox-makes-autonomy-acceptable&quot;&gt;Why a sandbox makes autonomy acceptable&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A sandbox does not make the agent trustworthy. It makes trust unnecessary. You stop trying to predict every command the model might emit and instead make the set of things any command can reach small and disposable. That inversion is what lets you walk away from a running agent and sleep.&lt;/p&gt;
&lt;p&gt;Concretely, a sandbox shrinks the worst case from “the agent leaked my SSH key and force-pushed to a client repo” to “the agent trashed a throwaway environment, so I reset it and re-ran the loop.” The first outcome is a security incident. The second is a Tuesday. Same model, same flags, completely different cost when something goes wrong.&lt;/p&gt;
&lt;p&gt;This is also why a sandbox is the precondition for real autonomy rather than a nice-to-have. Long unattended runs are the entire premise of &lt;a href=&quot;https://ralphloop.sh/blog/run-ai-coding-agent-overnight/&quot;&gt;running an AI coding agent overnight&lt;/a&gt;, and you cannot responsibly leave an agent running for hours unless the place it runs is one you can afford to lose. The sandbox is what makes the overnight run a reasonable decision instead of a gamble with your credentials.&lt;/p&gt;
&lt;p&gt;There is a structural point underneath this. A permission prompt is not a security boundary, it is a question. The only real boundary is one enforced from outside the agent, by something the agent cannot reach through or talk its way around. A sandbox is that external boundary. The full version of this argument lives in the pillar guide, &lt;a href=&quot;https://ralphloop.sh/blog/run-ai-agents-in-docker-sandboxes-safely/&quot;&gt;how to run AI coding agents in Docker Sandboxes safely&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-ralph-uses-docker-sandboxes-by-default&quot;&gt;How Ralph uses Docker Sandboxes by default&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Ralph does not bolt on isolation as an option you remember to enable. It runs every agent inside a Docker Sandbox by default, using the &lt;code dir=&quot;auto&quot;&gt;sbx&lt;/code&gt; CLI. Each agent gets a lightweight virtual machine with its own kernel, which is a stronger boundary than a namespaced process sharing your host kernel. The &lt;a href=&quot;https://docs.docker.com/ai/sandboxes/&quot;&gt;Docker Sandboxes documentation&lt;/a&gt; is the primary source for how the microVM works underneath.&lt;/p&gt;
&lt;p&gt;You do not manage that lifecycle by hand. The script computes a deterministic sandbox name, checks that &lt;code dir=&quot;auto&quot;&gt;sbx&lt;/code&gt; is installed, decides whether to create or attach, runs the agent in bypass-permissions mode inside the microVM, and stops the sandbox when the run ends. Because the sandbox is the boundary, the agent is free to skip permission prompts and move fast inside it.&lt;/p&gt;
&lt;p&gt;Installing and running looks like this:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@pageai/ralph-loop&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The first command drops Ralph into your project. The second runs the loop for fifty iterations, each one starting the agent with a fresh context inside the sandbox. Pick a different agent and pass agent-specific flags after a &lt;code dir=&quot;auto&quot;&gt;--&lt;/code&gt; separator:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;codex&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--model&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gpt-5.5&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gemini&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--model&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pro&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Supported agents are &lt;code dir=&quot;auto&quot;&gt;claude&lt;/code&gt; (the default), &lt;code dir=&quot;auto&quot;&gt;codex&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;copilot&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;cursor&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;gemini&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;opencode&lt;/code&gt;. The default iteration count is 10, and &lt;code dir=&quot;auto&quot;&gt;--once&lt;/code&gt; runs exactly one iteration.&lt;/p&gt;
&lt;p&gt;The sandbox name is deterministic so the same project and agent pair always reuses the same microVM:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ralph-&amp;#x3C;agent&gt;-&amp;#x3C;current-dir&gt;-&amp;#x3C;hash8&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;agent&gt;&lt;/code&gt; is the agent slug, &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;current-dir&gt;&lt;/code&gt; is the sanitized basename of the project directory, and &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;hash8&gt;&lt;/code&gt; is the first eight hex characters of a &lt;code dir=&quot;auto&quot;&gt;sha256&lt;/code&gt; of the absolute project path. The path hash keeps two same-named directories on different paths from colliding. You never have to memorize the name, because Ralph prints it on startup and on demand:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--print-name&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--print-name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;codex&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;When the run ends, by normal exit, by a double Ctrl+C, or by any path that fires the exit trap, Ralph stops only the sandbox it started:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stop&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-claude-my-app-a1b2c3d4&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Stopping is not deleting, so you can reattach later to inspect what the agent did, then remove it with &lt;code dir=&quot;auto&quot;&gt;sbx rm&lt;/code&gt; when you are finished.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-good-isolation-actually-looks-like&quot;&gt;What good isolation actually looks like&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Not every “sandbox” is a real boundary. A bare container that bind-mounts your whole home directory and has open network is theater. Three properties separate isolation that holds from isolation that only looks like it does.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;mount-only-the-project-nothing-else&quot;&gt;Mount only the project, nothing else&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The agent should see the project directory and nothing above it. Ralph shares your project at the same absolute path it has on your host, so tooling, config, and lockfiles resolve identically, while the rest of your home directory stays outside. No SSH keys, no &lt;code dir=&quot;auto&quot;&gt;~/.aws&lt;/code&gt;, no unrelated repositories, no shell history full of tokens. The blast radius becomes the project you pointed at, plus whatever network you explicitly allow.&lt;/p&gt;
&lt;p&gt;The corollary matters: because the project is shared, the agent can absolutely wreck your working tree. The protection there is git, not the sandbox. Work on a branch and commit often. The sandbox protects everything outside the project, and version control protects the code inside it.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;ephemeral-by-design&quot;&gt;Ephemeral by design&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The environment has to be cheap to destroy and recreate. If resetting a contaminated sandbox is a multi-hour chore, you will avoid resetting it, and a sandbox you never reset slowly turns back into a pet you are afraid to lose. Ralph leans on the deterministic name here: tear a sandbox down with &lt;code dir=&quot;auto&quot;&gt;sbx rm&lt;/code&gt;, and the next iteration simply probes, finds nothing, and creates a clean one. That re-probe each iteration is what makes a long run resilient to you poking at the sandbox by hand.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;network-limited-not-wide-open&quot;&gt;Network limited, not wide open&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Filesystem isolation is half the boundary. The other half is the network, because an agent with unrestricted outbound access can fetch arbitrary code and, in the worst case, send data out. Docker Sandboxes default to blocking outbound HTTP and HTTPS, then you allowlist exactly what the task needs:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;policy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;allow&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;network&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ralph-claude-my-app-a1b2c3d4&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;*.npmjs.org,github.com&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The practical symptom of deny-by-default is that &lt;code dir=&quot;auto&quot;&gt;npm install&lt;/code&gt; fails or an API call is refused until you grant the domain. That is the gate doing its job. You open specific hosts, not everything. There is a full-open escape hatch with the &lt;code dir=&quot;auto&quot;&gt;&quot;**&quot;&lt;/code&gt; rule, and it is the right tool only when you genuinely cannot enumerate the domains and you accept the tradeoff for that one sandbox. Building a tight allowlist that lets installs through while keeping exfiltration out is its own topic, covered in &lt;a href=&quot;https://ralphloop.sh/blog/ai-agent-sandbox-network-policies/&quot;&gt;network policies for AI agent sandboxes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A plain container can satisfy some of this and miss the rest, which is why the kind of sandbox matters. For where a hand-rolled container leaks and where a microVM holds, read &lt;a href=&quot;https://ralphloop.sh/blog/docker-sandboxes-vs-containers-for-agents/&quot;&gt;Docker Sandboxes vs plain containers for AI agents&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-quick-test-before-you-run-an-agent-unsandboxed&quot;&gt;A quick test before you run an agent unsandboxed&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you are tempted to skip the sandbox for a “quick” autonomous run, ask one question: if this agent ran &lt;code dir=&quot;auto&quot;&gt;rm -rf ~&lt;/code&gt; or piped the contents of &lt;code dir=&quot;auto&quot;&gt;~/.ssh&lt;/code&gt; to a pastebin, what would it cost you? If the honest answer is “a rebuild and some annoyance,” you are already in a disposable environment and you are fine. If the answer involves rotating credentials, notifying anyone, or the phrase “client repository,” you need the boundary before you start the loop.&lt;/p&gt;
&lt;p&gt;The reason to make this the default rather than a judgment call is that the judgment is the part autonomy removes. You are not going to evaluate the risk of each of the next four hundred commands. You evaluate the environment once, up front, and then let the agent be fearless inside it.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-the-boundary-ends&quot;&gt;Where the boundary ends&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A sandbox is a strong boundary and not a magic one. Two limits are worth stating so you do not over-trust the setup.&lt;/p&gt;
&lt;p&gt;First, the shared project directory is genuinely shared. The agent can corrupt your working tree, and only git will save you. Branch and commit.&lt;/p&gt;
&lt;p&gt;Second, whatever you allowlist is genuinely reachable. Grant a domain that accepts uploads and an agent could in principle send data there. Keep network grants minimal, specific, and reviewed, the same way you would treat firewall rules, and avoid the &lt;code dir=&quot;auto&quot;&gt;&quot;**&quot;&lt;/code&gt; rule except on purpose.&lt;/p&gt;
&lt;p&gt;Inside those limits the model is simple and it holds. Enforce the boundary from the outside, give the agent a disposable place to be fearless, and let the loop run. The sandbox is the blast radius, so fast and autonomous becomes the same thing as contained.&lt;/p&gt;
&lt;section aria-label=&quot;Frequently asked questions&quot;&gt; &lt;h2&gt;Frequently asked questions&lt;/h2&gt; &lt;div&gt; &lt;h3&gt;Why do I need to sandbox an AI coding agent at all?&lt;/h3&gt; &lt;p&gt;An autonomous agent runs with your user permissions, so it can read your SSH keys, cloud credentials, and git history, push to your remotes, and delete files anywhere you can write. In autonomous mode there is no human reviewing each command, so over a long run the agent will eventually execute something you would not have approved. A sandbox limits what any command can reach to a disposable environment.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;What is the worst case if I run an agent without a sandbox?&lt;/h3&gt; &lt;p&gt;The worst case is a security incident rather than an inconvenience. A single bad command can leak credentials from your home directory, force-push to an unrelated repository, or delete files with no undo, because the agent has your full permissions. With a sandbox the same bad command only touches a throwaway microVM that you reset and re-run.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;Is bypass-permissions or YOLO mode ever safe?&lt;/h3&gt; &lt;p&gt;It is unsafe on your host and safe inside a sandbox. On the host, --dangerously-skip-permissions gives the agent full shell access with no confirmation. Inside a Docker Sandbox microVM the same flag only grants access to the shared project directory and an allowlisted network, so the danger has no target on your machine.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;Does Ralph sandbox agents automatically?&lt;/h3&gt; &lt;p&gt;Yes. Ralph runs every agent inside a Docker Sandbox by default using the sbx CLI. It computes a deterministic sandbox name, creates or attaches the microVM each iteration, runs the agent in bypass-permissions mode inside it, and stops the sandbox when the run ends.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;What makes isolation good rather than just theater?&lt;/h3&gt; &lt;p&gt;Good isolation mounts only the project directory and nothing above it, stays ephemeral so it is cheap to destroy and recreate, and limits the network to an allowlist instead of leaving it wide open. A microVM with its own kernel is a stronger boundary than a container that shares your host kernel, especially for code you do not trust to behave.&lt;/p&gt; &lt;/div&gt; &lt;/section&gt; 
&lt;aside aria-label=&quot;Get started with Ralph Loop&quot;&gt; &lt;h2&gt;Run your own Ralph loop&lt;/h2&gt; &lt;p&gt;Ralph is a hackable script you point at your project. Install it and let an agent work through your task list.&lt;/p&gt; &lt;pre&gt;&lt;code&gt;npx @pageai/ralph-loop&lt;/code&gt;&lt;/pre&gt; &lt;div&gt; &lt;a href=&quot;https://www.npmjs.com/package/@pageai/ralph-loop&quot;&gt;
Install from npm
&lt;/a&gt; &lt;a href=&quot;https://github.com/pageai-pro/ralph-loop&quot;&gt;Star on GitHub&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=3TL8Ez66I3o&quot;&gt;Watch the walkthrough&lt;/a&gt; &lt;/div&gt; &lt;/aside&gt;</content:encoded><category>sandboxing</category></item><item><title>One Task Per Iteration: The Rule That Makes Autonomous Agents Reliable</title><link>https://ralphloop.sh/blog/one-task-per-iteration/</link><guid isPermaLink="true">https://ralphloop.sh/blog/one-task-per-iteration/</guid><description>The most reliable rule for autonomous coding is one task per invocation, commit, then stop. Here is why batching tasks wrecks an agent loop and how Ralph enforces the rule.

</description><pubDate>Tue, 21 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The most reliable rule for autonomous coding is also the most boring: one task per invocation, commit, then stop. Each iteration the agent picks the single highest-priority incomplete task, works it to done, verifies it, commits, and exits. It never batches two tasks into one run. Break that rule and a loop that should grind cleanly through fifty tasks turns into a branch full of half-finished, hard-to-review work.&lt;/p&gt;
&lt;p&gt;This rule is the operational core of a Ralph loop, and it sits one level below the &lt;a href=&quot;https://ralphloop.sh/blog/spec-driven-development-with-ai/&quot;&gt;spec-driven development workflow&lt;/a&gt; that produces the task list in the first place. The spec decides what the agent builds. This rule decides how it builds it without losing the plot.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-rule-one-task-one-commit-one-stop&quot;&gt;The rule: one task, one commit, one stop&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Stated as plainly as it goes in the repo’s own &lt;code dir=&quot;auto&quot;&gt;AGENTS.md&lt;/code&gt;: one task per invocation. When working from &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt;, the agent completes exactly one task, commits, and stops. It never batches multiple tasks.&lt;/p&gt;
&lt;p&gt;That is the whole rule. Three verbs: complete, commit, stop. The reason it is worth a thousand words is that every instinct (yours and the model’s) pushes the other way. You have fifty tasks and an agent that can clearly do more than one. Letting it power through feels efficient. It is not. It is the fastest way to turn a clean autonomous run into a debugging session.&lt;/p&gt;
&lt;p&gt;An iteration that follows the rule produces one self-contained unit of progress: a feature built, its tests passing, its types checked, and a single commit that maps to exactly one task in the list. The next iteration starts from a known-good state. That property, every iteration ends in a verified checkpoint, is what makes long runs survivable.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-batching-tasks-wrecks-an-agent-loop&quot;&gt;Why batching tasks wrecks an agent loop&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Batching means telling the agent (or letting it decide) to knock out three or five tasks before it commits. It looks like a throughput win. In practice it degrades the run in three compounding ways.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;context-bloat-and-context-rot&quot;&gt;Context bloat and context rot&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;A coding agent has a finite context window and finite attention inside it. The longer a single session runs, the more it fills with intermediate state: files it opened, tests it ran, dead ends it explored, decisions it half-remembers. This is context rot. The agent starts contradicting earlier choices, re-editing files it already finished, and forgetting which of the five tasks it was actually on.&lt;/p&gt;
&lt;p&gt;One task per iteration keeps the working context small and on-topic. The agent reads the summary and the one spec it needs, builds the one thing, and exits before the window gets crowded. Batching does the opposite: it lets the context grow across multiple unrelated tasks, which is precisely the condition under which agents start producing confident nonsense. Context rot is one of the headline &lt;a href=&quot;https://ralphloop.sh/blog/ralph-loop-failure-modes/&quot;&gt;Ralph loop failure modes&lt;/a&gt;, and batching is the most direct route to it.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;half-finished-work-piles-up&quot;&gt;Half-finished work piles up&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;When an agent juggles several tasks at once, a failure on task four can leave tasks one through three in a partial state. It edited shared files, started a refactor it did not finish, and now nothing in the batch is cleanly done. You cannot ship any of it, and you cannot easily tell where the good work ends and the broken work begins.&lt;/p&gt;
&lt;p&gt;One task per iteration makes failure local. If the agent cannot finish the current task, it emits a &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;BLOCKED:reason&amp;#x3C;/promise&gt;&lt;/code&gt; and stops, and everything committed before that point is intact and verified. You lost one iteration, not five tasks of tangled progress.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;commits-get-impossible-to-verify&quot;&gt;Commits get impossible to verify&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;A commit that contains one task is reviewable. The diff maps to a single entry in &lt;code dir=&quot;auto&quot;&gt;tasks.json&lt;/code&gt;, the acceptance criteria for that task tell you what to check, and the tests in the diff prove it. A commit (or worse, a single giant commit) that contains five tasks is archaeology. You cannot bisect it, you cannot revert one piece of it, and you cannot read why any single change exists six months later.&lt;/p&gt;
&lt;p&gt;Clean, atomic commits are not a nicety here. They are the audit trail that makes you trust a diff you did not write. Batching trades that trail for a vague sense of speed you do not actually get, because the review and rework cost lands on you in the morning.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;fresh-context-per-iteration-is-why-one-task-works&quot;&gt;Fresh context per iteration is why one task works&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;One task per iteration only makes sense because of the other half of the design: every iteration starts the agent with a fresh context window. The loop does not carry chat history forward. It reboots the agent’s understanding from files on disk each time.&lt;/p&gt;
&lt;p&gt;This is the central idea of the &lt;a href=&quot;https://ralphloop.sh/blog/spec-driven-development-with-ai/&quot;&gt;Ralph technique&lt;/a&gt;, popularized by Geoffrey Huntley in his &lt;a href=&quot;https://ghuntley.com/ralph/&quot;&gt;original Ralph writeup&lt;/a&gt;. The agent’s memory is not the conversation. It is the filesystem and the git history: &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt;, the per-task specs, the logs, and the commits. Because state lives on disk, a fresh agent can reconstruct exactly where the project stands and pick up cleanly.&lt;/p&gt;
&lt;p&gt;Fresh context and one-task scope are two sides of the same coin. Fresh context per iteration only helps if the unit of work fits inside one clean window, and one task is the unit sized to do that. Pair them and each iteration is crisp: clean slate in, one verified task out. Try to batch tasks on top of fresh context and you reintroduce the bloat that fresh context was meant to prevent.&lt;/p&gt;
&lt;pre dir=&quot;ltr&quot;&gt;flowchart TD
  Fresh[&quot;Fresh context window&quot;] --&gt; Read[&quot;Read SUMMARY.md and tasks.json&quot;]
  Read --&gt; Pick[&quot;Pick highest-priority incomplete task&quot;]
  Pick --&gt; Spec[&quot;Open one TASK-{ID}.json&quot;]
  Spec --&gt; Work[&quot;Work the steps for that one task&quot;]
  Work --&gt; Gate[&quot;Verify: tests, lint, types, screenshot&quot;]
  Gate --&gt;|&quot;fail&quot;| Work
  Gate --&gt;|&quot;pass&quot;| Commit[&quot;Set passes true and commit one task&quot;]
  Commit --&gt; Stop[&quot;Stop. Iteration ends at a checkpoint&quot;]
  Stop --&gt; Next[&quot;Next iteration starts with fresh context&quot;]
  Next --&gt; Fresh&lt;/pre&gt;
&lt;p&gt;Read that diagram as a single lap. The only way out of a lap is a verified commit or an explicit stop signal. There is no path where the agent commits two tasks in one pass, because the lap ends the moment one task is done.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;clean-commits-are-your-checkpoints-and-rollback-points&quot;&gt;Clean commits are your checkpoints and rollback points&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Treat each per-task commit as a save point in a game. When the agent finishes a task and commits, it records a state you can return to. If iteration twelve goes sideways, you do not lose the eleven good iterations before it. You reset to the last clean commit and keep the verified work.&lt;/p&gt;
&lt;p&gt;This is why the commit is not optional and not deferred. The loop’s standard iteration ends by updating the task status, taking a screenshot for UI work, and committing in Conventional Commit format. One task, one commit, one checkpoint. The git log becomes a ledger of progress where each line is a unit you can read, verify, and if necessary revert in isolation.&lt;/p&gt;
&lt;p&gt;Batching destroys this. A commit that bundles several tasks is a single, coarse save point. Revert it and you lose good work alongside the bad. Bisect it and every “bad” commit contains multiple changes, so you cannot pin the regression to one task. The granularity of your commits is the granularity of your recovery, and one task per commit is the finest useful grain.&lt;/p&gt;
&lt;p&gt;There is a second benefit that matters for long runs. Because every checkpoint is verified before it is committed, the branch is always in a shippable-ish state between iterations. You can stop the loop at any point, after iteration three or after iteration thirty, and what you have is a set of completed, tested tasks rather than a half-built mess. That is what lets you run an agent overnight and trust the morning diff.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-ralph-enforces-one-task-per-iteration&quot;&gt;How Ralph enforces one task per iteration&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The rule is not a suggestion the agent is free to ignore. It is wired into how the loop and the prompt work together.&lt;/p&gt;
&lt;p&gt;The loop itself is a Bash script (&lt;code dir=&quot;auto&quot;&gt;ralph.sh&lt;/code&gt;) that runs the agent once per iteration. Each iteration follows the same shape:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Find the highest-priority incomplete task in &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Work the ordered steps in &lt;code dir=&quot;auto&quot;&gt;.agent/tasks/TASK-{ID}.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run tests, linting, and type checking.&lt;/li&gt;
&lt;li&gt;Complete the task, take a screenshot, update task status, and commit.&lt;/li&gt;
&lt;li&gt;Repeat until all tasks pass or the iteration cap is reached.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Step one is where scope gets enforced: the agent selects the highest-priority incomplete task, singular. The prompt sent each iteration tells the agent to complete that one task, commit, and stop rather than continuing to the next. Because the next iteration starts a brand new agent with fresh context, there is no natural way to “keep going” across tasks. The process boundary between iterations is the enforcement mechanism.&lt;/p&gt;
&lt;p&gt;You control how many iterations run. The default is ten. Use the flag to set your own cap:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Run up to 50 iterations (50 tasks, one per iteration)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Run exactly one iteration to watch a single task closely&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--once&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;./ralph.sh --once&lt;/code&gt; is the cleanest demonstration of the rule. It runs a single iteration: one task, one commit, one stop. Use it the first time you point Ralph at a new task list, watch it complete exactly one task, review the commit, and only then turn it loose with a larger cap.&lt;/p&gt;
&lt;p&gt;The loop does not stop on a feeling. It stops on an explicit signal. After the run, the agent emits a promise tag and the script maps it to an exit code:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;promise&gt;COMPLETE&amp;#x3C;/promise&gt;         all tasks finished      exit 0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;promise&gt;BLOCKED:reason&amp;#x3C;/promise&gt;   needs human help        exit 2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;promise&gt;DECIDE:question&amp;#x3C;/promise&gt;  needs a decision        exit 3&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If the loop hits its iteration cap before the list is finished, it exits with code 1 (MAX_ITERATIONS). Either way, every task that did complete completed on its own iteration, with its own verified commit. The completion signal is per-run; the one-task discipline is per-iteration.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-about-tasks-that-are-too-small-or-too-big&quot;&gt;What about tasks that are too small or too big?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The obvious objection: if the agent does only one task per iteration, the task had better be the right size. That is true, and it is why scoping happens before the loop ever runs, during breakdown.&lt;/p&gt;
&lt;p&gt;If your tasks are too big, the agent runs out of room inside one iteration and you get half-finished work, the exact failure the rule was meant to prevent. The fix is not to relax the rule. It is to cut the task smaller. A good task is something an agent can finish in one short sitting, with its own acceptance criteria and its own verification. Getting tasks to that size is the subject of &lt;a href=&quot;https://ralphloop.sh/blog/break-prd-into-agent-tasks/&quot;&gt;breaking a PRD into atomic agent tasks&lt;/a&gt;, and it is the prerequisite that makes one task per iteration practical.&lt;/p&gt;
&lt;p&gt;If your tasks are too small, you simply burn an iteration on something trivial, which is cheap and harmless. The asymmetry matters: tasks that are slightly too small cost you a little time, tasks that are too big cost you a tangled branch. When in doubt, split.&lt;/p&gt;
&lt;p&gt;The other piece is ordering. One task per iteration only flows smoothly if the highest-priority incomplete task is actually buildable right now, with its dependencies already satisfied. That is what the task lookup table manages. Each task declares its dependencies, and the loop respects them so it never tries to build the dashboard before the data exists. Scaling that ordering to hundreds of tasks is covered in &lt;a href=&quot;https://ralphloop.sh/blog/task-lookup-table-for-agents/&quot;&gt;task lookup tables for agents&lt;/a&gt;. Get the breakdown and the ordering right, and one task per iteration is not a constraint you fight. It is the rhythm the whole run settles into.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-to-go-next&quot;&gt;Where to go next&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;One task per iteration is the discipline that turns a spec into a reliable run. To build the rest of the workflow around it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ralphloop.sh/blog/spec-driven-development-with-ai/&quot;&gt;Spec-driven development with AI&lt;/a&gt; for the full Specify, Plan, Tasks, Implement loop this rule lives inside.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ralphloop.sh/blog/break-prd-into-agent-tasks/&quot;&gt;Breaking a PRD into atomic agent tasks&lt;/a&gt; for sizing tasks so one fits cleanly in one iteration.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ralphloop.sh/blog/task-lookup-table-for-agents/&quot;&gt;Task lookup tables for agents&lt;/a&gt; for ordering and scaling to hundreds of tasks.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ralphloop.sh/blog/ralph-loop-failure-modes/&quot;&gt;Ralph loop failure modes&lt;/a&gt; for what goes wrong when this rule and its guardrails are not in place.&lt;/li&gt;
&lt;/ul&gt;
&lt;section aria-label=&quot;Frequently asked questions&quot;&gt; &lt;h2&gt;Frequently asked questions&lt;/h2&gt; &lt;div&gt; &lt;h3&gt;What does one task per iteration mean?&lt;/h3&gt; &lt;p&gt;It means the agent completes exactly one task per run of the loop, commits the result, and stops. The next iteration starts a fresh agent that picks the next highest-priority incomplete task. The agent never batches several tasks into a single invocation.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;Why should an agent not do multiple tasks in one run?&lt;/h3&gt; &lt;p&gt;Batching causes three problems. The context window fills with unrelated state and the agent starts contradicting itself, which is context rot. A failure mid-batch leaves several tasks half-finished and unshippable. And the resulting commit bundles many changes, so it is hard to review, revert, or bisect. One task per iteration keeps context small, failures local, and commits atomic.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;How does one task per iteration relate to fresh context?&lt;/h3&gt; &lt;p&gt;They are two halves of the same design. Each iteration starts the agent with a fresh context window and reconstructs state from files on disk, not from chat history. That only works if the unit of work fits inside one clean window, and one task is the unit sized to do that. Together they keep every iteration crisp: clean slate in, one verified task out.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;How does Ralph enforce one task per iteration?&lt;/h3&gt; &lt;p&gt;The loop runs the agent once per iteration. Each iteration the agent finds the single highest-priority incomplete task in .agent/tasks.json, works only that task spec, runs tests, lint, and type checks, updates the status, commits, and stops. Because the next iteration starts a brand new agent with fresh context, there is no way to carry work across tasks. The process boundary between iterations is the enforcement.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;What if a task is too big to finish in one iteration?&lt;/h3&gt; &lt;p&gt;Do not relax the rule, cut the task smaller. A good task is something an agent can finish in one short sitting with its own acceptance criteria. If the agent runs out of room inside one iteration, the breakdown was too coarse. Split the task during the breakdown phase so one task fits cleanly in one iteration.&lt;/p&gt; &lt;/div&gt; &lt;/section&gt; 
&lt;aside aria-label=&quot;Get started with Ralph Loop&quot;&gt; &lt;h2&gt;Run your own Ralph loop&lt;/h2&gt; &lt;p&gt;Ralph is a hackable script you point at your project. Install it and let an agent work through your task list.&lt;/p&gt; &lt;pre&gt;&lt;code&gt;npx @pageai/ralph-loop&lt;/code&gt;&lt;/pre&gt; &lt;div&gt; &lt;a href=&quot;https://www.npmjs.com/package/@pageai/ralph-loop&quot;&gt;
Install from npm
&lt;/a&gt; &lt;a href=&quot;https://github.com/pageai-pro/ralph-loop&quot;&gt;Star on GitHub&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=3TL8Ez66I3o&quot;&gt;Watch the walkthrough&lt;/a&gt; &lt;/div&gt; &lt;/aside&gt;</content:encoded><category>spec-driven</category></item><item><title>Breaking a PRD Into Atomic Agent Tasks</title><link>https://ralphloop.sh/blog/break-prd-into-agent-tasks/</link><guid isPermaLink="true">https://ralphloop.sh/blog/break-prd-into-agent-tasks/</guid><description>A PRD is too big for an agent to build in one shot. Here is how to decompose it into atomic, independently verifiable task packets the loop finishes one at a time.

</description><pubDate>Fri, 17 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;To break a PRD into tasks an agent can build, you decompose it into atomic task packets: small, self-contained units of work, each with one objective, the files to inspect, ordered steps, and acceptance criteria a machine can check. The agent picks one packet, builds it, verifies it, commits, and stops. The PRD is the contract for the whole project. A task packet is the contract for one iteration.&lt;/p&gt;
&lt;p&gt;This post is the decomposition itself: what goes inside a single packet, how Ralph stores packets in &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;.agent/tasks/TASK-{ID}.json&lt;/code&gt;, how to size each one so a single iteration can finish and commit, and how dependencies and priority decide the order the loop works them. It assumes you already have a buildable PRD. If you do not, start with &lt;a href=&quot;https://ralphloop.sh/blog/how-to-write-prd-for-ai-agent/&quot;&gt;how to write a PRD an AI agent can actually build from&lt;/a&gt;, then come back here to chop it up.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-a-prd-is-the-wrong-unit-of-work-for-an-agent&quot;&gt;Why a PRD is the wrong unit of work for an agent&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A PRD describes a finished product. An agent does not build finished products. It edits files in a single context window, and that window is finite. Hand it the whole PRD and tell it to build, and one of two things happens. It tries to do everything at once and produces a sprawling diff you cannot review, or it loses the plot halfway through and starts contradicting decisions it made an hour earlier. That second failure is context rot, and it gets worse the longer a single session runs.&lt;/p&gt;
&lt;p&gt;The fix is to never ask the agent to hold the whole project in its head. You decompose the PRD into the smallest pieces that still make sense on their own, and you feed the agent exactly one piece per iteration with a fresh context window each time. The &lt;a href=&quot;https://ralphloop.sh/blog/what-is-the-ralph-technique/&quot;&gt;Ralph technique&lt;/a&gt; is built around this: each loop starts the agent clean, it reads the task list, it picks one packet, and the filesystem carries the memory between passes instead of a long chat history.&lt;/p&gt;
&lt;p&gt;So the real question is not “how do I get the agent to build my PRD.” It is “what is the smallest packet of work I can define that the agent can finish and verify in one sitting.” Get that unit right and the loop becomes reliable. Get it wrong and no amount of prompting saves you.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-goes-inside-an-atomic-task-packet&quot;&gt;What goes inside an atomic task packet&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A task packet is atomic when it has a single objective and everything the agent needs to hit it. Four parts make a packet complete.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The objective.&lt;/strong&gt; One sentence describing the one thing this packet delivers. Not “build authentication.” That is a feature, not a task. “POST /api/auth/register creates a new user account” is a task. If you cannot state the objective in a single sentence without the word “and,” the packet is too big and you split it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The files to inspect.&lt;/strong&gt; Name the files the agent should read before it touches anything, and the files it is expected to change. This is the cheapest way to stop an agent from inventing a parallel module next to the one you already have. In Ralph these live in the step &lt;code dir=&quot;auto&quot;&gt;details&lt;/code&gt; and in &lt;code dir=&quot;auto&quot;&gt;technicalNotes&lt;/code&gt;: “extend the existing &lt;code dir=&quot;auto&quot;&gt;auth&lt;/code&gt; module in &lt;code dir=&quot;auto&quot;&gt;src/lib/auth.ts&lt;/code&gt;, do not add a second one.” A packet that points the agent at the right files reuses code instead of duplicating it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The ordered steps.&lt;/strong&gt; The packet breaks the objective into a short sequence the agent works in order. Each step is concrete enough to act on, and the last step is almost always “write the tests that prove the acceptance criteria.” Tests are steps inside the packet, not a separate task scheduled for later.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Testable acceptance criteria.&lt;/strong&gt; Each criterion is a condition the agent can confirm by running a command and reading the output. “Login works” is a vibe. “POST with a wrong password returns 401 and the body &lt;code dir=&quot;auto&quot;&gt;{ error: &apos;Invalid credentials&apos; }&lt;/code&gt;” is a criterion. The test is simple: can a machine return yes or no without your opinion? If not, rewrite it. The discipline of writing verifiable criteria is the same one that makes the PRD buildable, covered in &lt;a href=&quot;https://ralphloop.sh/blog/how-to-write-prd-for-ai-agent/&quot;&gt;how to write a PRD for an AI agent&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The packet is the unit of verification, not just the unit of work. When the agent finishes, it does not ask you whether it is done. It checks the criteria, and the criteria answer for it.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-ralph-stores-this-a-lookup-table-plus-per-task-specs&quot;&gt;How Ralph stores this: a lookup table plus per-task specs&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Ralph splits the task list into two layers, and the split is what lets a run scale to hundreds of packets without drowning the agent in detail.&lt;/p&gt;
&lt;p&gt;The root &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt; is a lookup table, not the detail. It is a flat list where each entry is a stub that points at a spec file. The agent scans this list every iteration to find the next thing to do, so it stays lean on purpose.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;TASK-1&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;title&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Verify project prerequisites and access&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;category&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;setup&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;specFilePath&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;.agent/tasks/TASK-1.json&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;passes&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;TASK-2&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;title&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Add avatars storage bucket and config&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;category&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;infrastructure&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;specFilePath&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;.agent/tasks/TASK-2.json&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;passes&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;TASK-3&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;title&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;POST /api/avatar uploads and stores a user avatar&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;category&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;api-endpoint&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;specFilePath&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;.agent/tasks/TASK-3.json&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;passes&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Each stub carries a &lt;code dir=&quot;auto&quot;&gt;passes&lt;/code&gt; flag. It starts &lt;code dir=&quot;auto&quot;&gt;false&lt;/code&gt; and only flips to &lt;code dir=&quot;auto&quot;&gt;true&lt;/code&gt; after the agent verifies the work for that packet. The loop reads this flag to decide what is left. Keeping the table this thin matters: a run with two hundred packets still scans fast, because the table holds titles and pointers, not the full contracts. That scaling pattern is its own topic in &lt;a href=&quot;https://ralphloop.sh/blog/task-lookup-table-for-agents/&quot;&gt;task lookup tables for agents&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The detail lives in the per-task spec at &lt;code dir=&quot;auto&quot;&gt;.agent/tasks/TASK-{ID}.json&lt;/code&gt;. This is the full packet: the objective as a description, the files to inspect inside the steps, the ordered steps, the testable acceptance criteria, the dependencies, an estimated complexity, and any technical notes.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;TASK-3&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;title&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;POST /api/avatar uploads and stores a user avatar&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;category&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;api-endpoint&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;description&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Accept an image upload from an authenticated user, validate it, store it in the avatars bucket, and save the URL on the user row.&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;acceptanceCriteria&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;POST with a valid PNG or JPEG under 2 MB returns 200 with the stored avatar URL&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;A file over 2 MB returns 413 with the error text File too large&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;A non-image content type returns 415&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;An unauthenticated request returns 401&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;The avatar URL is persisted on the user row and survives a re-fetch&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;steps&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;step&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;description&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Add the upload route handler&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;details&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Read src/lib/auth.ts for the session helper and src/lib/storage.ts for the bucket client. Add POST /api/avatar, validate size and content type, upload, then update users.avatarUrl.&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;pass&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;step&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;description&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Write tests for every acceptance criterion&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;details&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Add Vitest cases for valid upload, oversized file, wrong content type, unauthenticated request, and persistence after re-fetch.&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;pass&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;dependencies&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;TASK-1&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;TASK-2&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;estimatedComplexity&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;medium&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;technicalNotes&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Reuse the storage client in src/lib/storage.ts, do not add a second SDK&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Strip EXIF data before storing the image&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Notice the four parts of a packet map onto the JSON. &lt;code dir=&quot;auto&quot;&gt;description&lt;/code&gt; is the objective. The step &lt;code dir=&quot;auto&quot;&gt;details&lt;/code&gt; name the files to inspect and change. &lt;code dir=&quot;auto&quot;&gt;steps&lt;/code&gt; is the ordered sequence. &lt;code dir=&quot;auto&quot;&gt;acceptanceCriteria&lt;/code&gt; is the checkable definition of done. &lt;code dir=&quot;auto&quot;&gt;TASK-1&lt;/code&gt; is always reserved for prerequisite verification, so the agent confirms credentials, tools, and access exist before any feature work starts.&lt;/p&gt;
&lt;p&gt;Here is how a PRD turns into packets and lands in the lookup table.&lt;/p&gt;
&lt;pre dir=&quot;ltr&quot;&gt;flowchart TD
  PRD[&quot;prd/PRD.md: goals, constraints, criteria&quot;] --&gt; Decompose[&quot;Decompose into atomic packets&quot;]
  Decompose --&gt; P1[&quot;tasks/TASK-1.json: prerequisite gate&quot;]
  Decompose --&gt; P2[&quot;tasks/TASK-2.json: storage config&quot;]
  Decompose --&gt; P3[&quot;tasks/TASK-3.json: upload endpoint&quot;]
  Decompose --&gt; Pn[&quot;tasks/TASK-N.json: ...&quot;]
  P1 --&gt; Index[&quot;tasks.json lookup table (stubs + passes)&quot;]
  P2 --&gt; Index
  P3 --&gt; Index
  Pn --&gt; Index
  Index --&gt; Loop[&quot;ralph.sh: pick one packet per iteration&quot;]
  Loop --&gt; Verify[&quot;Build, test, screenshot, commit, set passes true&quot;]&lt;/pre&gt;
&lt;p&gt;The PRD is the source. Decomposition produces one spec file per packet. The lookup table indexes them. The loop reads the table, opens one spec, and works it. You do not write all of this by hand: the &lt;code dir=&quot;auto&quot;&gt;prd-creator&lt;/code&gt; skill generates the table and the spec files from your PRD, then you edit the packets that need sharpening.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;sizing-a-packet-so-one-iteration-can-finish-and-commit&quot;&gt;Sizing a packet so one iteration can finish and commit&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The hardest part of decomposition is sizing. Too big and the agent runs out of context before it finishes, leaves the packet half built, and the next iteration starts cold on a mess. Too small and you spend more iterations on overhead than on work. The target is the largest packet that still finishes, verifies, and commits inside a single iteration.&lt;/p&gt;
&lt;p&gt;A few concrete rules keep packets in the right range.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One objective, no “and.”&lt;/strong&gt; If the title needs the word “and” to be honest, it is two packets. “Add the upload endpoint and the avatar UI” is two: the endpoint is an api-endpoint packet, the UI is a ui-ux packet that depends on it. Split on the “and.”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One area of the codebase.&lt;/strong&gt; A packet that touches the data model, the API, and the frontend in one pass is too wide. Each layer is its own packet. The migration is one. The endpoint that reads the new column is another. The component that renders it is a third. Narrow packets keep the diff readable and the failure isolated.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Verifiable in the steps it contains.&lt;/strong&gt; If proving the criteria would take more steps than building the feature, the packet is doing too much. A well sized packet has a handful of steps, and the test step covers the criteria without ballooning into a second project.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A clean commit at the end.&lt;/strong&gt; The end state of every packet is a commit that builds, passes its tests, and changes nothing it did not need to. If you cannot imagine that commit as a single coherent change, the packet is wrong.&lt;/p&gt;
&lt;p&gt;The economics back this up. The loop runs one packet per iteration, and you cap iterations with &lt;code dir=&quot;auto&quot;&gt;./ralph.sh -n 50&lt;/code&gt; (the default is 10). A run of small packets makes steady, auditable progress: one commit per packet, one test suite per criterion, one screenshot per UI change. A run of giant packets thrashes, because the agent keeps almost finishing and never quite committing. When you want to watch a single packet land before turning the loop loose, run exactly one iteration:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@pageai/ralph-loop&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--once&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The rule that one invocation handles exactly one packet, commits, and stops is the reliability backbone of the whole approach. It has enough nuance to deserve its own treatment in &lt;a href=&quot;https://ralphloop.sh/blog/one-task-per-iteration/&quot;&gt;one task per iteration&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;dependencies-and-priority-ordering&quot;&gt;Dependencies and priority ordering&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Atomic packets are not independent of each other. The upload endpoint cannot exist before the storage bucket. The avatar UI cannot render a URL the endpoint does not yet return. Decomposition has to capture this order, or the agent builds on a foundation that is not there and the iteration fails.&lt;/p&gt;
&lt;p&gt;Ralph encodes order in two places. The &lt;code dir=&quot;auto&quot;&gt;dependencies&lt;/code&gt; array on each spec names the packets that must pass first. In the example above, &lt;code dir=&quot;auto&quot;&gt;TASK-3&lt;/code&gt; depends on &lt;code dir=&quot;auto&quot;&gt;[&quot;TASK-1&quot;, &quot;TASK-2&quot;]&lt;/code&gt;, so the loop will not start the upload endpoint until the prerequisite gate and the storage config both report &lt;code dir=&quot;auto&quot;&gt;passes: true&lt;/code&gt;. The agent respects this when it selects the next packet, which means you can write the whole task list up front without worrying that the agent jumps ahead.&lt;/p&gt;
&lt;p&gt;Priority decides what to do among packets that are all unblocked. Each iteration the loop finds the highest-priority incomplete task whose dependencies are satisfied, opens its spec, and works it. The selection logic each pass is small.&lt;/p&gt;
&lt;pre dir=&quot;ltr&quot;&gt;flowchart TD
  Start[&quot;Fresh context&quot;] --&gt; Scan[&quot;Scan tasks.json for incomplete packets&quot;]
  Scan --&gt; Ready{&quot;Dependencies all passes true?&quot;}
  Ready --&gt;|&quot;no&quot;| Skip[&quot;Skip, try next packet&quot;]
  Ready --&gt;|&quot;yes&quot;| Pick[&quot;Pick highest priority among ready packets&quot;]
  Skip --&gt; Scan
  Pick --&gt; Work[&quot;Open TASK-{ID}.json, work the steps&quot;]
  Work --&gt; Gate[&quot;Tests, lint, types, screenshot&quot;]
  Gate --&gt;|&quot;fail&quot;| Work
  Gate --&gt;|&quot;pass&quot;| Commit[&quot;Set passes true, commit, stop&quot;]&lt;/pre&gt;
&lt;p&gt;Two practices keep ordering dependable. First, front-load the foundation. The prerequisite gate is &lt;code dir=&quot;auto&quot;&gt;TASK-1&lt;/code&gt; for a reason: nothing should run before the agent confirms it has the access and tools to run at all. Data-model packets come early, because most feature packets depend on them. Second, keep dependency chains shallow. A packet that depends on a packet that depends on a packet is a sign the work is really one larger unit you split too aggressively, or that an intermediate packet is doing too little. Wide and shallow beats narrow and deep, because shallow graphs give the loop more ready packets to choose from at any moment, which keeps it moving even if one branch is blocked.&lt;/p&gt;
&lt;p&gt;When you need to reorder mid-run, you do not kill the loop. You edit &lt;code dir=&quot;auto&quot;&gt;.agent/STEERING.md&lt;/code&gt;, and the agent handles that critical work on its next iteration before returning to the task list. That is how you inject “fix the failing migration before anything else” without losing momentum.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-short-worked-example&quot;&gt;A short worked example&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Take “let users upload a profile avatar” from a PRD and watch it become packets.&lt;/p&gt;
&lt;p&gt;That sentence is a feature, not a task. Decomposition asks: what are the smallest verifiable units, and in what order. &lt;code dir=&quot;auto&quot;&gt;TASK-1&lt;/code&gt; is the prerequisite gate, always. &lt;code dir=&quot;auto&quot;&gt;TASK-2&lt;/code&gt; configures the storage bucket and is a dependency for everything that stores a file. &lt;code dir=&quot;auto&quot;&gt;TASK-3&lt;/code&gt; is the upload endpoint, depending on the bucket. &lt;code dir=&quot;auto&quot;&gt;TASK-4&lt;/code&gt; is the avatar component in the UI, depending on the endpoint because it renders the URL the endpoint returns. &lt;code dir=&quot;auto&quot;&gt;TASK-5&lt;/code&gt; handles deletion, depending on the upload existing.&lt;/p&gt;
&lt;p&gt;Each packet gets criteria a machine can check, not opinions. For the endpoint: a valid image under the size limit returns 200 with a URL, an oversized file returns 413, a wrong content type returns 415, an unauthenticated request returns 401. For the UI: the component shows a placeholder when no avatar is set, shows the image after a successful upload, and a Playwright run plus a screenshot confirms it. The feature that read as one line in the PRD is now five committed, tested steps, each small enough to finish in a single iteration, ordered so the loop never builds ahead of its foundation.&lt;/p&gt;
&lt;p&gt;The framing of phases that produces these packets (specify, plan, decompose into tasks, implement and verify) comes from &lt;a href=&quot;https://github.com/github/spec-kit&quot;&gt;GitHub Spec Kit&lt;/a&gt;, and the loop that runs the packets autonomously was popularized by Geoffrey Huntley in his &lt;a href=&quot;https://ghuntley.com/ralph/&quot;&gt;original Ralph writeup&lt;/a&gt;. Decomposition is the phase that decides whether the loop succeeds, which is why it is worth slowing down for.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-to-go-next&quot;&gt;Where to go next&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you are building this workflow, read across the spec-driven cluster:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ralphloop.sh/blog/spec-driven-development-with-ai/&quot;&gt;Spec-driven development with AI&lt;/a&gt; for the full Specify, Plan, Tasks, Implement workflow these packets sit inside.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ralphloop.sh/blog/how-to-write-prd-for-ai-agent/&quot;&gt;How to write a PRD an AI agent can actually build from&lt;/a&gt; for the goals, constraints, and acceptance criteria you decompose here.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ralphloop.sh/blog/one-task-per-iteration/&quot;&gt;One task per iteration&lt;/a&gt; for the rule that makes one-packet-at-a-time reliable.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ralphloop.sh/blog/task-lookup-table-for-agents/&quot;&gt;Task lookup tables for agents&lt;/a&gt; for scaling the table to hundreds of packets.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the mechanics of the loop that reads these packets on every pass and the fresh-context design behind it, start with &lt;a href=&quot;https://ralphloop.sh/blog/what-is-the-ralph-technique/&quot;&gt;what is the Ralph technique&lt;/a&gt;.&lt;/p&gt;
&lt;section aria-label=&quot;Frequently asked questions&quot;&gt; &lt;h2&gt;Frequently asked questions&lt;/h2&gt; &lt;div&gt; &lt;h3&gt;How do I break a PRD into tasks an AI agent can build?&lt;/h3&gt; &lt;p&gt;Decompose the PRD into atomic task packets, where each packet has one objective, the files to inspect, ordered steps, and acceptance criteria a machine can check by running a command and reading the output. Split on any objective that needs the word and, keep each packet inside one area of the codebase, and make sure the agent can finish and commit it in a single iteration.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;What makes a task packet atomic?&lt;/h3&gt; &lt;p&gt;A packet is atomic when it has a single objective and everything the agent needs to hit it without holding the rest of the project in its head. That means one deliverable, the files to read and change, a short ordered sequence of steps with tests as the final step, and verifiable acceptance criteria. If you cannot state the objective in one sentence without the word and, the packet is too big.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;How does Ralph store a decomposed PRD?&lt;/h3&gt; &lt;p&gt;Ralph uses two layers. The root .agent/tasks.json is a lean lookup table of stubs, each with an id, title, category, a pointer to a spec file, and a passes flag. The detail lives in per-task specs at .agent/tasks/TASK-{ID}.json, which hold the description, acceptance criteria, ordered steps, dependencies, estimated complexity, and technical notes. The agent scans the table to pick a packet and opens the spec to work it.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;How big should a single agent task be?&lt;/h3&gt; &lt;p&gt;The right size is the largest packet that still finishes, verifies, and commits inside one iteration. Too big and the agent runs out of context and leaves the work half done. Too small and overhead dominates. Aim for one objective, one area of the codebase, a handful of steps including the tests, and a single clean commit at the end.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;How do dependencies and priority decide task order?&lt;/h3&gt; &lt;p&gt;Each spec has a dependencies array naming the packets that must pass first, so the loop will not start a packet until its dependencies report passes true. Among packets whose dependencies are satisfied, the loop works the highest priority one. Front-load the prerequisite gate and data-model packets, keep dependency chains shallow, and edit STEERING.md to reorder mid-run without stopping the loop.&lt;/p&gt; &lt;/div&gt; &lt;/section&gt; 
&lt;aside aria-label=&quot;Get started with Ralph Loop&quot;&gt; &lt;h2&gt;Run your own Ralph loop&lt;/h2&gt; &lt;p&gt;Ralph is a hackable script you point at your project. Install it and let an agent work through your task list.&lt;/p&gt; &lt;pre&gt;&lt;code&gt;npx @pageai/ralph-loop&lt;/code&gt;&lt;/pre&gt; &lt;div&gt; &lt;a href=&quot;https://www.npmjs.com/package/@pageai/ralph-loop&quot;&gt;
Install from npm
&lt;/a&gt; &lt;a href=&quot;https://github.com/pageai-pro/ralph-loop&quot;&gt;Star on GitHub&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=3TL8Ez66I3o&quot;&gt;Watch the walkthrough&lt;/a&gt; &lt;/div&gt; &lt;/aside&gt;</content:encoded><category>spec-driven</category></item><item><title>How to Steer a Running AI Agent Without Stopping the Loop</title><link>https://ralphloop.sh/blog/steering-a-running-ai-agent/</link><guid isPermaLink="true">https://ralphloop.sh/blog/steering-a-running-ai-agent/</guid><description>You do not have to kill a Ralph loop to redirect it. Edit .agent/STEERING.md mid-run and the agent reads it at the top of the next iteration, handles the critical work first, then resumes the task list.

</description><pubDate>Mon, 13 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;To steer a running AI agent, edit &lt;code dir=&quot;auto&quot;&gt;.agent/STEERING.md&lt;/code&gt; while the loop is going. The agent reads that file at the top of every iteration, before it picks a task. Anything you write there gets handled first, in order, then the agent removes the finished items and goes back to the task list. You change direction without killing the process and without losing the momentum of a warm run.&lt;/p&gt;
&lt;p&gt;This is the redirect control inside the larger system for &lt;a href=&quot;https://ralphloop.sh/blog/run-ai-coding-agent-overnight/&quot;&gt;running an AI coding agent overnight&lt;/a&gt;. A loop that runs for hours will sometimes pick a task you no longer want, chase a dead end, or need a hotfix you only just noticed. Steering ai agent behavior through a file is how you correct course mid-flight instead of stopping, editing, and starting over.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-steering-an-ai-agent-actually-means&quot;&gt;What steering an AI agent actually means&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Steering is not a chat message and not a new prompt. It is a single Markdown file, &lt;code dir=&quot;auto&quot;&gt;.agent/STEERING.md&lt;/code&gt;, that lives next to the rest of the agent state. The Ralph prompt has one job for it. In the “Before Starting” section of &lt;code dir=&quot;auto&quot;&gt;.agent/PROMPT.md&lt;/code&gt;, the agent is told to check &lt;code dir=&quot;auto&quot;&gt;.agent/STEERING.md&lt;/code&gt; for critical work, complete the items in sequence, remove them when done, and only proceed to implement tasks if no critical work is pending.&lt;/p&gt;
&lt;p&gt;That ordering is the whole mechanism. Because each iteration starts the agent with a &lt;a href=&quot;https://ralphloop.sh/blog/what-is-the-ralph-technique/&quot;&gt;fresh context window&lt;/a&gt;, the agent rereads the prompt and the steering file every single loop. There is no stale chat history to fight and no need to interrupt the agent mid-thought. You write to a file, and the next clean iteration discovers it.&lt;/p&gt;
&lt;p&gt;A steering file looks like a short work order. The default one in the repo handles sandbox setup before any feature work runs:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Critical Steering Work&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;## Install and verify dependencies&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Run &lt;/span&gt;&lt;span&gt;`npm install --ignore-scripts`&lt;/span&gt;&lt;span&gt;, then fix native arm64 binaries.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;## Main Tasks&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Install Playwright system deps, start the dev server, take a screenshot.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;---&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;After you finish this work, exit with message &lt;/span&gt;&lt;span&gt;`Steering complete`&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;You can replace that content with whatever you need the agent to do next. The file is plain text, so you edit it with any tool from any machine that has the working tree, including a shell inside the sandbox.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;when-to-steer-a-running-agent&quot;&gt;When to steer a running agent&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Steer when the loop is healthy enough to keep running but pointed at the wrong thing, or when something urgent jumps the queue. There are three common triggers.&lt;/p&gt;
&lt;p&gt;The agent is stuck. You are watching the run and the same failure keeps coming back across iterations. Maybe a test depends on a binary that did not install, or a dev server never came up. The agent is not blocked enough to emit a promise tag, but it is spinning. A steering note that tells it exactly how to fix the environment clears the jam. Spotting this early is the payoff of &lt;a href=&quot;https://ralphloop.sh/blog/observability-for-ai-coding-agents/&quot;&gt;observability for autonomous coding agents&lt;/a&gt;: you see the climbing iteration time and the repeated debugging step before the loop burns ten more iterations.&lt;/p&gt;
&lt;p&gt;The agent went the wrong direction. The task list was fine when you wrote it, but the agent picked a task whose approach you now disagree with, or requirements changed since you started the run. You do not want to throw away the work it already shipped. You want it to switch tracks on the next iteration.&lt;/p&gt;
&lt;p&gt;You have an urgent fix. A bug landed in production, a dependency needs pinning, or a teammate needs a small change merged into the same branch. The loop is the fastest path to get it done, and you would rather inject the work than wait for the run to finish.&lt;/p&gt;
&lt;p&gt;Steering is the wrong tool for two cases. If the agent is fundamentally confused about the goal, fix &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt; or the PRD instead, because steering is for one-off interventions, not for rewriting the plan. And if the agent emitted &lt;code dir=&quot;auto&quot;&gt;BLOCKED&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;DECIDE&lt;/code&gt; and the loop already exited, you do not steer a stopped process. You answer it and rerun, which I cover below.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-the-loop-reads-steeringmd-every-iteration&quot;&gt;How the loop reads STEERING.md every iteration&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The reason steering works without a restart is that the loop already reopens the file on every pass. Each iteration is a clean run of the agent against the prompt, and the prompt routes through the steering check before it ever touches the task list.&lt;/p&gt;
&lt;pre dir=&quot;ltr&quot;&gt;flowchart TD
    Start[&quot;Iteration starts (fresh context)&quot;] --&gt; Read[&quot;Read .agent/PROMPT.md&quot;]
    Read --&gt; Check{&quot;STEERING.md has critical work?&quot;}
    Check --&gt;|&quot;yes&quot;| Steer[&quot;Do steering items in sequence&quot;]
    Steer --&gt; Remove[&quot;Remove finished items from STEERING.md&quot;]
    Remove --&gt; Tasks[&quot;Pick highest-priority task in tasks.json&quot;]
    Check --&gt;|&quot;no&quot;| Tasks
    Tasks --&gt; Verify[&quot;Run tests, lint, typecheck&quot;]
    Verify --&gt; Commit[&quot;Commit, emit promise tag&quot;]
    Commit --&gt; Next[&quot;Next iteration&quot;]
    You[&quot;You edit STEERING.md mid-run&quot;] -.-&gt;|&quot;injected here&quot;| Check&lt;/pre&gt;
&lt;p&gt;The dotted edge is the part you control. Your edit lands in the file at any moment, and the next iteration picks it up at the diamond. You never have to time it perfectly. If iteration 12 is in flight when you save the file, iteration 13 reads your note. Because the agent removes completed steering items, the file empties itself out and the loop returns to normal task work without you touching it again.&lt;/p&gt;
&lt;p&gt;This is why a fresh context per iteration is a feature, not a limitation. A single long session would have buried your note under thousands of tokens of prior reasoning. A loop that reloads its instructions every pass treats the file as the source of truth, so the latest version of &lt;code dir=&quot;auto&quot;&gt;.agent/STEERING.md&lt;/code&gt; always wins.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;steering-versus-killing-and-restarting-the-loop&quot;&gt;Steering versus killing and restarting the loop&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The blunt alternative is to stop the run, edit something, and start again. Steering beats that on three counts.&lt;/p&gt;
&lt;p&gt;You keep warm state. A sandbox that already installed dependencies, built the project, and warmed its caches is expensive to recreate. Ralph runs each agent in a deterministic Docker Sandbox named &lt;code dir=&quot;auto&quot;&gt;ralph-&amp;#x3C;agent&gt;-&amp;#x3C;dir&gt;-&amp;#x3C;hash8&gt;&lt;/code&gt;, and that sandbox persists between iterations. Killing the loop and recreating the box means paying the setup cost again. Steering reuses the running sandbox.&lt;/p&gt;
&lt;p&gt;You keep the run history intact. Every iteration writes a clean transcript to &lt;code dir=&quot;auto&quot;&gt;.agent/history/&lt;/code&gt;, appends to &lt;code dir=&quot;auto&quot;&gt;.agent/logs/LOG.md&lt;/code&gt;, and commits per task. A restart starts a new session id and fragments that trail. Steering threads your intervention into the same continuous record, so the morning review reads as one story.&lt;/p&gt;
&lt;p&gt;You avoid the gap. When you kill a loop manually, you have to remember to restart it. People walk away and forget. A steering note keeps the loop alive and self-correcting, which is the entire point of an unattended overnight run.&lt;/p&gt;
&lt;p&gt;There is one case where stopping is correct: when you want to change how the loop runs, not what it does next. Flags like the iteration count and the agent are set at launch, so switching from &lt;code dir=&quot;auto&quot;&gt;./ralph.sh -n 50&lt;/code&gt; to a different agent with &lt;code dir=&quot;auto&quot;&gt;./ralph.sh --agent codex&lt;/code&gt; does require a restart. For everything that is “do this work next,” steer instead.&lt;/p&gt;
&lt;p&gt;If you only need to validate a single steering note before trusting it overnight, run one pass with &lt;code dir=&quot;auto&quot;&gt;./ralph.sh --once&lt;/code&gt;, confirm the agent did what the file said, then start the full loop.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-to-write-a-steering-note-that-works&quot;&gt;How to write a steering note that works&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A good steering note reads like a task spec: specific, ordered, and verifiable. A bad one reads like a vibe. The agent reads your note with a fresh context, so it has no memory of the hallway conversation in your head. Spell it out.&lt;/p&gt;
&lt;p&gt;Write the exact commands and file paths. “Fix the build” is vague. The agent has to guess what broke and what done looks like. Compare:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Critical Steering Work&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;## Pin the failing dependency&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;1.&lt;/span&gt;&lt;span&gt; In &lt;/span&gt;&lt;span&gt;`package.json`&lt;/span&gt;&lt;span&gt;, set &lt;/span&gt;&lt;span&gt;`vite`&lt;/span&gt;&lt;span&gt; to &lt;/span&gt;&lt;span&gt;`5.4.10`&lt;/span&gt;&lt;span&gt; exactly.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;2.&lt;/span&gt;&lt;span&gt; Run &lt;/span&gt;&lt;span&gt;`npm install --ignore-scripts`&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;3.&lt;/span&gt;&lt;span&gt; Run &lt;/span&gt;&lt;span&gt;`npm run build`&lt;/span&gt;&lt;span&gt; and confirm it exits 0.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;4.&lt;/span&gt;&lt;span&gt; Commit with message &lt;/span&gt;&lt;span&gt;`fix: pin vite to 5.4.10`&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;---&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;After you finish this work, exit with message &lt;/span&gt;&lt;span&gt;`Steering complete`&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Every line is checkable. The agent knows the version, the commands, the success condition (build exits 0), and the commit message. That is the same standard the loop expects from a real task, and the same reason &lt;a href=&quot;https://ralphloop.sh/blog/verification-loops-for-ai-agents/&quot;&gt;verification loops&lt;/a&gt; matter: a steering note with a verifiable end state lets the agent prove it is done instead of guessing.&lt;/p&gt;
&lt;p&gt;Give the agent an exit condition. The default file ends with “After you finish this work, exit with message &lt;code dir=&quot;auto&quot;&gt;Steering complete&lt;/code&gt;.” That tells the agent the boundary of the steering work so it does not bleed into task work uninvited. Keep that pattern. Without a clear end, the agent may either stop too early or wander.&lt;/p&gt;
&lt;p&gt;Keep it small. Steering is for one or a few critical items, not a second task list. If your note grows into a dozen steps across unrelated areas, that is a sign the work belongs in &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt; as real tasks with their own specs. Steering is the fast lane, not the highway.&lt;/p&gt;
&lt;p&gt;Order matters. The prompt says complete items in sequence. List prerequisites first. If the agent must install a binary before a test can pass, the install goes above the test.&lt;/p&gt;
&lt;p&gt;A few quick contrasts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vague: “Make the homepage better.” Specific: “On the homepage hero, change the CTA label to &lt;code dir=&quot;auto&quot;&gt;Start free&lt;/code&gt;, verify with a Playwright screenshot saved to &lt;code dir=&quot;auto&quot;&gt;.agent/screenshots/&lt;/code&gt;.”&lt;/li&gt;
&lt;li&gt;Vague: “Tests are flaky, look into it.” Specific: “The test &lt;code dir=&quot;auto&quot;&gt;auth.spec.ts&lt;/code&gt; fails on a timeout. Increase the wait in that file to 10 seconds and rerun it until green.”&lt;/li&gt;
&lt;li&gt;Vague: “Clean up the deps.” Specific: “Remove &lt;code dir=&quot;auto&quot;&gt;lodash&lt;/code&gt; from &lt;code dir=&quot;auto&quot;&gt;package.json&lt;/code&gt;, replace its three usages in &lt;code dir=&quot;auto&quot;&gt;src/utils/&lt;/code&gt; with native equivalents, run the unit tests.”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The pattern is always the same. Name the file, name the command, name the proof.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;combining-steering-with-blocked-and-decide-promises&quot;&gt;Combining steering with BLOCKED and DECIDE promises&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Steering and promise tags solve different halves of the human-in-the-loop problem, and they work together.&lt;/p&gt;
&lt;p&gt;Promise tags are how the agent talks to you. When the agent cannot continue, it emits &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;BLOCKED:reason&amp;#x3C;/promise&gt;&lt;/code&gt; and the loop exits with code &lt;code dir=&quot;auto&quot;&gt;2&lt;/code&gt;. When it needs a decision it cannot make alone, it emits &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;DECIDE:question&amp;#x3C;/promise&gt;&lt;/code&gt; and exits with code &lt;code dir=&quot;auto&quot;&gt;3&lt;/code&gt;. A clean finish is &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;COMPLETE&amp;#x3C;/promise&gt;&lt;/code&gt; with exit code &lt;code dir=&quot;auto&quot;&gt;0&lt;/code&gt;, and hitting the iteration cap is exit code &lt;code dir=&quot;auto&quot;&gt;1&lt;/code&gt;. Ralph plays a sound and sends a desktop notification on &lt;code dir=&quot;auto&quot;&gt;BLOCKED&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;DECIDE&lt;/code&gt;, so you find out the moment a human is actually needed.&lt;/p&gt;
&lt;p&gt;Steering is how you talk back. The promise tells you the loop stopped and why. Your answer goes into &lt;code dir=&quot;auto&quot;&gt;.agent/STEERING.md&lt;/code&gt;, and then you rerun the loop. The next iteration reads your steering note first, handles the unblock or the chosen direction, removes it, and continues with the task list. The flow is tight:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# loop exits 2 with BLOCKED:cannot reach npm registry&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# you allow the domain from the host, then leave a steering note&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# in .agent/STEERING.md describing the retry, then:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;For a &lt;code dir=&quot;auto&quot;&gt;DECIDE&lt;/code&gt;, the agent gave you a question like “Option A vs B.” You pick one in the steering file in plain terms: “Use option A. Implement the REST client, not the GraphQL one. Then continue tasks.” The agent does not relitigate the choice because the steering note is now the instruction, and the fresh context means it reads that note as current truth.&lt;/p&gt;
&lt;p&gt;This pairing is the reason an overnight run stays autonomous without being unaccountable. The agent stops itself on the two events that genuinely need a person, notifies you, and waits. You drop a precise note into one file and restart. The promise mechanics behind those exit codes are worth knowing in full if you want the loop to stop on a signal rather than a guess, and the deny-by-default network policy that triggers many &lt;code dir=&quot;auto&quot;&gt;BLOCKED&lt;/code&gt; exits is enforced by the sandbox, not the agent.&lt;/p&gt;
&lt;p&gt;When a steering note itself is not enough, get inside the box. List the running sandboxes and open a shell to debug by hand, then write what you learned back into the steering file:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ls&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;exec&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-it&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x3C;ralph-sandbox-name&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bash&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Print the exact name for your project with &lt;code dir=&quot;auto&quot;&gt;./ralph.sh --print-name&lt;/code&gt;. Between promise tags that pull you in, a steering file that pushes instructions back, and a sandbox you can open and inspect, you can run a long autonomous loop and still keep both hands on the wheel whenever you want them there.&lt;/p&gt;
&lt;section aria-label=&quot;Frequently asked questions&quot;&gt; &lt;h2&gt;Frequently asked questions&lt;/h2&gt; &lt;div&gt; &lt;h3&gt;How do I steer a running AI agent in a Ralph loop?&lt;/h3&gt; &lt;p&gt;Edit the .agent/STEERING.md file while the loop is running. The agent reads that file at the top of every iteration, before it picks a task from tasks.json. It completes the steering items in sequence, removes them when done, and then resumes normal task work. Because each iteration starts with a fresh context, you do not have to time the edit precisely. The next iteration picks up whatever the file says.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;Do I have to stop the loop to change what the agent is doing?&lt;/h3&gt; &lt;p&gt;No. That is the point of steering. Editing STEERING.md injects work into the next iteration without killing the process, so you keep the warm sandbox, the running session, and the per-task git history intact. You only need to restart the loop when you want to change how it runs, such as switching the agent or the iteration count, since those flags are set at launch.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;What makes a good steering note?&lt;/h3&gt; &lt;p&gt;Specific, ordered, verifiable instructions. Name the exact files and commands, list prerequisites first, and give a clear success condition such as a build exiting 0 or a test going green. End the note with an explicit exit message so the agent knows where steering work stops. Keep it to one or a few critical items. If it grows into a full plan, that work belongs in tasks.json as real tasks instead.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;How does steering work together with BLOCKED and DECIDE promises?&lt;/h3&gt; &lt;p&gt;Promise tags are how the agent signals it needs you. BLOCKED exits with code 2 and DECIDE exits with code 3, both with a desktop notification. Steering is how you respond. You write the unblock steps or the chosen option into STEERING.md and rerun the loop, and the next fresh-context iteration reads your note first, handles it, removes it, and continues the task list.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;Where does the agent look for steering instructions?&lt;/h3&gt; &lt;p&gt;In .agent/STEERING.md. The .agent/PROMPT.md file tells the agent in its Before Starting section to check that file for critical work, complete items in sequence, remove them when done, and only proceed to implement tasks if no critical work is pending. So the latest version of STEERING.md is always the source of truth for what the agent does next.&lt;/p&gt; &lt;/div&gt; &lt;/section&gt; 
&lt;aside aria-label=&quot;Get started with Ralph Loop&quot;&gt; &lt;h2&gt;Run your own Ralph loop&lt;/h2&gt; &lt;p&gt;Ralph is a hackable script you point at your project. Install it and let an agent work through your task list.&lt;/p&gt; &lt;pre&gt;&lt;code&gt;npx @pageai/ralph-loop&lt;/code&gt;&lt;/pre&gt; &lt;div&gt; &lt;a href=&quot;https://www.npmjs.com/package/@pageai/ralph-loop&quot;&gt;
Install from npm
&lt;/a&gt; &lt;a href=&quot;https://github.com/pageai-pro/ralph-loop&quot;&gt;Star on GitHub&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=3TL8Ez66I3o&quot;&gt;Watch the walkthrough&lt;/a&gt; &lt;/div&gt; &lt;/aside&gt;</content:encoded><category>autonomous-agents</category></item><item><title>Claude Code vs Codex vs Cursor vs Gemini: Best CLI for Long-Running Agent Loops</title><link>https://ralphloop.sh/blog/best-agentic-cli-for-long-running-tasks/</link><guid isPermaLink="true">https://ralphloop.sh/blog/best-agentic-cli-for-long-running-tasks/</guid><description>There is no single best agentic CLI. Here is how Claude Code, Codex, Cursor, Gemini, Copilot, and opencode hold up over a long autonomous loop, and how to pick per task.

</description><pubDate>Wed, 08 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;There is no single best agentic CLI for long-running loops. The honest answer is that you pick per task, and the agent matters less than the loop you wrap around it. Claude Code, OpenAI’s Codex CLI, the Cursor CLI agent, Gemini CLI, GitHub Copilot CLI, and opencode all run inside Ralph behind the same &lt;code dir=&quot;auto&quot;&gt;--agent&lt;/code&gt; flag, so swapping one for another is a one word change, not a rewrite. This post compares the six on the dimensions that actually decide a multi-hour run: autonomy quality, cost, model options, tool use, and ecosystem.&lt;/p&gt;
&lt;p&gt;I am not going to quote benchmark numbers. Vendor leaderboards move every few weeks and rarely reflect how an agent behaves over dozens of iterations against your codebase. What follows is concrete and opinionated, grounded in how each CLI is invoked and how each one tends to behave when you leave it running.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-best-is-the-wrong-question-for-a-multi-hour-loop&quot;&gt;Why “best” is the wrong question for a multi-hour loop&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A one-shot demo and a multi-hour loop are different sports. In a demo, raw reasoning on the first try wins. In a loop, the agent runs the same task family dozens or hundreds of times, sees its own test failures, and fixes them on the next pass. What you actually want is an agent that responds well to feedback, follows a structured prompt, and exits cleanly so the harness can decide whether to loop again.&lt;/p&gt;
&lt;p&gt;That reframes the comparison. The strongest model on a leaderboard can still lose a long run if it ignores the prompt structure, never stops on its own, or burns your budget on token-heavy reasoning for mechanical work. A merely good model with tight verification gates will grind toward correct. This is the core point from the &lt;a href=&quot;https://ralphloop.sh/blog/agentic-coding-clis/&quot;&gt;field guide to agentic coding CLIs&lt;/a&gt;: the harness around the agent carries more of the result than the agent does.&lt;/p&gt;
&lt;p&gt;So the real question is not “which CLI is smartest.” It is “which CLI holds up when I leave it alone.” Hold that distinction while you read the comparison.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-ralph-makes-the-six-clis-interchangeable&quot;&gt;How Ralph makes the six CLIs interchangeable&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Ralph is a Bash script you point at a project. It does not reimplement any agent. It wraps whichever one you pick, runs it inside an isolated Docker Sandbox, watches the output, and loops with a fresh context each iteration. Three design choices are what make the agent swappable.&lt;/p&gt;
&lt;p&gt;First, one flag selects the agent. &lt;code dir=&quot;auto&quot;&gt;claude&lt;/code&gt; is the default, and you switch with &lt;code dir=&quot;auto&quot;&gt;--agent&lt;/code&gt; (short &lt;code dir=&quot;auto&quot;&gt;-a&lt;/code&gt;):&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Claude Code (default), 50 iterations&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Swap the agent, nothing else changes&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;codex&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cursor&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Second, a bare &lt;code dir=&quot;auto&quot;&gt;--&lt;/code&gt; separator forwards anything after it straight to the underlying CLI. Ralph parses its own flags first, then hands the rest to the agent untouched. That is how you pin a model without Ralph needing to know each vendor’s model names:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;codex&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--model&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gpt-5.5&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gemini&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--model&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pro&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Third, each agent gets its own deterministic sandbox, named &lt;code dir=&quot;auto&quot;&gt;ralph-&amp;#x3C;agent&gt;-&amp;#x3C;current-dir&gt;-&amp;#x3C;hash8&gt;&lt;/code&gt;. Your Claude sandbox and your Codex sandbox never share credentials, history, or installed tools, so you can compare agents on the same project without them stepping on each other. Print the name without starting a run:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--print-name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;codex&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Under the hood, Ralph builds a different invocation per agent but keeps the loop identical. The expansions are:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# claude:    sbx run ... claude .   -- --output-format stream-json --verbose -p &quot;$PROMPT_CONTENT&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# codex:     sbx run ... codex .    -- exec &quot;$PROMPT_CONTENT&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# copilot:   sbx run ... copilot .  -- -p &quot;$PROMPT_CONTENT&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# cursor:    sbx run ... cursor .   -- -p &quot;$PROMPT_CONTENT&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# gemini:    sbx run ... gemini .   -- -p &quot;$PROMPT_CONTENT&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# opencode:  sbx run ... opencode . -- run &quot;$PROMPT_CONTENT&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Every one of those runs the same loop: pick the top task from &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt;, work it, run the verification stack, commit, and either continue or stop on a promise tag. Because the loop is shared, you can treat the choice of CLI as a variable. Pick one, run it, and if it stalls on your codebase, change the flag and try another. The mechanics never move.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-dimensions-that-decide-a-long-run&quot;&gt;The dimensions that decide a long run&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Five things separate these agents once you are running them unattended. I will go through each, then break the agents down one by one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Autonomy quality&lt;/strong&gt; is how well the agent works without a human in the chair. Does it follow the prompt structure, stay on one task per invocation, run its own tests, and emit a clean completion signal? A loop has nobody to answer “should I proceed?”, so any agent that pauses for approval will stall unless you put it in a non-interactive mode.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cost&lt;/strong&gt; over a loop is dominated by model choice and iteration count, not the per-call price you see in a demo. Fifty iterations on a flagship model add up. The lever is the model flag and the iteration cap, which I cover in depth alongside the overnight-run architecture in &lt;a href=&quot;https://ralphloop.sh/blog/run-ai-coding-agent-overnight/&quot;&gt;how to run an AI coding agent overnight&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Model options&lt;/strong&gt; decide whether you can match the model to the task. Heavy reasoning (architecture, gnarly refactors) rewards the strongest model. Mechanical work (CRUD wiring, lint fixes, filling in tests) runs fine and cheaper on a mid tier model. The more model choices a CLI exposes through &lt;code dir=&quot;auto&quot;&gt;-- --model&lt;/code&gt;, the more you can tune.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tool use&lt;/strong&gt; is how the agent edits files, runs shell commands, and reads results. All six edit files and run commands. The difference shows up in how cleanly they run headless and how readable their output is while looping.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ecosystem&lt;/strong&gt; is auth, billing, and which world you already live in. A team standardized on GitHub auth has a different default than one paying for an Anthropic or OpenAI plan.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;claude-code&quot;&gt;Claude Code&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Claude Code is Ralph’s default for a reason. It is steady on long, multi-step tasks and follows a structured prompt closely, which is exactly what a fresh-context loop needs. Ralph runs it with &lt;code dir=&quot;auto&quot;&gt;--output-format stream-json --verbose&lt;/code&gt;, and that structured stream gives the loop the richest live, readable step view of any of the six. When you want to watch what the agent is doing each iteration, Claude Code shows the most.&lt;/p&gt;
&lt;p&gt;It runs in bypass-permissions mode inside the sandbox (&lt;code dir=&quot;auto&quot;&gt;--dangerously-skip-permissions&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;--permission-mode bypassPermissions&lt;/code&gt;), so it never pauses for approval during an unattended run. Model selection goes through &lt;code dir=&quot;auto&quot;&gt;-- --model&lt;/code&gt;. For autonomy quality and clarity of feedback, this is the smoothest first run. The end-to-end setup is in &lt;a href=&quot;https://ralphloop.sh/blog/run-claude-code-in-a-loop/&quot;&gt;running Claude Code in a loop&lt;/a&gt;. See the &lt;a href=&quot;https://code.claude.com/docs&quot;&gt;Claude Code docs&lt;/a&gt; for the flag surface.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;codex-cli&quot;&gt;Codex CLI&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Codex is OpenAI’s agent, run through its non-interactive &lt;code dir=&quot;auto&quot;&gt;exec&lt;/code&gt; mode. It is a strong reasoner on hard, self-contained problems, which makes it a natural pick when the task is logic-heavy rather than sprawling. Pin a model with &lt;code dir=&quot;auto&quot;&gt;-- --model gpt-5.5&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The thing to know about Codex in a loop: &lt;code dir=&quot;auto&quot;&gt;codex exec&lt;/code&gt; runs read-only by default, so a loop using the default mode will spin without ever editing a file. You grant write access deliberately with &lt;code dir=&quot;auto&quot;&gt;-- --sandbox workspace-write --ask-for-approval never&lt;/code&gt;, or bypass Codex’s own gates entirely with &lt;code dir=&quot;auto&quot;&gt;-- --dangerously-bypass-approvals-and-sandbox&lt;/code&gt;. The bypass flag is safe here because the microVM is the real boundary, not Codex policing itself. Codex also has a clean &lt;code dir=&quot;auto&quot;&gt;--json&lt;/code&gt; event stream for CI parsing. The full wiring, including the read-only gotcha and CI flags, is in &lt;a href=&quot;https://ralphloop.sh/blog/run-codex-cli-in-a-loop/&quot;&gt;running the Codex CLI in an autonomous loop&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;cursor-cli-agent&quot;&gt;Cursor CLI agent&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The Cursor CLI agent brings the headless &lt;code dir=&quot;auto&quot;&gt;cursor-agent&lt;/code&gt; to the loop, invoked with &lt;code dir=&quot;auto&quot;&gt;-p&lt;/code&gt;. If your team already lives in Cursor, running the same agent unattended over a sandbox lets you review a finished diff in the morning instead of pair-programming all afternoon. The autonomy quality is solid, and the appeal is continuity: you keep the agent and the mental model you already trust, and you add looping on top. Model and other flags pass through after &lt;code dir=&quot;auto&quot;&gt;--&lt;/code&gt; like every other agent.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;gemini-cli&quot;&gt;Gemini CLI&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Gemini CLI is Google’s agent, driven with &lt;code dir=&quot;auto&quot;&gt;-p&lt;/code&gt; and model selection through &lt;code dir=&quot;auto&quot;&gt;-- --model pro&lt;/code&gt;. It is worth reaching for when you want a different model family in the mix or you already live in the Google ecosystem. The argument for keeping a non-Anthropic, non-OpenAI option in your rotation is practical: when one agent stalls on a specific task, a different model family sometimes walks straight through it. Ralph makes that switch a one word change.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;github-copilot-cli&quot;&gt;GitHub Copilot CLI&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Copilot CLI slots in for teams standardized on GitHub’s tooling and auth. Ralph runs it with the same &lt;code dir=&quot;auto&quot;&gt;-p&lt;/code&gt; prompt pattern and the same sandbox boundary as the rest. The pull here is ecosystem, not raw capability: if your auth, your billing, and your repos already run through GitHub, Copilot is the path of least friction. The loop treats it identically to the others.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;opencode&quot;&gt;opencode&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;opencode is the open source option, invoked with its &lt;code dir=&quot;auto&quot;&gt;run&lt;/code&gt; subcommand. It is the pick when you want to avoid vendor lock-in, run an agent you can fully inspect and modify, or route to a provider and model of your own choosing. For cost-sensitive runs where you want maximum control over the model layer, an open agent you can point at any backend is a real advantage. You trade some of the polished, batteries-included feel of the vendor CLIs for control and inspectability.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;pick-this-when&quot;&gt;Pick this when&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Here is the practical version, stripped of hedging. Match the agent to the situation rather than hunting for one winner.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pick &lt;strong&gt;Claude Code&lt;/strong&gt; when you want the smoothest first loop and the clearest live view of what the agent is doing. It is the right default, and the right place to start if you are new to running loops.&lt;/li&gt;
&lt;li&gt;Pick &lt;strong&gt;Codex&lt;/strong&gt; when the task is logic-heavy and self-contained, and you want explicit model pinning plus a clean JSON event stream for CI. Remember to grant write access, or it will not edit anything.&lt;/li&gt;
&lt;li&gt;Pick &lt;strong&gt;Cursor&lt;/strong&gt; when your team already uses Cursor and you want the same agent to run unattended so you review a diff instead of babysitting.&lt;/li&gt;
&lt;li&gt;Pick &lt;strong&gt;Gemini&lt;/strong&gt; when you want a different model family in your rotation, or you are already in the Google ecosystem.&lt;/li&gt;
&lt;li&gt;Pick &lt;strong&gt;Copilot&lt;/strong&gt; when your auth and billing already run through GitHub and you want the least new setup.&lt;/li&gt;
&lt;li&gt;Pick &lt;strong&gt;opencode&lt;/strong&gt; when you want an open, inspectable agent, no vendor lock-in, or full control over the model and provider behind it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A decision guide for the common case:&lt;/p&gt;
&lt;pre dir=&quot;ltr&quot;&gt;flowchart TD
  Start([&quot;Choosing an agent for a long loop&quot;]) --&gt; Q1{&quot;New to running loops?&quot;}
  Q1 --&gt;|&quot;yes&quot;| Claude[&quot;Start with claude (default, richest live view)&quot;]
  Q1 --&gt;|&quot;no&quot;| Q2{&quot;What matters most?&quot;}
  Q2 --&gt;|&quot;hard reasoning task&quot;| Codex[&quot;codex (grant write access)&quot;]
  Q2 --&gt;|&quot;already in Cursor&quot;| Cursor[&quot;cursor&quot;]
  Q2 --&gt;|&quot;different model family&quot;| Gemini[&quot;gemini -- --model pro&quot;]
  Q2 --&gt;|&quot;GitHub-standardized team&quot;| Copilot[&quot;copilot&quot;]
  Q2 --&gt;|&quot;open, no lock-in&quot;| Opencode[&quot;opencode&quot;]
  Claude --&gt; Swap{&quot;Stalls on your codebase?&quot;}
  Codex --&gt; Swap
  Cursor --&gt; Swap
  Gemini --&gt; Swap
  Copilot --&gt; Swap
  Opencode --&gt; Swap
  Swap --&gt;|&quot;yes&quot;| Change[&quot;Change one --agent flag, retry&quot;]
  Swap --&gt;|&quot;no&quot;| Ship[&quot;Let the loop run, review in the morning&quot;]&lt;/pre&gt;
&lt;p&gt;The last edge is the important one. Because the harness is shared, “this agent stalled” is not a dead end. It is a flag change. That is the whole reason Ralph treats the agent as swappable rather than picking one for you.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-actually-moves-the-result&quot;&gt;What actually moves the result&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you take one thing from this comparison, take this: a weaker agent inside a good loop will out-ship a stronger agent you babysit in a single session. The loop gives the agent fresh context every iteration, keeps state on disk, isolates it in a sandbox, and runs real verification gates. Those four things matter more than the gap between any two of these CLIs.&lt;/p&gt;
&lt;p&gt;Fresh context per iteration is what beats context rot. Each pass boots the agent clean, so it does not drag an hours-long transcript from one task to the next. The filesystem and git history are the memory layer: progress lives in &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt;, the per-task spec files, &lt;code dir=&quot;auto&quot;&gt;.agent/logs/LOG.md&lt;/code&gt;, and the git log, not in a chat window. This is the mechanic Geoffrey Huntley described in the &lt;a href=&quot;https://ghuntley.com/ralph/&quot;&gt;original Ralph writeup&lt;/a&gt;, and it applies identically to all six agents.&lt;/p&gt;
&lt;p&gt;Verification is what lets a cheaper model succeed. The loop assumes a stack of Playwright for end-to-end tests, Vitest for unit tests, TypeScript for types, ESLint for lint, and Prettier for format. The repo mantra is blunt: if you didn’t test it, it doesn’t work. The agent does not need to be right on the first try. It needs to write code, run the gates, read the failures, and fix them next pass. When you compare agents for long runs, you are really comparing how each one responds to that feedback, not how clever its first draft looks.&lt;/p&gt;
&lt;p&gt;The sandbox is what makes any of this safe to walk away from. Ralph runs every agent inside a Docker Sandbox microVM with deny-by-default networking, so bypass-permissions mode is reasonable: the worst the agent can do is wreck a disposable VM, not read your SSH keys. You open exactly what a task needs with &lt;code dir=&quot;auto&quot;&gt;sbx policy allow network &amp;#x3C;name&gt; &amp;#x3C;domain&gt;&lt;/code&gt;. The full reasoning is in the &lt;a href=&quot;https://docs.docker.com/ai/sandboxes/&quot;&gt;Docker Sandboxes docs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And the loop stops on a signal, not a vibe. Each agent emits a promise tag that Ralph reads:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;COMPLETE&amp;#x3C;/promise&gt;&lt;/code&gt; means every task is finished.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;BLOCKED:reason&amp;#x3C;/promise&gt;&lt;/code&gt; means the agent needs human help.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;DECIDE:question&amp;#x3C;/promise&gt;&lt;/code&gt; means it needs a decision you have to make.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those map to exit codes: &lt;code dir=&quot;auto&quot;&gt;0&lt;/code&gt; for COMPLETE, &lt;code dir=&quot;auto&quot;&gt;1&lt;/code&gt; for MAX_ITERATIONS, &lt;code dir=&quot;auto&quot;&gt;2&lt;/code&gt; for BLOCKED, and &lt;code dir=&quot;auto&quot;&gt;3&lt;/code&gt; for DECIDE. You branch on them in CI or a wrapper script, which means the comparison between agents is also fair: every one of them ends with a verdict you can act on.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;putting-it-together&quot;&gt;Putting it together&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;There is no best agentic CLI, so stop looking for one. Start with Claude Code because it is the steady default with the clearest output. Reach for Codex on hard reasoning tasks, Cursor when your team already uses it, Gemini for a different model family, Copilot when you are GitHub-standardized, and opencode when you want an open agent with no lock-in. Then let the harness do the heavy lifting.&lt;/p&gt;
&lt;p&gt;The shortest path to running any of them is three commands:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 1. install&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@pageai/ralph-loop&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 2. authenticate the agent inside its sandbox&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--login&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;codex&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# 3. run the loop on your chosen agent and model&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;codex&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--model&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gpt-5.5&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Swap &lt;code dir=&quot;auto&quot;&gt;codex&lt;/code&gt; for any of the six and the loop is identical. Pick the agent, pin the model, and let it work one task per iteration inside the sandbox while you sleep. If it stalls, you already know the fix: change one flag.&lt;/p&gt;
&lt;section aria-label=&quot;Frequently asked questions&quot;&gt; &lt;h2&gt;Frequently asked questions&lt;/h2&gt; &lt;div&gt; &lt;h3&gt;Which is the best agentic CLI for long-running loops?&lt;/h3&gt; &lt;p&gt;There is no single winner. Claude Code is the steady default with the richest live output, Codex is strong on hard reasoning tasks, Cursor suits teams already using Cursor, Gemini brings a different model family, Copilot fits GitHub-standardized teams, and opencode is the open source option. The loop you wrap around the agent matters more than the agent, so pick one and swap with the --agent flag if it stalls.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;Claude Code vs Codex: which should I use?&lt;/h3&gt; &lt;p&gt;Use Claude Code as the default when you want the smoothest unattended run and the clearest live view of each iteration, since Ralph parses its stream-json output. Use Codex for logic-heavy, self-contained problems and CI pipelines that parse its JSON event stream. One gotcha: codex exec is read-only by default, so grant write access with -- --sandbox workspace-write --ask-for-approval never or it will not edit files.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;How do I switch between agents in Ralph?&lt;/h3&gt; &lt;p&gt;Pass the --agent flag, or -a for short. ./ralph.sh runs Claude Code by default, ./ralph.sh --agent codex runs Codex, and ./ralph.sh -a cursor -n 5 runs Cursor for five iterations. Each agent gets its own sandbox named ralph-&amp;#x3C;agent&gt;-&amp;#x3C;dir&gt;-&amp;#x3C;hash8&gt;, so they do not share credentials or history. The loop itself is identical across all six.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;Does the choice of agent matter more than the loop?&lt;/h3&gt; &lt;p&gt;No. A weaker agent inside a good loop, with fresh context each iteration, state on disk, sandbox isolation, and real verification gates, will out-ship a stronger agent you babysit in a single session. Those four properties decide the result more than the gap between any two CLIs, which is why Ralph treats the agent as a swappable variable.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;How do I pick a model for each agent?&lt;/h3&gt; &lt;p&gt;Model selection is an agent flag, not a Ralph setting, so you pass it after the -- separator. For example ./ralph.sh --agent codex -- --model gpt-5.5 or ./ralph.sh -a gemini -- --model pro. Match the model to the task: a strong model for heavy reasoning, a cheaper mid tier model for mechanical work, since model choice and iteration count are the biggest cost levers in a long run.&lt;/p&gt; &lt;/div&gt; &lt;/section&gt; 
&lt;aside aria-label=&quot;Get started with Ralph Loop&quot;&gt; &lt;h2&gt;Run your own Ralph loop&lt;/h2&gt; &lt;p&gt;Ralph is a hackable script you point at your project. Install it and let an agent work through your task list.&lt;/p&gt; &lt;pre&gt;&lt;code&gt;npx @pageai/ralph-loop&lt;/code&gt;&lt;/pre&gt; &lt;div&gt; &lt;a href=&quot;https://www.npmjs.com/package/@pageai/ralph-loop&quot;&gt;
Install from npm
&lt;/a&gt; &lt;a href=&quot;https://github.com/pageai-pro/ralph-loop&quot;&gt;Star on GitHub&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=3TL8Ez66I3o&quot;&gt;Watch the walkthrough&lt;/a&gt; &lt;/div&gt; &lt;/aside&gt;</content:encoded><category>agentic-clis</category></item><item><title>Ralph Loop Failure Modes (Context Rot, Runaway Cost) and How to Avoid Them</title><link>https://ralphloop.sh/blog/ralph-loop-failure-modes/</link><guid isPermaLink="true">https://ralphloop.sh/blog/ralph-loop-failure-modes/</guid><description>A Ralph loop fails in a handful of predictable ways. Here is each failure mode, why it happens, and the specific guardrail that stops it.

</description><pubDate>Sat, 04 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A Ralph loop does not fail in mysterious ways. It fails in a short list of predictable ones: the agent forgets the plot, the loop burns money forever, it thrashes on a task that is too big or too vague, it ships work that looks done but is wrong, or it damages something on your machine. Each of these has a known cause and a specific guardrail. None of them require luck to avoid.&lt;/p&gt;
&lt;p&gt;This post walks through every failure mode of running an &lt;a href=&quot;https://ralphloop.sh/blog/what-is-the-ralph-technique/&quot;&gt;AI coding agent in a loop&lt;/a&gt;, why each one happens, and the exact mechanism in &lt;code dir=&quot;auto&quot;&gt;ralph.sh&lt;/code&gt; that prevents it. If you are setting up a loop for the first time or wondering why a run went sideways, this is the checklist.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-are-the-failure-modes-of-a-ralph-loop&quot;&gt;What are the failure modes of a Ralph loop?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Five, mostly. They are context rot, runaway cost, thrashing on a bad task, silent wrong work, and sandbox damage. Every one maps to a guardrail you can turn on or design around. Here is the short version before the details:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Context rot.&lt;/strong&gt; Fixed by fresh context per iteration and state on disk.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Runaway cost and infinite loops.&lt;/strong&gt; Fixed by an iteration cap, a completion promise, and a budget you set.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Thrashing on a bad or oversized task.&lt;/strong&gt; Fixed by atomic tasks, one task per iteration, and the &lt;code dir=&quot;auto&quot;&gt;BLOCKED&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;DECIDE&lt;/code&gt; promise tags.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Silent wrong work.&lt;/strong&gt; Fixed by verification gates and screenshots.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sandbox escape or damage.&lt;/strong&gt; Fixed by running the agent inside a Docker Sandbox.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The rest of this post takes each one in turn.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;context-rot-the-agent-loses-the-plot&quot;&gt;Context rot: the agent loses the plot&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Context rot is the slow decay of an agent’s reasoning as its context window fills with stale and conflicting information. Old tool output, abandoned plans, your corrections, the agent’s own apologies, half-finished edits: all of it competes with the task that actually matters right now. The longer a single session runs, the worse the signal-to-noise ratio gets. You see it as repeated mistakes, forgotten constraints, and confident edits that quietly undo earlier good work.&lt;/p&gt;
&lt;p&gt;This is the failure the Ralph technique was built to kill, so the guardrail is the core of the design rather than a bolt-on.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;fix-fresh-context-every-iteration&quot;&gt;Fix: fresh context every iteration&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Each iteration spawns the agent with a clean context window. It reads only what matters right now: the prompt, a short project summary, the task list, and the spec for the one task it is about to work. There is no transcript of the previous twelve iterations clogging the window. The model spends its attention on the current task instead of re-reading its own history.&lt;/p&gt;
&lt;p&gt;Think of it as a series of sprints rather than one marathon. A marathon session accumulates fatigue. A fresh sprint each time stays sharp.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;fix-state-lives-on-disk-not-in-chat&quot;&gt;Fix: state lives on disk, not in chat&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;If the agent forgets everything between iterations, how does it make progress? Because progress does not live in the conversation. It lives in files. Ralph keeps state under &lt;code dir=&quot;auto&quot;&gt;.agent/&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.agent/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── PROMPT.md      # Prompt sent to the agent each iteration&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── tasks.json     # Task lookup table&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── tasks/         # Per-task specs (TASK-{ID}.json)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;├── logs/LOG.md    # Progress log&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;└── history/       # Per-iteration output logs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;When a fresh agent boots, it reads &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt; to see what is done, reads &lt;code dir=&quot;auto&quot;&gt;.agent/logs/LOG.md&lt;/code&gt; to see what happened recently, and reads the git log to see the actual committed changes. The code on disk is the record. The window never grows large enough to rot because it resets every pass. The deeper version of this design, including how the prompt file makes a fresh agent reorient in seconds, is in &lt;a href=&quot;https://ralphloop.sh/blog/ralph-loop-prompt-file/&quot;&gt;how to write the PROMPT.md file that drives a Ralph loop&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;runaway-cost-and-infinite-loops-the-money-fire&quot;&gt;Runaway cost and infinite loops: the money fire&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The naive Ralph loop is one line:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;while&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;do&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cat&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;PROMPT.md&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your-agent-cli&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;done&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That loop never stops on its own. It has no iteration cap and no notion of success. Point it at a paid model and walk away, and it will happily keep calling the API until your bill says otherwise. Even when the work is genuinely done, a loop without a stop condition will keep re-running the agent to rediscover that there is nothing left to do, paying full freight each time.&lt;/p&gt;
&lt;p&gt;Runaway cost has two shapes. One is the loop that never terminates. The other is the loop that terminates eventually but does far more iterations than the work needed. Both are guardrail problems, not model problems.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;fix-an-iteration-cap-with--n&quot;&gt;Fix: an iteration cap with -n&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;ralph.sh&lt;/code&gt; takes a hard cap on iterations. The default is ten. You set a higher one explicitly:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--max-iterations&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;To smoke test the setup without committing to a long run, do exactly one pass:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--once&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;When the loop hits the cap with work still pending, it exits with code &lt;code dir=&quot;auto&quot;&gt;1&lt;/code&gt; (MAX_ITERATIONS). That is not a crash. It is the safety net doing its job. You read the log, decide whether to top up the budget, and run it again.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;fix-a-completion-promise-not-a-vibe&quot;&gt;Fix: a completion promise, not a vibe&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The loop stops on an explicit signal, never on a guess. The agent emits a promise tag to declare where things stand:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;COMPLETE&amp;#x3C;/promise&gt;&lt;/code&gt; every task is finished.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;BLOCKED:reason&amp;#x3C;/promise&gt;&lt;/code&gt; the agent needs a human to clear something.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;promise&gt;DECIDE:question&amp;#x3C;/promise&gt;&lt;/code&gt; the agent hit a real decision point.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;ralph.sh&lt;/code&gt; watches for these tags and translates them into exit codes:&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Code&lt;/th&gt;&lt;th&gt;Meaning&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;COMPLETE. All tasks finished.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;MAX_ITERATIONS. Reached the cap.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;BLOCKED. Needs human help.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;DECIDE. Needs a human decision.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;A &lt;code dir=&quot;auto&quot;&gt;COMPLETE&lt;/code&gt; ends the run cleanly the moment the work is actually done, so you do not pay for victory laps. The full design of the stop condition, and why a promise beats letting the agent decide it feels finished, is in &lt;a href=&quot;https://ralphloop.sh/blog/ralph-loop-completion-promise/&quot;&gt;completion promises and exit codes&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;fix-a-budget-you-choose-up-front&quot;&gt;Fix: a budget you choose up front&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Cost control is mostly about deciding limits before you start: how many iterations, which model, and what the loop is allowed to touch. A cheaper model on a well specified task can outperform an expensive model on a vague one, because the expensive model spends its budget flailing. The full treatment of caps, model choice, and the verification gates that keep a loop from spinning is in &lt;a href=&quot;https://ralphloop.sh/blog/ai-agent-cost-control/&quot;&gt;cost control for autonomous AI coding agents&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;thrashing-on-a-bad-or-oversized-task&quot;&gt;Thrashing on a bad or oversized task&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Give an agent a task like “refactor the billing system” and it will thrash. The task is too big to hold in one iteration and too vague to verify, so the agent makes a sprawling change, second-guesses it, partially reverts, and produces a commit you cannot review. Repeat that across iterations and the loop spins without converging. This is the most common reason a loop “does not work,” and it is almost always a task design problem rather than an agent problem.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;fix-atomic-tasks&quot;&gt;Fix: atomic tasks&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The unit of work matters. A task should be small enough to finish in a single iteration and specific enough to verify. “Add a unit test for the discount calculation in &lt;code dir=&quot;auto&quot;&gt;cart.ts&lt;/code&gt;” is atomic. “Improve test coverage” is not. The loop is only as good as the tasks you feed it. Garbage tasks in, garbage commits out. How to decompose a large project into atomic, independently verifiable packets is the heart of spec-driven development, and it is upstream of everything the loop does.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;fix-one-task-per-iteration&quot;&gt;Fix: one task per iteration&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The rule that keeps a loop reliable is blunt: the agent completes exactly one task, commits, and stops. It never batches several tasks into one iteration. Batching is how an agent drifts off scope and produces a tangled diff. One task per invocation keeps each commit reviewable and each iteration’s blast radius small.&lt;/p&gt;
&lt;p&gt;This pairs with the task lookup table. The agent reads &lt;code dir=&quot;auto&quot;&gt;.agent/tasks.json&lt;/code&gt;, picks the highest-priority incomplete task, opens its spec at &lt;code dir=&quot;auto&quot;&gt;.agent/tasks/TASK-{ID}.json&lt;/code&gt;, works only those steps, and exits. The table is a lightweight index, so a project can hold hundreds of tasks while each iteration loads only the one it needs.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;fix-blocked-and-decide-instead-of-guessing&quot;&gt;Fix: BLOCKED and DECIDE instead of guessing&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;When a task is genuinely underspecified or the agent hits a wall it cannot clear, the worst outcome is for it to guess and burn iterations on a wrong assumption. The promise tags give it an honest exit. &lt;code dir=&quot;auto&quot;&gt;BLOCKED:reason&lt;/code&gt; stops the loop and hands you the blocker. &lt;code dir=&quot;auto&quot;&gt;DECIDE:question&lt;/code&gt; stops and asks for your call. The loop returns exit code &lt;code dir=&quot;auto&quot;&gt;2&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;3&lt;/code&gt;, and you are not staring at a dead terminal wondering what happened. A loop that stops to ask is far cheaper than a loop that thrashes in silence.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;silent-wrong-work-it-looks-done-but-it-is-broken&quot;&gt;Silent wrong work: it looks done but it is broken&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The scariest failure is the one that looks like success. The agent flips a task to done, commits, and moves on, but the code does not actually work. Maybe it compiles and the tests it wrote test the wrong thing. Maybe the UI renders but the button does nothing. Across an overnight run, silent wrong work compounds: later tasks build on the broken one, and you wake up to a green task list and a red product.&lt;/p&gt;
&lt;p&gt;The cause is letting the agent self-assess on a vibe. The fix is to take that judgment away from the agent and give it to a tool that cannot be fooled by optimism.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;fix-verification-gates&quot;&gt;Fix: verification gates&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Before the agent calls a task done, it runs the project’s checks. Ralph assumes a verification stack: Playwright for end to end tests, Vitest for unit tests, TypeScript for types, ESLint for linting, and Prettier for formatting. The repo mantra is exactly this blunt: if you didn’t test it, it doesn’t work.&lt;/p&gt;
&lt;p&gt;The gate is binary. The checks pass or the iteration is not done. If a check fails, the agent loops back inside the same iteration and fixes the code until it passes. The agent does not get to claim success while the test suite is red. This is what makes a commit in the git log trustworthy rather than aspirational.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;fix-screenshots-for-the-things-tests-miss&quot;&gt;Fix: screenshots for the things tests miss&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Some failures do not show up in a unit test. A layout that is technically rendered but visually broken passes a DOM assertion and fails a human glance. For UI work, the loop takes a screenshot as part of completing a task, so the artifact you review in the morning includes proof of what the agent saw. A screenshot is a cheap, honest signal that catches the class of bugs that slip past assertions.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;sandbox-escape-and-damage-to-your-machine&quot;&gt;Sandbox escape and damage to your machine&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Autonomous agents run in bypass-permissions mode, often called YOLO mode, because stopping to approve every file write would defeat the point of an overnight loop. Claude Code calls this &lt;code dir=&quot;auto&quot;&gt;--dangerously-skip-permissions&lt;/code&gt;. The flag name is honest. An agent with your permissions and no approval prompts can read your SSH keys, touch files outside the repo, run arbitrary install scripts, and make network calls you never intended. On your laptop, that is a real risk. The mistake is treating the agent’s good behavior as the safety boundary.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;fix-the-sandbox-is-the-boundary&quot;&gt;Fix: the sandbox is the boundary&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Run the agent inside an isolated Docker Sandbox microVM. The boundary is the sandbox, not the agent’s restraint. &lt;code dir=&quot;auto&quot;&gt;ralph.sh&lt;/code&gt; does this by default through the &lt;code dir=&quot;auto&quot;&gt;sbx&lt;/code&gt; CLI, giving each run a deterministic sandbox name of the form &lt;code dir=&quot;auto&quot;&gt;ralph-&amp;#x3C;agent&gt;-&amp;#x3C;current-dir&gt;-&amp;#x3C;hash8&gt;&lt;/code&gt;. Inside that microVM, the agent can run in YOLO mode because the worst it can damage is the sandbox.&lt;/p&gt;
&lt;p&gt;You can inspect and operate the sandbox like any container:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ls&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;exec&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-it&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x3C;name&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bash&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Network is deny-by-default. The agent gets only the domains you allow:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sbx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;policy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;allow&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;network&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x3C;name&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;registry.npmjs.org&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That last point matters for both safety and cost. A deny-by-default network blocks exfiltration and stops an agent from wandering off to install or call something you did not sanction. The isolation model is documented in the &lt;a href=&quot;https://docs.docker.com/ai/sandboxes/&quot;&gt;Docker Sandboxes docs&lt;/a&gt;, and the bypass flag the loop relies on is in the &lt;a href=&quot;https://code.claude.com/docs&quot;&gt;Claude Code docs&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;failure-modes-mapped-to-guardrails&quot;&gt;Failure modes mapped to guardrails&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Here is the whole picture in one view: each failure on the left, each guardrail on the right.&lt;/p&gt;
&lt;pre dir=&quot;ltr&quot;&gt;flowchart LR
    subgraph Failures[&quot;Failure modes&quot;]
        F1[&quot;Context rot&quot;]
        F2[&quot;Runaway cost / infinite loop&quot;]
        F3[&quot;Thrashing on a bad task&quot;]
        F4[&quot;Silent wrong work&quot;]
        F5[&quot;Sandbox damage&quot;]
    end
    subgraph Guards[&quot;Guardrails&quot;]
        G1[&quot;Fresh context + state on disk&quot;]
        G2[&quot;-n cap + completion promise + budget&quot;]
        G3[&quot;Atomic tasks, one per iteration, BLOCKED / DECIDE&quot;]
        G4[&quot;Verification gates + screenshots&quot;]
        G5[&quot;Docker Sandbox microVM&quot;]
    end
    F1 --&gt; G1
    F2 --&gt; G2
    F3 --&gt; G3
    F4 --&gt; G4
    F5 --&gt; G5&lt;/pre&gt;
&lt;p&gt;Read it as a contract. If you have turned on the guardrail, the matching failure mode is handled. If a run went wrong, find which guardrail was missing or misconfigured.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-pre-flight-checklist-before-a-long-run&quot;&gt;A pre-flight checklist before a long run&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Before you start a multi-hour or overnight loop, walk this list. It is the difference between waking up to a clean branch and waking up to a mess.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Cap the iterations.&lt;/strong&gt; Pass &lt;code dir=&quot;auto&quot;&gt;-n&lt;/code&gt; with a number you are willing to pay for. Do not run the uncapped one-liner.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test the setup with one pass.&lt;/strong&gt; Run &lt;code dir=&quot;auto&quot;&gt;./ralph.sh --once&lt;/code&gt; and read the output before trusting a long run.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Check your tasks are atomic.&lt;/strong&gt; Each task should be finishable in one iteration and verifiable by a check. If you cannot write the acceptance criteria, the task is not ready.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Confirm the verification stack runs.&lt;/strong&gt; Make sure tests, lint, and type checking actually execute in the project, because the loop leans on them as its truth signal.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Run in the sandbox.&lt;/strong&gt; Keep the agent inside the Docker Sandbox and the network on deny-by-default. Allow only the domains the build genuinely needs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pick the right agent and model.&lt;/strong&gt; Switch agents with &lt;code dir=&quot;auto&quot;&gt;--agent&lt;/code&gt; and pass model flags after a separator:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--agent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;codex&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--model&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gpt-5.5&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gemini&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--model&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pro&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Supported agents are &lt;code dir=&quot;auto&quot;&gt;claude&lt;/code&gt; (the default), &lt;code dir=&quot;auto&quot;&gt;codex&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;copilot&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;cursor&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;gemini&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;opencode&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-honest-framing&quot;&gt;The honest framing&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A Ralph loop is persistence with a memory and a stop button. Its failure modes are the failure modes of any long autonomous process: it forgets, it overspends, it chases a bad goal, it lies to itself about success, and it can break things it touches. The technique does not pretend these do not exist. It pairs each one with a guardrail and makes the guardrails the default.&lt;/p&gt;
&lt;p&gt;None of this removes your judgment from the loop. You still write the tasks, set the budget, and review the commits. What the guardrails buy you is the confidence to let an agent grind for hours without standing over it. Get the five right and the loop becomes boring in the best way: it either finishes, or it stops and tells you exactly why.&lt;/p&gt;
&lt;section aria-label=&quot;Frequently asked questions&quot;&gt; &lt;h2&gt;Frequently asked questions&lt;/h2&gt; &lt;div&gt; &lt;h3&gt;What are the most common ways a Ralph loop fails?&lt;/h3&gt; &lt;p&gt;There are five common failure modes: context rot where the agent loses the plot over a long session, runaway cost or infinite loops, thrashing on a task that is too big or too vague, silent wrong work that looks done but is broken, and damage from running an agent without a sandbox. Each one maps to a specific guardrail in ralph.sh.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;How does a Ralph loop avoid context rot?&lt;/h3&gt; &lt;p&gt;It resets the context window every iteration and stores project state on disk. Because the agent reads the task list, logs, and git history each pass instead of carrying a growing conversation, the window never gets large enough to rot. Progress lives in files like tasks.json and commits, not in chat memory.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;How do I stop a Ralph loop from burning money?&lt;/h3&gt; &lt;p&gt;Set an iteration cap with -n, rely on the completion promise so the loop stops the moment all tasks are done, and choose your model and budget before you start. The loop exits with code 1 when it hits the cap, which is a safety net rather than a failure. Running a single pass with --once first confirms the setup before a long run.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;Why does my agent thrash instead of finishing the task?&lt;/h3&gt; &lt;p&gt;Almost always because the task is too large or too vaguely specified to finish and verify in one iteration. The fix is atomic tasks, one task per invocation, and clear acceptance criteria. When a task is genuinely blocked or needs a decision, the agent should emit a BLOCKED or DECIDE promise and stop rather than guess.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;Is it safe to run an autonomous coding agent in YOLO mode?&lt;/h3&gt; &lt;p&gt;Only inside a sandbox. Bypass-permissions mode is dangerous on your laptop because the agent can read credentials and touch files outside the repo. Run it inside a Docker Sandbox microVM with deny-by-default networking, so the boundary is the sandbox rather than the agent&apos;s good behavior.&lt;/p&gt; &lt;/div&gt; &lt;/section&gt; 
&lt;aside aria-label=&quot;Get started with Ralph Loop&quot;&gt; &lt;h2&gt;Run your own Ralph loop&lt;/h2&gt; &lt;p&gt;Ralph is a hackable script you point at your project. Install it and let an agent work through your task list.&lt;/p&gt; &lt;pre&gt;&lt;code&gt;npx @pageai/ralph-loop&lt;/code&gt;&lt;/pre&gt; &lt;div&gt; &lt;a href=&quot;https://www.npmjs.com/package/@pageai/ralph-loop&quot;&gt;
Install from npm
&lt;/a&gt; &lt;a href=&quot;https://github.com/pageai-pro/ralph-loop&quot;&gt;Star on GitHub&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=3TL8Ez66I3o&quot;&gt;Watch the walkthrough&lt;/a&gt; &lt;/div&gt; &lt;/aside&gt;</content:encoded><category>ralph-technique</category></item><item><title>How to Write a PRD an AI Agent Can Actually Build From</title><link>https://ralphloop.sh/blog/how-to-write-prd-for-ai-agent/</link><guid isPermaLink="true">https://ralphloop.sh/blog/how-to-write-prd-for-ai-agent/</guid><description>A PRD for an AI coding agent needs goals, constraints, and verifiable acceptance criteria, not a feature wishlist. Here is what goes in PRD.md and SUMMARY.md and how to draft it.

</description><pubDate>Tue, 31 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A PRD an AI agent can build from is not a feature wishlist. It is three things in one document: goals (what to build and why), constraints (the stack, the boundaries, what is out of scope), and verifiable acceptance criteria (conditions the agent can check by running a command and reading the output). Drop any one of those and the agent fills the gap with a guess. The guess looks fine in the diff and breaks on the case nobody wrote down.&lt;/p&gt;
&lt;p&gt;This post is the practical version: what goes in &lt;code dir=&quot;auto&quot;&gt;.agent/prd/PRD.md&lt;/code&gt;, what goes in the short &lt;code dir=&quot;auto&quot;&gt;SUMMARY.md&lt;/code&gt; the loop sends every iteration, how to draft both with the &lt;code dir=&quot;auto&quot;&gt;prd-creator&lt;/code&gt; skill, and how to write acceptance criteria an agent can actually verify. It assumes you already know the shape of &lt;a href=&quot;https://ralphloop.sh/blog/spec-driven-development-with-ai/&quot;&gt;spec-driven development with AI&lt;/a&gt; and want to write the document that sits at the top of it.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-a-prd-for-an-ai-agent-actually-is&quot;&gt;What a PRD for an AI agent actually is&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A human PRD and an agent PRD overlap, but they are not the same document. A human reads a PRD, fills the gaps with judgment, and asks a teammate when something is unclear. An agent does none of that. It reads what is on disk, and when the spec is silent, it invents an answer and proceeds with full confidence. So the agent PRD has to do more work up front.&lt;/p&gt;
&lt;p&gt;Three properties decide whether a PRD is buildable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Goals are concrete, not aspirational.&lt;/strong&gt; “Make onboarding delightful” is not a goal an agent can build toward. “A new user reaches the dashboard in three steps or fewer after submitting the signup form” is. State the outcome in terms something can observe.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Constraints are explicit.&lt;/strong&gt; Name the framework, the data store, the auth approach, and the libraries you have already committed to. Name what is out of scope just as clearly. An out-of-scope section is a fence: without it, an agent on a long run happily adds a feature you never asked for and now have to maintain.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Acceptance criteria are verifiable.&lt;/strong&gt; Each criterion is a condition the agent can confirm by running a test, hitting an endpoint, or reading a file. “Login works” is a vibe. “POST /api/login with a wrong password returns 401 and the body &lt;code dir=&quot;auto&quot;&gt;{ error: &apos;Invalid credentials&apos; }&lt;/code&gt;” is a criterion. The difference is whether a machine can return a yes or a no without your opinion.&lt;/p&gt;
&lt;p&gt;The reason this matters more for agents than for people: an autonomous loop amplifies whatever you feed it. Feed it ambiguity and it amplifies ambiguity across every iteration. The Ralph technique runs an agent against your task list until the work is done, and the whole design rests on the agent rebuilding its understanding from files on each pass. If you want the mechanics of that loop, start with &lt;a href=&quot;https://ralphloop.sh/blog/what-is-the-ralph-technique/&quot;&gt;what is the Ralph technique&lt;/a&gt;. The PRD is the document the entire loop reads from.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-the-prd-lives-prdmd-and-summarymd&quot;&gt;Where the PRD lives: PRD.md and SUMMARY.md&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Ralph keeps the product specification in two files under &lt;code dir=&quot;auto&quot;&gt;.agent/prd/&lt;/code&gt;, and the split is deliberate.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;PRD.md&lt;/code&gt; is the full document. It is for depth: a human reads it once to understand the project, and the agent reads it when it needs detail it cannot get from the summary. The &lt;code dir=&quot;auto&quot;&gt;prd-creator&lt;/code&gt; skill writes it with a consistent set of sections:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;App overview and objectives&lt;/li&gt;
&lt;li&gt;Target audience&lt;/li&gt;
&lt;li&gt;Success metrics and KPIs&lt;/li&gt;
&lt;li&gt;Competitive analysis&lt;/li&gt;
&lt;li&gt;Core features and user flows&lt;/li&gt;
&lt;li&gt;Technical stack&lt;/li&gt;
&lt;li&gt;Prerequisites and access&lt;/li&gt;
&lt;li&gt;Security considerations&lt;/li&gt;
&lt;li&gt;Assumptions and dependencies&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;SUMMARY.md&lt;/code&gt; is the short executive overview. This is the file that gets sent to the agent every iteration so it reorients fast without rereading the entire PRD. It contains an overall description of the project, the main features, the key user flows, and a short list of key requirements. Nothing more.&lt;/p&gt;
&lt;p&gt;The economics drive the split. Every iteration starts the agent with a fresh context window, and tokens in that window cost money and attention. You do not want to spend that budget reloading a 3000 word PRD on every pass when a tight summary reorients the agent just as well. Long PRD for depth, short summary for the working context on every iteration.&lt;/p&gt;
&lt;pre dir=&quot;ltr&quot;&gt;flowchart LR
  Reqs[&quot;Unstructured requirements&quot;] --&gt; Interview[&quot;prd-creator interview, plan mode&quot;]
  Interview --&gt; PRD[&quot;prd/PRD.md: goals, constraints, criteria&quot;]
  PRD --&gt; Summary[&quot;prd/SUMMARY.md: short overview, sent each iteration&quot;]
  PRD --&gt; Tasks[&quot;tasks.json and tasks/TASK-ID.json&quot;]
  Summary --&gt; Loop[&quot;ralph.sh loop, fresh context each iteration&quot;]
  Tasks --&gt; Loop
  Loop --&gt; Verify[&quot;Run tests, lint, types, screenshot&quot;]
  Verify --&gt; Commit[&quot;Commit, set passes true&quot;]&lt;/pre&gt;
&lt;p&gt;The PRD feeds two children. The summary is the version the loop reads constantly. The task list is the executable decomposition. Get the PRD right and both children inherit clear goals and criteria. Get it vague and both inherit the vagueness.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;draft-it-with-the-prd-creator-skill-in-plan-mode&quot;&gt;Draft it with the prd-creator skill in plan mode&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;You do not write all of this by hand. Ralph ships a &lt;code dir=&quot;auto&quot;&gt;prd-creator&lt;/code&gt; skill that turns unstructured requirements into a PRD plus a task list. Run it in plan mode, where the agent is read-only and focused on asking questions instead of writing code. Plan mode matters here: you want the agent interrogating your idea, not racing ahead to scaffold files before the spec exists.&lt;/p&gt;
&lt;p&gt;The flow is a conversation, not a one shot. The instinct most people have is to paste a paragraph and expect a finished plan. The skill instead pushes back. It interviews you to fill the gaps, asking clarifying questions one at a time, and it researches the competitive landscape before it commits anything to &lt;code dir=&quot;auto&quot;&gt;PRD.md&lt;/code&gt;. When a question can be answered by reading the codebase, it reads the codebase instead of asking you.&lt;/p&gt;
&lt;p&gt;A prompt to kick it off looks like plain language:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Use the prd-creator skill in plan mode. I want to build a link shortener&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;with accounts, custom slugs, and click analytics. Interview me, write the&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;PRD to .agent/prd/PRD.md and the summary to .agent/prd/SUMMARY.md, then&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;generate the task list in .agent/tasks.json.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;During the interview, the skill also verifies prerequisites and creates or updates &lt;code dir=&quot;auto&quot;&gt;.env.local&lt;/code&gt; with placeholder values only. It never writes a real secret to the PRD, the tasks, the logs, or &lt;code dir=&quot;auto&quot;&gt;.env.local&lt;/code&gt;. You fill the real values in by hand. This is the moment the spec records what credentials and access the project needs, so the agent is not discovering halfway through a 50 task run that it never had database access.&lt;/p&gt;
&lt;p&gt;When it finishes, you have &lt;code dir=&quot;auto&quot;&gt;PRD.md&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;SUMMARY.md&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;tasks.json&lt;/code&gt; with one &lt;code dir=&quot;auto&quot;&gt;TASK-{ID}.json&lt;/code&gt; spec per task. From there you run the loop:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@pageai/ralph-loop&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;./ralph.sh&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;You can amend later. When you want to add a feature or fix a bug mid-project, run the skill again to update the PRD and append tasks. The spec grows with the project instead of going stale the moment you start coding.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;write-acceptance-criteria-the-agent-can-verify&quot;&gt;Write acceptance criteria the agent can verify&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This is the part that separates a PRD an agent can build from a PRD that produces confident nonsense. An acceptance criterion is only useful if the agent can check it without you. The test is simple: can the agent confirm this by running something and reading the output? If not, rewrite it.&lt;/p&gt;
&lt;p&gt;Three rules make a criterion verifiable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Name the input and the expected output.&lt;/strong&gt; Vague: “the endpoint validates email.” Verifiable: “POST /api/register with email ‘not-an-email’ returns 400 and the body &lt;code dir=&quot;auto&quot;&gt;{ error: &apos;Please enter a valid email&apos; }&lt;/code&gt;.” Now the agent can send the request, read the status and body, and compare.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Point at an observable artifact.&lt;/strong&gt; “Passwords are secure” is unprovable. “The stored password starts with the bcrypt prefix $2b$ and never equals the plaintext value” can be confirmed by reading the row. Anchor the criterion to something the agent can inspect: a database row, a response header, a file on disk, a console exit code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Make it pass or fail, never partial.&lt;/strong&gt; A criterion that needs interpretation is a criterion the agent will interpret in its favor. “The UI looks clean” invites argument. “The submit button is disabled until both fields are non-empty” does not.&lt;/p&gt;
&lt;p&gt;In Ralph, the criteria do not float in the PRD. They land in the per-task spec files. Each &lt;code dir=&quot;auto&quot;&gt;TASK-{ID}.json&lt;/code&gt; carries an &lt;code dir=&quot;auto&quot;&gt;acceptanceCriteria&lt;/code&gt; array, and the tests that prove those criteria are steps inside the task, not separate tasks scheduled for later.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;TASK-3&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;title&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;POST /api/auth/register creates a new user account&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;category&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;api-endpoint&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;description&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Validate input, hash the password, store the user, return a success response.&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;acceptanceCriteria&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;POST with valid email and password returns 201 with the user id and email&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Invalid email format returns 400 with the error text Please enter a valid email&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Password shorter than 8 characters returns 400&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Duplicate email returns 409, not a generic 500&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;The stored password starts with $2b$ and is never the plaintext value&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;steps&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;step&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;description&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Add the register route handler&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;details&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Validate with a zod schema, hash with bcrypt, insert into users.&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;pass&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;step&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;description&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Write Vitest cases for every acceptance criterion&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;details&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Cover valid registration, invalid email, short password, duplicate email, and the stored hash prefix.&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;pass&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;dependencies&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;TASK-1&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;TASK-2&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;estimatedComplexity&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;medium&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Every line in &lt;code dir=&quot;auto&quot;&gt;acceptanceCriteria&lt;/code&gt; maps to something the verification stack can confirm. The loop assumes that stack: Playwright for end to end, Vitest for unit tests, TypeScript for types, ESLint for lint, Prettier for format. The repo mantra is blunt: if you didn’t test it, it doesn’t work. The agent runs the gate, and only after it passes does it flip &lt;code dir=&quot;auto&quot;&gt;passes&lt;/code&gt; to &lt;code dir=&quot;auto&quot;&gt;true&lt;/code&gt;, take a screenshot, and commit. Turning criteria into atomic, independently verifiable packets like this is its own discipline, covered in &lt;a href=&quot;https://ralphloop.sh/blog/break-prd-into-agent-tasks/&quot;&gt;breaking a PRD into atomic agent tasks&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;name-env-vars-libraries-and-user-flows-so-the-agent-does-not-guess&quot;&gt;Name env vars, libraries, and user flows so the agent does not guess&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The fastest way to get an agent to invent something wrong is to leave a decision implicit. Three categories cause the most trouble, and the PRD should pin all three.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Environment variables.&lt;/strong&gt; List every variable the project reads, with a one line note on what each is for. The &lt;code dir=&quot;auto&quot;&gt;prd-creator&lt;/code&gt; skill writes placeholders into &lt;code dir=&quot;auto&quot;&gt;.env.local&lt;/code&gt; during prerequisite verification, so the agent knows the keys exist without ever seeing a real secret. Without this, an agent guesses variable names, scatters them across files, and you spend the morning reconciling &lt;code dir=&quot;auto&quot;&gt;DATABASE_URL&lt;/code&gt; against &lt;code dir=&quot;auto&quot;&gt;DB_CONNECTION_STRING&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Libraries and the stack.&lt;/strong&gt; Say which framework, which ORM, which validation library, which test runner. If the project already uses zod, the PRD should say so, or the agent will reach for whatever it saw most recently in its training and add a second validation library next to your first. Naming the stack is also where you encode reuse: tell the agent to extend the existing auth module rather than write a parallel one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;User flows.&lt;/strong&gt; A flow is a sequence of steps with branches, and the branches are where agents guess. “Users can reset their password” hides a dozen decisions. Does the reset link expire? After how long? What happens on an expired link? Does requesting a reset for an unknown email reveal that the account does not exist? Write the flow as steps and edge cases, and each edge case becomes an acceptance criterion instead of a surprise in production.&lt;/p&gt;
&lt;p&gt;The pattern across all three: every decision you leave out is a decision the agent makes for you, silently, at the moment it is most expensive to change. The PRD is where you make those decisions while they are still cheap.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-short-worked-example&quot;&gt;A short worked example&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Take the link shortener from the prompt above and watch the three properties show up.&lt;/p&gt;
&lt;p&gt;The goal is concrete: “an authenticated user creates a short link with an optional custom slug and sees total clicks per link.” Not “build a great link tool.” The constraint section names the stack and fences the scope: custom domains and team accounts are out of scope for version one. The acceptance criteria get specific per feature. For slug generation: “a generated slug is 7 characters of base62” and “a collision retries up to 3 times before returning an error.” For the redirect: “GET /:slug on an unknown slug returns 404” and “a valid slug records exactly one click row and issues a 302 to the target URL.”&lt;/p&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;prd-creator&lt;/code&gt; interview is where these get extracted. Are slugs unique globally or per account? What happens on a collision? Do analytics count unique visitors or raw hits? Each answer becomes a line in the PRD, and the unanswered questions become the edge cases that would otherwise blow up the loop. By the time the PRD is approved, the ambiguity is gone, and the task list inherits criteria a machine can check. The loop then runs one task at a time, which is the rule that keeps a long run from drifting, explained in &lt;a href=&quot;https://ralphloop.sh/blog/one-task-per-iteration/&quot;&gt;one task per iteration&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The framing of phases here (specify intent, plan the approach, decompose into tasks, implement and verify) comes from &lt;a href=&quot;https://github.com/github/spec-kit&quot;&gt;GitHub Spec Kit&lt;/a&gt;, and the loop that runs it autonomously was popularized by Geoffrey Huntley in his &lt;a href=&quot;https://ghuntley.com/ralph/&quot;&gt;original Ralph writeup&lt;/a&gt;. The PRD is the artifact the first phase produces and every later phase consumes.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-to-go-next&quot;&gt;Where to go next&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you are writing the spec that drives a loop, read down through the spec-driven cluster:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ralphloop.sh/blog/spec-driven-development-with-ai/&quot;&gt;Spec-driven development with AI&lt;/a&gt; for the full Specify, Plan, Tasks, Implement workflow this PRD sits inside.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ralphloop.sh/blog/break-prd-into-agent-tasks/&quot;&gt;Breaking a PRD into atomic agent tasks&lt;/a&gt; for turning the PRD into independently verifiable packets.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ralphloop.sh/blog/one-task-per-iteration/&quot;&gt;One task per iteration&lt;/a&gt; for the rule that keeps long runs reliable.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the mechanics of the loop that reads your PRD on every pass, the fresh-context design, and where the technique came from, read &lt;a href=&quot;https://ralphloop.sh/blog/what-is-the-ralph-technique/&quot;&gt;what is the Ralph technique&lt;/a&gt;.&lt;/p&gt;
&lt;section aria-label=&quot;Frequently asked questions&quot;&gt; &lt;h2&gt;Frequently asked questions&lt;/h2&gt; &lt;div&gt; &lt;h3&gt;What makes a PRD buildable by an AI agent rather than a person?&lt;/h3&gt; &lt;p&gt;A buildable PRD states concrete goals, explicit constraints, and verifiable acceptance criteria. A person can fill gaps with judgment and ask a teammate, but an agent invents an answer whenever the spec is silent. So the agent PRD must name the stack, fence what is out of scope, and define each acceptance criterion as a condition a machine can confirm by running a command and reading the output.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;What is the difference between PRD.md and SUMMARY.md in Ralph?&lt;/h3&gt; &lt;p&gt;PRD.md is the full document with app overview, target audience, success metrics, core features and user flows, technical stack, prerequisites, security considerations, and assumptions. SUMMARY.md is a short executive overview with the main features, key user flows, and key requirements. The summary is what gets sent to the agent every iteration so it reorients fast without rereading the entire PRD.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;How do I write acceptance criteria an agent can verify?&lt;/h3&gt; &lt;p&gt;Name the input and the expected output, point at an observable artifact, and make every criterion pass or fail with no interpretation. For example, POST with an invalid email returns 400 with a specific error text, or the stored password starts with the bcrypt prefix and is never the plaintext value. The agent confirms each one with Playwright, Vitest, type checks, and lint before it marks the task done.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;How do I create the PRD without writing it all by hand?&lt;/h3&gt; &lt;p&gt;Use the prd-creator skill in plan mode. It interviews you one question at a time, researches the competitive landscape, verifies prerequisites, and writes placeholder values into .env.local without ever storing a real secret. It then writes PRD.md and SUMMARY.md and generates tasks.json with a prerequisite verification task first. You can run it again later to amend the PRD and append tasks.&lt;/p&gt; &lt;/div&gt;&lt;div&gt; &lt;h3&gt;Why does naming env vars, libraries, and user flows matter so much?&lt;/h3&gt; &lt;p&gt;Every decision left implicit is a decision the agent makes for you, silently, at the moment it is most expensive to change. If you do not name the validation library, the agent may add a second one. If you do not spell out the password reset flow, it guesses how expiry and unknown emails behave. Listing variables, the stack, and flows with their edge cases turns those guesses into acceptance criteria.&lt;/p&gt; &lt;/div&gt; &lt;/section&gt; 
&lt;aside aria-label=&quot;Get started with Ralph Loop&quot;&gt; &lt;h2&gt;Run your own Ralph loop&lt;/h2&gt; &lt;p&gt;Ralph is a hackable script you point at your project. Install it and let an agent work through your task list.&lt;/p&gt; &lt;pre&gt;&lt;code&gt;npx @pageai/ralph-loop&lt;/code&gt;&lt;/pre&gt; &lt;div&gt; &lt;a href=&quot;https://www.npmjs.com/package/@pageai/ralph-loop&quot;&gt;
Install from npm
&lt;/a&gt; &lt;a href=&quot;https://github.com/pageai-pro/ralph-loop&quot;&gt;Star on GitHub&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=3TL8Ez66I3o&quot;&gt;Watch the walkthrough&lt;/a&gt; &lt;/div&gt; &lt;/aside&gt;</content:encoded><category>spec-driven</category></item></channel></rss>