12. Ralph loop — the road from PRD to release, walked without the operator
An autonomous loop walking from PRD to release gates on its own: one verified commit per iteration, plus a DECISIONS queue for human-only choices.
Part 10 — PRD said what to build. Part 11 — Release gates said when it is built.
This post is about what fills the gap between those two — without the operator at the keyboard.
Ralph. The autonomous loop that takes a PRD with gates and walks the road on its own, one verified commit at a time. It works in the background. It pushes when it can. It queues decisions when it cannot. The operator wakes up, runs one slash command, makes a few decisions, and goes back to whatever else they were doing. The system did the work.
I think what makes Ralph feel different from automation I used before is what it means for the operator's relationship to the work. Not because it does more — a cron job can also do things while you sleep — but rather because Ralph has an opinion about what belongs in the DECISIONS queue and what doesn't. Initially the loop felt like "faster CI." What it actually turns out to be is a system that knows what a human is for.
This is the third post in the work-flow extension of the series, and the one where everything before it compounds. Persona narrows the decision space. Memory keeps the why. Skills compress the verbs. Hooks fire what would be forgotten. Multi-agent splits the load. Verification locks "done." Token economy protects signal. PRDs hold intent. Release gates enforce arrival. Ralph runs the whole arrangement, on a clock.
0. The premise — autonomy is not "trust the agent more"
A bad version of autonomy is "let the agent do whatever it wants and hope." That collapses for the same reason the PRD-less workflow in Part 10 collapses — the operator's mind has nowhere durable to live, and the agent ends up improvising.
A working version of autonomy is the opposite. The operator writes the PRD. The operator writes the gates. The agent's discretion is sharply bounded — it can do anything that does not change what the system is, and it cannot do anything that does. Inside that bound, the agent runs without supervision. Outside it, the agent stops and waits.
Autonomy works when the mind is fixed in code and the labor is what gets delegated.
Ralph is the harness that makes this distinction operationally real.
1. What ralph actually is
Ralph is two scripts and a folder.
ralph-code.sh— runs one phase at a time. Reads a PRD-N. Picks the next[ ]item. Implements it. Runs verify. Commits if verify passes. Repeats.ralph-orchestrator.sh— runs many phases in a row. Reads a Master Plan. For each phase, callsralph-code.sh. Between phases, checks the gate. Either passes the gate (Auto), or stops and queues a decision (Manual).ralph-loop/— a folder per project that holds the PRDs, the master plan, the progress log, the blockers list, and — most importantly — the DECISIONS queue.
The child process is not a long-lived agent. It is a fresh session per iteration. Each iteration is one item: implement, verify, commit. The next iteration starts from a clean slate, reading the PRD again to find the next [ ]. Drift cannot accumulate across iterations because there is no shared context to drift in.
The parent — the orchestrator — does not implement anything. Its only job is gate enforcement. After each phase, it asks "did the gate pass?" If yes, advance. If no, stop and write to the queue.
2. The Master Plan — PRD and gates fused
A Master Plan is the document Ralph reads. It is shaped like a PRD with the gates inlined per phase:
Approved-By: lead, 2026-05-18
Phase P1 — Privacy and compliance scrub
PRD: ralph-loop/PRD-P1.md
Auto-Gate: pnpm verify:privacy && pnpm build && pnpm lint
Manual-Gate: lead approves privacy policy text
Items: 12
Phase P2 — Asset SHA-256 enforcement
PRD: ralph-loop/PRD-P2.md
Auto-Gate: pnpm verify:assets && pnpm build
Manual-Gate: —
Items: 7
Phase P3 — i18n locale expansion (4 new locales)
PRD: ralph-loop/PRD-P3.md
Auto-Gate: pnpm build && pnpm verify:i18n
Manual-Gate: lead approves machine-translated content for each locale
Items: 24
...
Three properties matter.
2.1 Approved-By: is a required line
The orchestrator refuses to start without it. If the lead has not signed the Master Plan, Ralph does not run.
2.2 Each phase has its PRD pre-generated
Not "the orchestrator will generate PRD-P3 when it gets there" — PRD-P3.md exists at start time, written by Alice from the Master Plan. Pre-generation is a drift safety net: an in-flight PRD-P3 has no operator review.
2.3 Auto-Gate and Manual-Gate are named per phase
Auto is a shell command. Manual is a named approver and what they approve. No phase advances without its gate passing.
3. How the loop actually runs
A simplified trace of one iteration of ralph-code.sh:
1. cd to worktree
2. read PRD-N.md
3. find the first item marked [ ]
4. if no [ ] items remain → exit (ALL DONE)
5. start a fresh Claude Code session
6. system prompt: "implement exactly this one item, then stop"
7. child writes code
8. child runs verify (the script named in the PRD)
9. if verify passes:
- child commits with message including item ID
- parent re-runs verify (defense in depth)
- if parent verify also passes → mark [ ] as [x], continue
- if parent verify fails → revert, write to BLOCKERS, continue with next item
10. if verify fails:
- log to BLOCKERS
- retry once
- if still failing → mark as blocked, continue with next item
11. if BLOCKERS reaches threshold (default 3 per phase) → abort
12. loop back to step 2
Key properties of this loop, and why each one is shaped the way it is.
3.1 One item per iteration
The child cannot do "while I'm in here, let me also fix...". The system prompt explicitly limits it to one PRD row. This is what stops scope drift dead.
3.2 Fresh session per iteration
No carried context between items. The next item reads the PRD fresh. If the previous item left subtle bad assumptions, they do not propagate.
3.3 Parent re-verifies
The child's verify is one check. The parent's verify is the same script, run again outside the child's session, on the committed state. If they disagree, the child's claim is wrong and the commit is reverted. This is how the system survives a hallucinated "verify passed" line.
3.4 Blockers cap
After N blockers in one phase, the phase aborts. The operator inspects what kept blocking and either fixes the root cause or rewrites the PRD. Ralph does not keep running into the same wall forever.
4. The orchestrator — phase boundaries are where humans live
ralph-code.sh handles items inside a phase. ralph-orchestrator.sh handles the transition between phases.
After a phase's ralph-code.sh exits ALL DONE:
1. parent runs the phase's Auto-Gate command
2. if Auto-Gate fails:
- log to DECISIONS as "Auto-Gate failed, see verify output"
- stop the orchestrator
3. if Auto-Gate passes and there's no Manual-Gate:
- mark phase [x] in ORCHESTRATOR-STATE.md
- advance to next phase
4. if Auto-Gate passes and there IS a Manual-Gate:
- log to DECISIONS as "Manual-Gate awaits: <approver> approves <thing>"
- stop the orchestrator
5. every 5 phases (regardless of state):
- force a DECISIONS checkpoint
- stop the orchestrator
- operator runs /btw to confirm direction
Three intentional stops, each one corresponding to a different human-only responsibility.
4.1 Auto-Gate failed
This is a technical event, but the interpretation is human. Was the verify script wrong? Was the spec wrong? Did the world change? The operator decides. The orchestrator does not retry forever.
4.2 Manual-Gate reached
This is the explicit handoff. The lead approves the privacy policy, or the seed content, or the OAuth scope choice. There is no automating this row, so the orchestrator stops at the door.
4.3 Forced checkpoint
Every five phases, even if nothing has gone wrong, the orchestrator stops and asks for direction. This is a drift safety net. The single hardest failure mode of long-running autonomy is "everything worked but went the wrong way," and the only known countermeasure is periodic human review at fixed cadence.
5. The DECISIONS queue — quarantining "I need a human"
Three files accumulate in ralph-loop/:
| File | What it holds | Trigger | How it gets processed |
|---|---|---|---|
PROGRESS.md | One line per iteration, success or fail | every iter | append-only, audit-only |
BLOCKERS.md | Technical failures the agent could not resolve | verify fails twice, etc. | counts toward abort threshold |
DECISIONS.md | Things only a human can decide | option choices, security/DB, dep adds | the operator processes with /btw |
The discipline that makes this work is in how a child classifies a situation it cannot resolve.
child encounters a situation it does not know how to handle:
├── "I think I know what to do, but the spec is silent" → BLOCKER
├── "There are two reasonable choices and only the lead can pick" → DECISION
└── "This requires a new dependency / security review / public API change" → DECISION
In doubt: classify as DECISION, not BLOCKER. Wrong-classifying a decision as a blocker just wastes iterations. Wrong-classifying a blocker as a decision wakes the operator unnecessarily, but does not corrupt the codebase. The asymmetry is the point.
A PRD item with status [?] (decision pending) is skipped by Ralph. The loop keeps running on [ ] items. When everything that can be done is done, only [?] items remain — and the loop naturally exits, signaling "operator's turn."
6. /btw — the operator's side of the loop
On the operator's side, the entire interaction is one slash command.
/btw
What it does:
- Reads
ralph-loop/DECISIONS.mdin every known project - Sorts pending decisions by priority (security/DB +10 weight, time-waiting weight)
- Presents them to the operator as a batched question with options
- For each answer:
- Moves the decision from "pending" to "resolved" with the answer and a timestamp
- In the PRD, flips
[?]back to[ ]and updates the row's content per the decision - Commits with message
[Decision] D-NNN R0N: <summary>
- Asks the operator whether to restart Ralph on affected phases
The operator's mental model is — I am answering questions, not running work. The orchestrator handled the actual work. The orchestrator hit a thing only I can decide. I decide. The orchestrator continues.
A real-shape interaction:
operator (morning): /btw
Alice: 3 decisions pending across 2 projects.
Project A (waiting 14 h):
D-007 Phase P5 — Use Resend or Postmark for transactional email?
Spec is silent. Pricing favors Resend, deliverability favors Postmark.
Project A (waiting 14 h):
D-008 Phase P6 — Add `zod` dependency for runtime validation?
Spec did not anticipate a runtime validator. Two alternatives:
(a) zod (~14 kB)
(b) hand-rolled validators
Project B (waiting 2 h):
D-002 Phase P2 — Manual-Gate: lead approves machine-translated content?
24 files, 4 locales × 6 categories. Sample diff attached.
operator: D-007: Resend. D-008: zod. D-002: approved.
Alice: Recorded all three. Project A Phase P5 resumed.
Project B Phase P2 advanced to P3. Project B continuing.
That conversation is the entire interaction. The operator did not write code. The operator did not run a build. The operator made three decisions in under a minute. Ralph did the rest before the conversation and continues to do the rest after.
7. When ralph is the wrong tool
Ralph is not a universal hammer. It is good at one shape of work and bad at another.
7.1 Good fits
- Many similar items, each verifiable. (Apply a polish pass to 36 MDX files. Generate boilerplate for 48 lens variants. Refactor 24 callsites of a renamed function.)
- Generated tests or docs the verify script can check.
- Lint or format or type-strict batches that move a large surface in one direction.
7.2 Bad fits
- Architecture decisions. (The whole point is that decisions go to DECISIONS, but if every item is a decision, Ralph does no work and just produces queue noise.)
- Security or DB schema changes. Both touch invariants Ralph cannot fully verify, and both have failure modes where "passed" is dangerously wrong.
- Public-API changes. The verify script can confirm the local change works but cannot confirm downstream consumers still do.
- Anything with a new dependency. Always a decision.
When in doubt, the heuristic: would I be comfortable with this commit landing while I am asleep? If yes, Ralph. If no, do it in a normal session with the operator present.
8. Safety rails — what keeps autonomy from becoming a fire
Several rails compound to keep Ralph safe to leave running.
Worktree isolation. Ralph runs in a git worktree, never on main. The worktree is on a dedicated branch (ralph/master-plan or ralph/{phase}). The operator can always inspect and discard the entire branch.
Approved-By line. The orchestrator refuses to start a Master Plan without Approved-By: lead, <date>. The operator's signature is mechanically enforced — not "the lead said okay in chat," but a string in the file the orchestrator parses.
Verify is the only path to commit. The child cannot commit without the verify script exiting 0. The parent re-runs verify on the committed state. Two independent passes of the same gate.
Bounded scope per iteration. The child's system prompt says "implement exactly this one PRD row, then stop." Scope creep at the iteration level is mechanically blocked.
Phase abort thresholds. After N blockers in one phase, the phase aborts. After M blockers across the whole plan, the orchestrator aborts. The system quits when the road becomes unrecognizable, instead of trying to power through.
Forced human checkpoints. Every 5 phases, the orchestrator stops and asks. Even if nothing has visibly gone wrong, the operator confirms direction. Long unsupervised runs without human review are the most dangerous mode; this rail forecloses it.
Decision quarantine. Things only the operator can decide are routed to DECISIONS and skipped by Ralph. The agent never guesses on a question that belongs to a human.
Cost cap. Cost monitoring is the operator's responsibility (an external tool like ccusage). A phase that goes unexpectedly expensive is also a signal that something has drifted and is worth a check.
9. One principle
The whole shape of Ralph collapses to one line.
"Anything the operator's mind would treat as routine, let the system walk on its own. Anything the operator's mind would treat as a decision, queue and wait."
And that line is also where this three-post extension ends — and where the series itself comes to rest.
Part 1 — Why Alice asked: how do I accumulate my mind so AI does not lose it?
The first nine posts answered: build a persona that draws mind's boundary, a memory that holds it across sessions, skills that compress its verbs, hooks that fire what would be forgotten, multi-agent to keep one context from carrying it all, verification to lock "done," token economy to protect the signal slots, and session protocols so every day lights up and shuts down in the same place.
The last three posts answered: write a PRD so intent outlives the session. Write release gates so "done" becomes a fact. Run a loop so the road between intent and arrival walks itself while the operator handles only the decisions that are theirs to make.
When all twelve pieces are in place, the operator stops being the person who does the work and becomes the person who decides what work is worth doing. The mind — the original, irreducible thing from Part 1 — is finally where it should be: at the top of the stack, deciding, while everything below it carries the labor.
That is the version of working with AI I have spent the past months building toward. The tool may be Claude, may be Codex, may be whatever comes next. The twelve decisions above do not depend on the tool. They wait for anyone who decides to build their own collaborator instead of using one.
If this series shortened the time to those decisions even a little — the operator's intent landed where it was meant to.