The Ralph Loop
The Ralph Loop is Chief's core execution model: a continuous cycle that drives the agent to complete user stories one by one. It's the engine that makes autonomous development possible.
Background Reading
For the motivation and philosophy behind this approach, read the blog post Ship Features in Your Sleep with Ralph Loops.
Multi-agent support
Chief supports multiple agent backends: Claude Code (default), Codex CLI, OpenCode CLI, Cursor CLI, and Gemini CLI. This page uses "the agent" to refer to whichever backend you've configured. See Configuration for setup details.
The Loop Visualized
Here's the complete Ralph Loop as a flowchart:
┌─────────────┐
│ Start Chief │
└──────┬──────┘
│
▼
┌─────────────┐
│ Show TUI │
└──────┬──────┘
│
▼
┌─────────────┐
│ Press 's' │◀─────────────────────────────────────┐
└──────┬──────┘ │
│ │
▼ │
┌─────────────┐ │
│ Read State │ │
└──────┬──────┘ │
│ │
▼ │
╔═════════════╗ all complete ┌────────────┐ │
║ Next Story? ║─────────────────────▶│ ✓ Done │ │
╚══════╤══════╝ └────────────┘ │
│ story found ▲ │
▼ │ │
┌─────────────┐ │ │
│Build Prompt │ │ │
└──────┬──────┘ │ │
│ │ │
▼ │ │
┌─────────────┐ │ │
│Invoke Agent │ │ │
└──────┬──────┘ │ │
│ │ │
▼ │ │
┌─────────────┐ <chief-done/> │ │
│Stream Output├────────────────────────────▶┘ │
└──────┬──────┘ │
│ session ends │
▼ │
╔═════════════╗ no ┌────────────┐ │
║ Max Iters? ║────────────────────▶│ Continue │───┘
╚══════╤══════╝ └────────────┘
│ yes
▼
┌─────────────┐
│ ✗ Stop │
└─────────────┘Before the Loop: Worktree Setup
Before the loop starts, Chief sets up the working environment. When you press s to start a PRD, the TUI shows a dialog offering to create an isolated worktree:
- Create branch — A new branch (e.g.,
chief/auth-system) is created from your default branch - Create worktree — A git worktree is set up at
.chief/worktrees/<prd-name>/ - Run setup — If a setup command is configured (e.g.,
npm install), it runs in the worktree
This setup happens once per PRD. The loop then runs entirely within the worktree directory, isolating all file changes and commits to that branch.
You can also skip worktree creation and run in the current directory if you prefer.
The dialog only appears while the question is open. Restarting a PRD that already has a branch or a worktree resumes it there without asking — a worktree is picked back up (running worktree.setup again unless worktree.setupOnReuse says otherwise), and a run that lives in your current checkout simply continues on its branch.
Step by Step
Each step in the loop has a specific purpose. Here's what happens in each one.
1. Read State
Chief reads all the files it needs to understand the current situation:
| File | What Chief Learns |
|---|---|
prd.md | Which stories are complete (**Status:** done), which are pending, and which is in progress |
progress.md | What happened in previous iterations: learnings, patterns, and context |
| Codebase files | Current state of the code (via the agent's file reading) |
This step ensures the agent always has fresh, accurate information about what's done and what's left to do.
2. Select Next Story
Chief picks the next story to work on by looking at prd.md:
- Find all stories without
**Status:** done - Sort by
**Priority:**(lowest number = highest priority), or document order if unset - Pick the first one
If a story has **Status:** in-progress, Chief continues with that story instead of starting a new one. This handles cases where the agent was interrupted mid-story.
3. Build Prompt
Chief constructs a prompt that tells the agent exactly what to do. The prompt includes:
- The user story: ID, title, description, and acceptance criteria
- Instructions: Read the PRD, implement the story, run checks, commit
- Progress context: The path to
progress.md— Chief inlines nothing from it; the agent reads the parts it needs (Codebase Patterns and the last entries) - Research guidance (Claude only): delegate broad codebase exploration to a subagent and read files with the Read tool, so exploring the repo doesn't drag the whole conversation through hundreds of shell turns
Here's a simplified version of what the agent receives:
## Your Task
1. Read the PRD at `.chief/prds/your-prd/prd.md`
2. Read `progress.md` selectively if it exists: Codebase Patterns plus the last
one or two entries — not the whole file
3. Implement the assigned user story
4. Run quality checks (typecheck, lint, test)
5. If checks pass, commit with message: `feat: <PRD Name>/<Story ID> - <Story Title>`
6. Output `<chief-done/>` when the story is complete
7. Append your progress to `progress.md`The prompt is embedded directly in Chief's code. There's no external template file to manage.
4. Invoke the Agent
Chief runs the agent CLI, passing the constructed prompt:
claude --dangerously-skip-permissions -p "<prompt>" --output-format stream-json --verboseThe flags tell the agent to:
- Skip permission prompts (Chief runs unattended)
- Run the constructed prompt non-interactively (
-p) - Output structured JSON for parsing, verbosely so Chief sees each event
(Exact flags vary by backend; this is the Claude invocation.)
The agent now has full control. It can read files, write code, run tests, and commit changes, all autonomously.
5. Stream & Parse Output
As the agent works, it produces a stream of JSON messages. Chief parses this stream in real-time using a streaming JSON parser. This is what allows the TUI to show live progress.
Here's what the output stream looks like:
┌─────────────────────────────────────────────────────────────┐
│ Agent Output Stream (stream-json format) │
├─────────────────────────────────────────────────────────────┤
│ {"type":"text","content":"Reading prd.md..."} │
│ {"type":"tool_use","name":"Read","input":{...}} │
│ {"type":"text","content":"Found story US-012..."} │
│ {"type":"tool_use","name":"Write","input":{...}} │
│ {"type":"text","content":"Running tests..."} │
│ {"type":"tool_use","name":"Bash","input":{...}} │
│ {"type":"text","content":"Story complete, committing..."} │
└─────────────────────────────────────────────────────────────┘Each message contains:
- type: What kind of output (text, tool_use, etc.)
- content: The actual output or tool details
Chief parses this stream to display progress in the TUI. When the agent's session ends, Chief checks if the story was completed (by reading the updated PRD) and continues the loop.
6. The Completion Signal
When the agent finishes working on a story, it outputs a special marker:
<chief-done/>This signal tells Chief that the current story is done. Before marking it done, Chief verifies that a matching commit (feat: <PRD Name>/<Story ID> - <Story Title>) actually landed. If it did, Chief marks the story as **Status:** done in prd.md and selects the next incomplete story. When no stories remain, the loop ends naturally.
Only the signal counts, not a mention of it: a tag inside backticks or a code block is a quote and is ignored, <chief-blocked> needs its closing tag and a reason between the two, and when the agent writes both signals, the one written last is its verdict. For Gemini, which streams its answer in pieces, the pieces of one answer are read together, so a signal split across them is still found and a <chief-blocked> waits for its reason.
If the agent emits <chief-done/> but no matching commit exists — it forgot to commit, a pre-commit hook rejected the change, or it crashed before committing — Chief does not trust the signal. The story is treated as a failed attempt (counting toward the per-story retry budget below) instead of being falsely marked done, so the uncommitted work isn't silently lost when the next fresh-context iteration moves on.
7. Continue the Loop
After each agent session ends, Chief:
- Increments the iteration counter
- Checks if max iterations is reached
- If not at limit, loops back to step 1 (Read State)
The next iteration starts fresh. The agent reads the updated prd.md, sees the completed story, and picks the next one. If all stories are done, Chief stops.
Per-Story Retries
Chief tracks attempts per story, not globally. If a story is attempted repeatedly without the agent emitting <chief-done/>, Chief eventually parks it instead of retrying forever.
After DefaultMaxAttemptsPerStory (5) failed attempts on the same story, Chief:
- Marks the story
**Status:** needs-reviewinprd.md(shown with a ◆ "Needs Review" marker in the TUI) - Moves on to the next unblocked story
A story the agent reports as blocked (<chief-blocked>reason</chief-blocked>, for things only a person can resolve) skips all of this: it is set to blocked with its reason on the first attempt, because another attempt would run into the same wall, and the stories depending on it wait for it.
This matters because stories are usually independent — one stuck story shouldn't block the other 200. The loop keeps making progress and only ends once no actionable stories remain (every story is either done or parked for review). Parked stories are left for a human to inspect: break them down, fix the PRD, or resolve whatever the agent got stuck on, then reset the status.
Rate Limits Are Not Crashes
An agent that stops because your account's usage window is full looks exactly like an agent that crashed: same non-zero exit, same empty stderr. Treated as a crash it burns all three retries within half a minute — against a window that may have hours left to run — and the run ends on a generic error.
Chief reads the rate limit reports the agent CLI emits alongside its normal output, so it can tell the two apart:
- While the window fills up, the header shows a chip (
Limit 90% · 20:00) so a run walking toward the wall says so while there is still time to react. - Once requests are rejected, the loop waits until the reported reset time (plus a minute for clock skew) and then runs the iteration again. The wait doesn't count against the crash retry budget, because nothing crashed. The run stays in
Running— it will continue on its own. - Stopping still works mid-wait, and after six waited-out windows in one run Chief gives up and reports the limit as an error.
Iteration Limits
Beyond per-story retries, Chief keeps a global iteration cap purely as a runaway backstop. When --max-iterations is not specified, it is calculated dynamically from the remaining stories and their per-story attempt budget, so it normally never fires before the per-story parking logic does. You can still set it explicitly with --max-iterations, or adjust it at runtime with +/- in the TUI.
| Scenario | What Happens |
|---|---|
| Story completes normally | Iteration counter goes up by 1, loop continues |
| Story takes multiple agent sessions | Each agent invocation is 1 iteration |
| Story fails 5× | Parked as needs-review, loop continues with other stories |
| Agent reports the story blocked | Set to blocked with its reason at once, loop continues with stories that don't depend on it |
| Global backstop reached | Chief stops and displays a message |
Post-Completion Actions
When a run finishes (all stories complete, or max iterations reached with committed work), Chief can automatically:
- Write a run summary — If
onComplete.summaryis enabled (on by default), Chief runs the agent once more to write a human-facing, timestampedsummary-<date>-<time>.mdnext to the PRD (what was built, how to test it, where the new functionality lives, open/parked follow-ups) and commits it so it rides along in any push/PR - Push the branch — If
onComplete.pushis enabled in.chief/config.yaml, Chief pushes the branch to origin - Create a pull request — If
onComplete.createPRis also enabled, Chief creates a PR via theghCLI with a title and body generated from the PRD - Send a desktop notification — If
onComplete.notifyis enabled (on by default), Chief pings you when the run finishes (macOSosascript, Linuxnotify-send). The same switch covers the endings nobody wants: a run capped bymaxIterationsand a run that died on an error it could not retry past both ping too, naming the reason — a dead run is the one you most want to hear about while you are away from the terminal
Summary, push, and PR only run when the branch has at least one commit. A run that completes with no committed work (for example, every story was parked for review) is not pushed, so Chief never creates an empty branch or PR.
The completion screen shows the progress of these actions with spinners, checkmarks, or error messages. On PR success, the PR URL is displayed and clickable.
What the run produced
Above the per-story list, the completion screen reports what the run did to the code, read from the run's own commits (StartRef..HEAD — an earlier run's work on the same branch is never counted):
Completed in 4h13m00s • $312.00
1h34m00s of that waiting for the usage window
+4,812 −387 lines in 47 files (31 new) · net +4,425
PHP 3,204 · Blade 1,120 · YAML 488
Tests: 1,840 lines in 12 files (38%)- Waiting — how much of the run went into sitting out a full usage window. Unlike the "Mac slept" line it is inside the total above, because the clock keeps running while the loop waits — it is what explains a four-hour run that holds two hours of work. Omitted when the run never hit a limit
- Lines and files — written and removed lines, how many of the touched files were new, and the net balance. The net appears only when something was removed, and it goes negative for a refactoring or consolidation run that ends smaller than it started. Binary files are skipped; a rename counts as a changed file, not a new one
- Languages — written lines per language, the three biggest first
- Tests — how much of the written code went into test files, recognised from paths (
_test.go,.test./.spec., atest_prefix, aTestsuffix, andtests/,spec/,__tests__/directories). The line is omitted when a run wrote no tests
Each line is dropped when it has nothing to report, so a run that committed nothing shows none of them.
If auto-actions aren't configured, the completion screen shows a hint to configure them via the Settings TUI (,).
You can also take manual actions from the completion screen:
m— Merge the branch locallyc— Clean up the worktreel— Switch to another PRDq— Quit Chief
Why "Ralph"?
The name comes from Ralph Wiggum loops, a pattern coined by Geoffrey Huntley. The idea: instead of fighting context window limits with one long session, you run the AI in a loop. Each iteration starts fresh but reads persisted state from the previous run.
Chief's implementation was inspired by snarktank/ralph, an early proof-of-concept that demonstrated the pattern in practice.
What's Next
- The .chief Directory: Where all this state lives
- PRD Format: How to write effective user stories
- CLI Reference: Running Chief with different options