A ralph harness for long-running AI coding tasks β a hardened ralph loop that drives your coding agent of choice (Claude Code, GitHub Copilot CLI, or OpenAI Codex CLI) across one or more repositories.
"I'm helping!" β Ralph Wiggum
Note
Active development. New features and polish ship regularly. All three providers β Claude Code,
GitHub Copilot CLI, and OpenAI Codex CLI β are supported; pick one per flow or mix them, in one command,
from a preset matrix of 20 presets across five families (standard, economic, strong-gate, fast,
frontier), each in mixed / claude-only / copilot-only / codex-only variants.
Upgrades are best-effort: install the latest version, redo your config, proceed.
See Upgrading and CHANGELOG.
The "Ralph" technique comes from Geoffrey Huntley's Ralph Wiggum as a software engineer:
point a coding agent at a task and run it in a loop until the work is done. The bare version
(while :; do cat PROMPT.md | claude; done) loops blindly β it re-runs the same prompt and hopes each pass lands.
ralphctl is a ralph harness around that idea: instead of blind repetition it runs a generator-evaluator loop, where one
pass writes the change and a second independent pass reviews it against the task spec before the loop advances. Same
loop, with a verification gate on every step. For the wider picture β what an agent harness is, and how the
plan β generate β evaluate β verify loop turns one-shot prompting into a repeatable workflow β see
AI Agent Harnesses: A Field Guide.
AI coding agents are powerful but lose context on long tasks, need babysitting when things break, and have no way to coordinate changes across multiple repositories. ralphctl wraps your chosen AI CLI β Claude Code, GitHub Copilot CLI, or OpenAI Codex CLI β in a structured harness that decomposes your work into dependency-ordered tasks, drives each one through a generator-evaluator loop that catches issues before moving on, and persists context across sessions so nothing gets lost.
You describe what to build. ralphctl handles the rest β or works alongside you, whichever you prefer.
npm install -g ralphctlNeeds Node.js β₯ 24 β
mise use node@24ornvm install 24.
Install one of the supported CLIs β Claude Code, GitHub Copilot CLI, or OpenAI Codex CLI β and authenticate it, then confirm ralphctl can see it:
ralphctl doctor # verifies your provider CLI is installed + authenticated β the #1 first-run failureWhen doctor is green, launch:
ralphctlThat's it. The TUI launches, walks you through registering a project, refining your first ticket, generating a task
plan, and kicking off implementation. Press + from the home screen to create a new sprint, press n to start a
flow (refine / plan / implement / readiness / β¦), or open the Sprints submenu and follow its on-screen hint to pick
or create a sprint. No commands to memorize.
Requirements: Node.js β₯ 24, Git, and one supported AI CLI in PATH
and authenticated.
Prefer the CLI for inspection + one-shot operations?
Interactive flows (refine / plan / ideate / implement / readiness / create sprint) are TUI-only. The CLI covers inspection and one-shot operations:
# Inspect projects + sprints
ralphctl project list
ralphctl sprint list
ralphctl sprint show <sprint-id>
ralphctl sprint progress <sprint-id>
# Add / inspect tickets
ralphctl ticket add --title "<title>"
ralphctl ticket list
# Manage sprint state
ralphctl sprint activate <sprint-id>
ralphctl sprint close <sprint-id> # review β done
ralphctl sprint remove <sprint-id>
# Open a PR for the sprint branch
ralphctl create-pr --sprint <sprint-id>
# Export sprint artifacts
ralphctl export-requirements --sprint <id> --output <path>
ralphctl export-context --sprint <id> --project <id> --output <path>
# Settings
ralphctl settings show
ralphctl settings apply-preset claude-only # or mixed / copilot-only / codex-only / *-economic / *-strong-gate / *-fast / *-frontier
ralphctl settings set ai.implement.generator.provider claude-code
ralphctl settings set ai.implement.generator.model <model-id>
ralphctl settings set ai.implement.generator.effort high
ralphctl settings set ai.implement.evaluator.provider openai-codex
ralphctl settings set ai.implement.evaluator.model <model-id> You describe what to build ralphctl drives it to done
βββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββ ββββββββββββ ββββββββββ ββββββββ βββββββββββββ ββββββββββ
β Create βββ>β Add ββββββββ>β Refine βββ>β Plan βββ>β Implement βββ>β Review βββ> done
β Sprint β β Tickets β β (WHAT) β β(HOW) β β Loop β β Loop β
ββββββββββββ ββββββββββββ ββββββββββ ββββββββ βββββββββββββ ββββββββββ
β β β β
AI clarifies AI builds AI implements you steer
requirements the task + AI reviews revisions,
with you graph each task close to done
Refine is implementation-agnostic: the AI clarifies requirements with you, ticket by ticket, and flips each one from
pending to approved. Plan requires every ticket approved β the AI explores the affected repos and generates a
dependency-ordered task graph. Implement drives those tasks in dependency order through a generator-evaluator cycle:
a second AI pass reviews each task against its spec before the harness marks it done and moves on. Independent tasks in
the same dependency wave can run in parallel (opt-in) when you want a sprint to finish faster. Review closes the
loop β once every task lands, the sprint enters review and you run human-steered feedback rounds: you flag what's off,
the AI revises, and the sprint flips to done when you're satisfied. Opening a PR (ralphctl create-pr) is separate
and optional.
Key properties:
- Dependency-ordered execution β tasks run in topological order; no task starts until its blockers are done.
Opt-in parallelism (
concurrency.maxParallelTasks> 1) runs independent tasks within a dependency wave concurrently, each in its own git worktree folded onto one branch β default stays serial - Generator-evaluator cycle β an independent AI reviewer checks each task; if it fails, the generator gets the
critique and iterates (up to
harness.maxAttemptstries before the task is flaggedblocked) - Context persistence β sprint state, branch, progress history, and per-task context survive across sessions; interrupted runs resume automatically
- Multi-repo support β one sprint can span several repositories with per-repo setup and verify scripts
For the full architectural picture see .claude/docs/ARCHITECTURE.md and
.claude/docs/REQUIREMENTS.md.
ralphctl drives three AI coding CLIs. Choose one per flow β or mix them, say plan with one and implement with another β through a preset or per-row settings. All three are supported and in everyday use.
| Provider | CLI | Headless permission mapping | Native context file |
|---|---|---|---|
Claude Code (claude-code) |
claude |
--permission-mode bypassPermissions + per-tool deny list |
CLAUDE.md at repo root |
GitHub Copilot CLI (github-copilot) |
copilot |
--autopilot --max-autopilot-continues=200 + --allow-all (per-tool deny list when read-only) |
.github/copilot-instructions.md |
OpenAI Codex CLI (openai-codex) |
codex |
-s workspace-write (topology-scoped) |
AGENTS.md |
Claude Code has the most end-to-end mileage inside the harness β it's the most battle-tested of the three β but Copilot
and Codex run every flow and are supported first-class. Two small differences worth knowing: bundled skill injection
and bodyFile forensic artifacts currently no-op on Copilot and Codex, and Codex's sandbox has only two modes
(read-only / workspace-write), so path scope (cwd + --add-dir) is its fine-grained safety envelope rather than a
per-tool deny list. Parallel execution is provider-agnostic β it works with whichever provider each implement role is
configured to use. Hit a rough edge with any provider? Please open an
issue.
One-shot configuration for any provider: ralphctl settings apply-preset <name> where <name> is one of
20 presets across five families β standard, economic, strong-gate, fast, and frontier, each in
mixed / claude-only / copilot-only / codex-only variants.
- Break big tickets into small tasks β dependency-ordered so they execute in the right sequence
- Catch mistakes before they compound β independent AI review after each task, iterating until quality passes or budget is exhausted
- Coordinate across repositories β one sprint can span multiple repos with automatic dependency tracking
- Finish sprints faster (opt-in) β run independent tasks within a dependency wave in parallel, each in its own git worktree, folded back onto one sprint branch (still one PR); default stays serial, zero change
- Branch per sprint β optional shared branch across every affected repo;
ralphctl create-pr --sprint <id>opens a PR / MR viaghorglabwhen you're done - Recover from rate limits β exponential backoff and session resume keep the in-flight task's full context when the provider restarts
- Separate the what from the how β AI clarifies requirements first (Refine), then generates the implementation plan (Plan), with human approval gates between
- Pick up where you left off β full state persistence; interrupted Implement runs resume in-progress tasks first β the crashed attempt is settled as aborted (kept in history) and a fresh attempt opens automatically
- Pair or let it run β work alongside your AI agent interactively, or let it execute unattended
- Zero-memorization start β run
ralphctlwith no args for a guided menu
Configure via the TUI Settings view or one-shot CLI commands.
Quickest path β apply a preset. Presets auto-seed from your detected CLIs on first run; override later with
apply-preset.
All 20 presets across five families
# Standard β flagship model per flow
ralphctl settings apply-preset mixed # best-fit provider per flow
ralphctl settings apply-preset claude-only # every flow on Claude Code
ralphctl settings apply-preset copilot-only # every flow on GitHub Copilot CLI
ralphctl settings apply-preset codex-only # every flow on OpenAI Codex CLI
# Economic β implement starts one tier below flagship; escalation ladder climbs only on plateau
ralphctl settings apply-preset mixed-economic
ralphctl settings apply-preset claude-economic
ralphctl settings apply-preset copilot-economic
ralphctl settings apply-preset codex-economic
# Strong-gate β cheap generator, permanently-flagship evaluator gate
ralphctl settings apply-preset mixed-strong-gate
ralphctl settings apply-preset claude-strong-gate
ralphctl settings apply-preset copilot-strong-gate
ralphctl settings apply-preset codex-strong-gate
# Fast β cheapest viable tier at low effort; plateau settles rather than escalating (escalateOnPlateau=false)
ralphctl settings apply-preset mixed-fast
ralphctl settings apply-preset claude-fast
ralphctl settings apply-preset copilot-fast
ralphctl settings apply-preset codex-fast
# Frontier β flagship everywhere at max effort
ralphctl settings apply-preset mixed-frontier
ralphctl settings apply-preset claude-frontier
ralphctl settings apply-preset copilot-frontier
ralphctl settings apply-preset codex-frontierTwenty presets across five families ship, all equally first-class β none is marked default. Applying a
preset stamps the entire ai section plus harness.escalateOnPlateau in one transaction (fast stamps it
false so a plateau settles; all others stamp it true). On a fresh install the welcome view silently
auto-seeds a preset based on which provider CLIs it detects on PATH.
Per-flow settings. Each flow carries its own {provider, model, effort?} row: refine, plan, readiness,
ideate, and createPr. The implement flow instead splits into a nested generator / evaluator pair
(ai.implement.generator.* and ai.implement.evaluator.*), each its own {provider, model, effort?} row. Edit
individual keys with:
ralphctl settings set ai.implement.generator.provider claude-code
ralphctl settings set ai.implement.generator.model <model-id>
ralphctl settings set ai.implement.generator.effort high
ralphctl settings set ai.plan.provider github-copilot
ralphctl settings set ai.plan.model <model-id>The selected provider's CLI must be in your PATH and authenticated. Every AI-spawning flow probes its
row's CLI at launch and exits with a clear error if the binary is missing.
Tune the generator-evaluator loop (under harness):
ralphctl settings set harness.maxAttempts 2 # Cap fix attempts per task (1β10, default 3)
ralphctl settings set harness.maxTurns 8 # Generator-evaluator turns per attempt (1β10)
ralphctl settings set harness.rateLimitRetries 3 # Adapter-side 429 retries (0β10)Run tasks in parallel (optional β default is serial):
ralphctl settings set concurrency.maxParallelTasks 3 # 1β5; 1 = serial (default), >1 = parallel git worktreesWhen > 1, independent tasks within a dependency wave run concurrently β each in its own git worktree, with its own
setupScript run, folded back onto the single sprint branch (still one PR per sprint). A task whose worktree setup
fails is blocked on its own without stopping its siblings; if two same-wave tasks edit the same file, the second is
blocked at fold time and a relaunch retries it. Dependencies are always respected β only independent tasks overlap.
All state lives in ~/.ralphctl/ by default (settings under config/, sprints + projects under data/, advisory locks
under state/). Override the root with:
export RALPHCTL_HOME="/path/to/custom/dir"| Variable | Default | Purpose |
|---|---|---|
RALPHCTL_HOME |
~/.ralphctl/ |
Override application root (data + config + state) |
RALPHCTL_SKIP_LEGACY_CHECK |
unset | Bypass the v0.6.x legacy-layout detector at boot |
RALPHCTL_NO_TUI |
unset | Suppress implicit interactive prompts in implement |
NO_COLOR |
unset | Suppress ANSI colors |
CI |
auto-detected | Suppress implicit interactive prompts in implement |
Log verbosity is settings.logging.level (silent / debug / info / warn / error, default info), set via
ralphctl settings set logging.level <level> or the TUI Settings view β not an environment variable.
Install the latest version, redo your config, proceed. Only the latest release is supported β there's no backporting, and upgrading is the answer to most "is this fixed?" questions.
npm install -g ralphctl@latest
ralphctl settings apply-preset <name> # if your settings need a reset
ralphctl # TUI prompts you to re-register projects if neededIf your ~/.ralphctl/ data from an older release doesn't load cleanly, back
it up and start fresh:
mv ~/.ralphctl ~/.ralphctl.bakThe backup keeps your ticket bodies, plan output, and progress notes around for reference. See MIGRATION.md if you're crossing a major boundary (e.g. 0.6.x β 0.7.x) and want the longer story.
CLI Command Reference
The CLI surface is deliberately smaller than v0.6.x β interactive flows (refine / plan / ideate / implement / readiness / create sprint) stay TUI-only by design. The CLI exposes inspection + one-shot operations.
| Command | Description |
|---|---|
ralphctl |
Interactive TUI (primary surface) |
ralphctl doctor |
Check environment health |
ralphctl settings show |
Print current settings |
ralphctl settings set <key> <value> |
Set a single settings key |
ralphctl settings apply-preset <name> |
Stamp the entire ai section β 20 presets across five families: standard / economic / strong-gate / fast / frontier, each in mixed / *-only / *-economic / *-strong-gate / *-fast / *-frontier variants |
ralphctl completion <shell> |
Print shell tab-completion script |
| Command | Description |
|---|---|
ralphctl project list |
List registered projects |
ralphctl project show <id> |
Show one project (incl. repositories) |
ralphctl project remove <id> |
Delete a project registration |
ralphctl sprint list |
List all sprints |
ralphctl sprint show <id> |
Show one sprint (tickets, status, branch) |
ralphctl sprint progress <id> |
Sprint progress with blocker diagnostics |
ralphctl sprint set-current <id> |
Switch the current sprint pointer |
ralphctl ticket add --title <title> |
Add a ticket to the current sprint (--sprint, --description, --link optional) |
ralphctl ticket list / show <id> |
Inspect tickets |
ralphctl ticket remove <id> |
Remove a ticket from a draft sprint |
ralphctl task list / show <id> |
Inspect tasks (planning generates them) |
ralphctl task unblock <id> |
Reset a blocked task to todo |
| Command | Description |
|---|---|
ralphctl sprint activate <id> |
Flip a draft sprint to active |
ralphctl sprint close <id> |
Transition review β done |
ralphctl sprint remove <id> |
Delete a sprint permanently |
| Command | Description |
|---|---|
ralphctl export-requirements [--sprint <id>] --output <path> |
Render approved-ticket requirements to markdown |
ralphctl export-context [--sprint <id>] [--project <id>] --output <path> |
Render harness context (sprint + project + tasks) to markdown |
ralphctl create-pr --sprint <id> [--base <branch>] [--draft] |
Open a PR/MR via gh or glab, persist the URL on the sprint |
| Command | Description |
|---|---|
ralphctl runs list [--flow <name>] |
List per-run forensic artifacts grouped by flow |
ralphctl runs prune [--older-than 7d] [--keep-last <n>] [--flow <name>] [--dry-run] [-y] |
Delete per-run forensic artifacts |
Run ralphctl <command> --help for flag-level detail.
| Resource | Description |
|---|---|
| Architecture | Data models, harness loop, file storage, error reference |
| Adding a provider | Extension guide: wire a new AI CLI into the harness |
| Requirements | Acceptance criteria and feature checklist |
| Contributing | Dev setup, code style, PR process |
| Migration | Per-version upgrade context for big version jumps |
| Changelog | Version history |
From the author (Lukas Grigis): Building ralphctl (backstory) | From task CLI to ralph harness (evaluator deep-dive) | The harness era caught up (the field converges on the harness bet)
Further reading: AI Agent Harnesses: A Field Guide β Lukas Grigis | Harness Engineering for Coding Agent Users β Martin Fowler (April 2026) | Harness Design for Long-Running Application Development β Anthropic Engineering
git clone https://github.com/lukas-grigis/ralphctl.git
cd ralphctl
pnpm install
pnpm dev --help # Run CLI in dev mode (tsx, no build needed)
pnpm build # Compile for npm distribution (tsup)
pnpm typecheck # Type check
pnpm test # Run tests
pnpm lint # LintContributions are welcome! Please open an issue first to discuss what you'd like to change.
See CONTRIBUTING.md for the full guide β dev setup, code style, PR process, and releasing.
This project follows the Contributor Covenant code of conduct.
To report a vulnerability, use GitHub's private reporting. See SECURITY.md for details.
MIT β see LICENSE for details.
