ralph
Health Pass
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Community trust — 25 GitHub stars
Code Warn
- fs module — File system access in .github/workflows/publish-npm.yml
- process.env — Environment variable access in .github/workflows/release-please.yml
Permissions Pass
- Permissions — No dangerous permissions requested
No AI report is available for this listing yet.
Autonomous coding-agent loop. Runs Claude Code or Codex against your repo in a Docker sandbox, iterating implementer → reviewer until the plan or GitHub issues are done. AFK-friendly: detach, retries, notifications.
Ralph — Autonomous Coding-Agent Loop
Ralph drives Claude Code by default, or Codex when selected with--agent codex, against a target repository in an iterating implementer →
reviewer pipeline isolated inside a custom Docker image.
⚠️ Security: Ralph runs the selected agent without interactive approval inside the sandbox (
--permission-mode bypassPermissionsfor Claude;--dangerously-bypass-approvals-and-sandboxfor Codex) and, by default, bind-mounts the host Docker socket — granting root-equivalent access to the host Docker daemon. Point it only at repositories, plans, and GitHub issues you trust. Disable the socket mount withRALPH_DOCKER_SOCK=0. See SECURITY.md for the full threat model.
New here? Start with QUICKSTART.md (zero-to-first-loop). Hacking on Ralph itself → CONTRIBUTING.md. Internals → docs/ARCHITECTURE.md. Background / design walkthrough → The Ralph AFK Stack, Explained (Substack).
@daonhan/ralph-core— library: iteration loop, docker runner, template renderer, stage registry. Importable from any Node project.@daonhan/ralph— CLI: exposesralph-afkandralph-ghafkbin entries. Depends on@daonhan/ralph-core.
Two AFK entry points (both installed globally after npm i -g @daonhan/ralph):
ralph-afk— plan/PRD-driven loop. Hand it a plan + PRD string; iterates until the agent emits the sentinel<promise>NO MORE TASKS</promise>.ralph-ghafk— GitHub-issue-driven loop. Pulls open issues withgh issue listand lets the agent pick the next AFK task.
Convenience shims live at apps/cli/scripts/afk.sh and apps/cli/scripts/ghafk.sh — thin wrappers that fall back to npx @daonhan/ralph if not installed.
Agent playbooks: packages/core/templates/prompt.md (for ralph-afk) and packages/core/templates/ghprompt.md (for ralph-ghafk). Reviewer instructions: packages/core/templates/review.md. All three ship inside @daonhan/ralph-core.
Architecture (AFK loops)
ralph-afk / ralph-ghafk (bin entries from @daonhan/ralph, on PATH after `npm i -g`)
│
▼
@daonhan/ralph (CLI, apps/cli) bin: ralph-afk, ralph-ghafk; scripts: afk.sh, ghafk.sh shims
│ imports
▼
@daonhan/ralph-core (packages/core)
├── runAfk / runGhAfk (env-driven entry: argv → runLoop)
├── runLoop (drives stage chain per iteration; checks sentinel)
├── render (renderer: @include / @spill / !? / !`cmd` / {{ INPUTS }})
├── stages (stage registry: implementer, ghafkImplementer, reviewer)
└── runner (docker run → NDJSON stream → live print → final result)
│
▼
docker run ralph-sandbox <selected-agent> …
Each iteration runs the stage chain [implementer, reviewer]. The implementer is the "gate": if it emits <promise>NO MORE TASKS</promise>, the loop exits before the reviewer runs.
Prompt templates expand five tag forms before each stage runs, in order — @include: (inline a file, no shell), @spill[?]: (run a command, write its output to a side file the agent Reads), !?`cmd|||fallback` (try-shell), !`cmd` (host shell), and {{ INPUTS }} (the entry CLI's input arg — the plan/PRD string for ralph-afk, empty for ralph-ghafk). Full semantics under Change the template syntax; the runtime model lives in docs/ARCHITECTURE.md.
Repo layout
ralph/
├── package.json monorepo root (private, shared devDeps, pnpm scripts)
├── pnpm-workspace.yaml
├── tsconfig.base.json shared TS compiler options
├── .npmrc link-workspace-packages, prefer-workspace-packages
├── .dockerignore shrinks build context (consumed at repo root)
├── apps/
│ └── cli/ @daonhan/ralph
│ ├── package.json
│ ├── bin/
│ │ ├── ralph-afk.js
│ │ └── ralph-ghafk.js
│ └── scripts/ optional bash shims (ship in npm tarball)
│ ├── afk.sh
│ └── ghafk.sh
├── packages/
│ └── core/ @daonhan/ralph-core
│ ├── package.json
│ ├── tsconfig.json
│ ├── src/ main.ts, gh-main.ts, loop.ts, runner.ts, render.ts, stages.ts, index.ts, cli-help.ts, retry.ts, keepalive.ts, detach.ts, notify.ts + __tests__/
│ └── templates/ afk.md, ghafk.md, review.md, prompt.md, ghprompt.md, CHANGELOG.md, Dockerfile (builds ralph-sandbox image)
└── (playbooks live in packages/core/templates/ alongside the prompt templates)
At runtime, the host workspace gets a .ralph-tmp/ directory containing the per-iteration prompt files and logs/*.ndjson. This directory is gitignored.
Prerequisites
- Docker — Docker Desktop (Windows/macOS) or Docker Engine (Linux). The orchestrator shells out to
docker build/docker run. - Node.js 20+ + npm 9+ (or
pnpm/yarn). For Windows: native nvm-for-windows, nvm-windows, directly from nodejs.org, or Node inside WSL. For macOS/Linux: nvm, asdf, or a distro package. ghauthenticated (only required forralph-ghafk):gh auth loginonce.- Claude Code or Codex authentication for the provider you select. See "First-run setup" below.
- (Windows, optional but recommended)
bash.exeon PATH — comes free with Git for Windows. The renderer prefers it overcmd.exebecause POSIX redirects + utilities (git log,gh issue list) are smoother. If absent, the renderer falls back tocmd.exeand uses the built-in try-shell tag (!?\cmd|||fallback``) so commands that fail return their fallback string cleanly — no broken render.
Supported shells / OS combinations
Where you invoke ralph-afk |
Claude | Codex | Notes |
|---|---|---|---|
| Linux native (Ubuntu, etc.) | ✓ | ✓ | /bin/bash is used for shell tags. |
| macOS native | ✓ | ✓ | /bin/bash is used. |
| Windows PowerShell / cmd | ✓ | ✓ | Native Windows is supported for both providers. |
| Windows + WSL bash | ✓ | ✓ | Install Ralph, the selected host CLI, and credentials inside the same WSL distro. |
| Windows + Git Bash | ✓ | ✓ | Native Git Bash and its Windows home are supported for both providers. |
Windows + WSL: credentials
Credentials live on the host at ~/.claude or ~/.codex (for the selected provider) and ~/.config/gh, then get bind-mounted into the container. The path resolves per the shell that launches ralph-afk:
| Launch from | $HOME is |
Mounted into container |
|---|---|---|
| Windows PowerShell / cmd | C:\Users\<name> |
The selected provider's credential store under this home is mounted |
| WSL bash | /home/<linuxname> |
The selected provider's credential store under this home is mounted |
| Linux / macOS | /home/<name> or /Users/<name> |
The selected provider's credential store under this home is mounted |
Codex works from native Windows shells and WSL alike: Ralph mounts ~/.codex
read-only at /mnt/codex-creds and copies auth.json (plus config.toml andAGENTS.md when present) into a container-local CODEX_HOME before each
stage, so the credential home never sits on an NTFS-backed bind mount. Log in
with the host Codex CLI (codex login) from the same shell environment that
launches Ralph.
If you already logged in via PowerShell claude.exe and want WSL to use those creds too:
# WSL bash — replace <WINUSER>
mkdir -p ~/.claude
cp -r /mnt/c/Users/<WINUSER>/.claude/. ~/.claude/
cp /mnt/c/Users/<WINUSER>/.claude.json ~/.claude.json 2>/dev/null || true
mkdir -p ~/.config/gh
# gh on native Windows stores config in AppData/Roaming/GitHub CLI; fall back to .config/gh
cp -r "/mnt/c/Users/<WINUSER>/AppData/Roaming/GitHub CLI/." ~/.config/gh/ 2>/dev/null || \
cp -r /mnt/c/Users/<WINUSER>/.config/gh/. ~/.config/gh/ 2>/dev/null || true
- Launching Claude from PowerShell after a global install — just call the bin directly:
ralph-afk "<plan-and-prd>" 3 - Or from inside WSL bash:
ralph-afk "<plan-and-prd>" 3
Choose the coding agent
Claude remains the default:
ralph-afk "./docs/plans/x.md ./docs/prd/x.md" 5
Select Codex per invocation:
ralph-afk --agent codex "./docs/plans/x.md ./docs/prd/x.md" 5
ralph-ghafk --agent codex 5
For automation, RALPH_AGENT=codex is the fallback when --agent is absent.
The explicit flag always wins.
Codex ignores ~/.codex/config.toml by default while still reusing its login.
Pass --codex-user-config to load that configuration intentionally. This may
start configured MCP servers and hooks, so their commands and paths must work
inside the Linux sandbox.
RALPH_MODEL applies to the selected agent. For Claude the model resolves asRALPH_MODEL → the model pinned by the host's ~/.claude/settings.json
(env.ANTHROPIC_MODEL, else the model key /model stored; its "(default)"
entry stores no model) → claude-opus-5[1m], Ralph's own default. Ralph passes--model rather than letting the container choose, because the sandbox image's
CLI is frozen at image build time and its built-in default can lag the host's.
The exception is third-party routing: when the host settings enableCLAUDE_CODE_USE_BEDROCK, CLAUDE_CODE_USE_VERTEX, orCLAUDE_CODE_USE_FOUNDRY, model IDs are provider-specific, so Ralph sends no--model and the container CLI resolves as before. Isolated Codex defaults togpt-5.6-sol with high reasoning when RALPH_MODEL is unset. In inherited
configuration mode, an unset model and reasoning effort come from~/.codex/config.toml. An explicit invalid model fails; Ralph never reruns the
stage with another model.
First-run setup
1. Get the image
The orchestrator resolves the image in three steps on each run:
docker image inspect $RALPH_IMAGE— short-circuits if the image is already on the host (a floating tag like:latestis re-pulled anyway, so a republished sandbox isn't pinned to a stale local copy).- Otherwise
docker pull $RALPH_IMAGE— defaults todocker.io/daonhan/ralph-sandbox:latest. - If pull fails AND
$RALPH_DOCKER_CONTEXT/Dockerfileexists, falls back todocker build -t $RALPH_IMAGE $RALPH_DOCKER_CONTEXT.
For most users step 2 is enough — no local Dockerfile needed. To prime the cache:
docker pull docker.io/daonhan/ralph-sandbox:latest
Build locally (offline, custom changes):
cd ralph
docker build -t docker.io/daonhan/ralph-sandbox:latest -f packages/core/templates/Dockerfile .
The image bundles Node 22, Debian Bookworm Python 3.11 as python and python3,python -m venv, uv/uvx 0.11.28, .NET SDK 10, gh, jq, git, Claude Code,
and the pinned Codex CLI. Basic Python repositories need no extra runtime install. Create a
project-local virtual environment (for example, .venv) or use uv-managed
isolation; do not install project dependencies globally into the Debian system
Python.
This release provides one baked system Python and does not select versions from.python-version, .tool-versions, .mise.toml, pyproject.toml, or similar
manifests. Repositories pinned to another Python version need a customRALPH_IMAGE until future version-detection support is added.
Publishing a new image (maintainers)
The repo ships a GitHub Actions workflow at .github/workflows/publish-image.yml that builds + pushes linux/amd64 images to Docker Hub.
The Python runtime and tooling addition is a ralph-sandbox image release only;
it does not bump @daonhan/ralph-core or @daonhan/ralph.
Triggers:
workflow_dispatch— manual run from the Actions tab; pick the tag and whether to also push:latest.- Git tag
ralph-sandbox-v*— pushing a tag likeralph-sandbox-v0.1.3(cut by release-please) publishes:v0.1.3plus:latest, and enriches the matching GitHub Release with the image digest, an SBOM, and a keyless cosign attestation. - Git tag
image-v*— legacy compatibility shim; publishes:vX.Y.Zplus:latestbut does not enrich a GitHub Release. Slated for removal after one release cycle through the new path.
Required repo secrets: DOCKERHUB_USERNAME, DOCKERHUB_TOKEN (a Docker Hub access token with Read & Write scope on the daonhan/ralph-sandbox repository).
2. Authenticate (one-off)
The image is stateless. Provider credentials live on the host at ~/.claude or ~/.codex. If you use ralph-ghafk, its provider-independent GitHub CLI credentials live at ~/.config/gh. The orchestrator mounts only the selected provider's credentials, plus GitHub CLI credentials when present, into each container.
Same-shell rule.
ralph-afk/ralph-ghafkread$HOMEof the shell that launched them. Auth from the same shell context you intend to run the bins in. PowerShell host (C:\Users\<you>\.config\gh\) and WSL host (\\wsl$\Ubuntu\home\<you>\.config\gh\) are separate stores — don't mix. Native PowerShell and Git Bash homes are valid for both providers.
Choose one provider login path below. Claude and Codex authentication are
mutually exclusive; GitHub authentication is provider-independent and required
only for ralph-ghafk.
Claude login
Linux / macOS / WSL bash
mkdir -p ~/.claude
touch ~/.claude.json
docker run -it --rm \
-v "$HOME/.claude:/home/agent/.claude" \
-v "$HOME/.claude.json:/home/agent/.claude.json" \
docker.io/daonhan/ralph-sandbox:latest bash
Windows PowerShell
New-Item -ItemType Directory -Force "$HOME\.claude" | Out-Null
if (-not (Test-Path "$HOME\.claude.json")) { New-Item -ItemType File "$HOME\.claude.json" | Out-Null }
docker run -it --rm `
-v "${HOME}\.claude:/home/agent/.claude" `
-v "${HOME}\.claude.json:/home/agent/.claude.json" `
docker.io/daonhan/ralph-sandbox:latest bash
Inside the container
claude /login # browser flow; Claude only
exit
Codex login
Use this path instead when you select Codex. Install the host CLI version
pinned in Ralph's sandbox from the same shell environment that will launch
Ralph:
npm install --global @openai/[email protected]
codex --version
Codex credentials must be file-backed because a host OS keyring is not
available inside Docker. Create ~/.codex/config.toml if needed and set:
cli_auth_credentials_store = "file"
Then authenticate in that same shell:
codex login
codex login status
Ralph mounts ~/.codex read-only at /mnt/codex-creds and copies auth.json
(plus config.toml and AGENTS.md when present) into a container-localCODEX_HOME=/home/agent/.codex before each stage. Codex therefore never writes
to the host credential store: an OAuth token refreshed inside the container is
not written back, and the host CLI re-refreshes on its next use. Ralph runs
Codex with --ephemeral, so stage session transcripts are not persisted
either.
GitHub login (ralph-ghafk only)
Skip this section when you use only ralph-afk. For ralph-ghafk, authenticate
GitHub regardless of whether you selected Claude or Codex. Ralph renders issue
data with the host gh command, then mounts the same configuration read-only at/home/agent/.config/gh for the stage.
Linux / macOS / WSL bash
export GH_CONFIG_DIR="$HOME/.config/gh"
mkdir -p "$GH_CONFIG_DIR"
gh auth login
gh auth status
Windows PowerShell
$env:GH_CONFIG_DIR = "$HOME\.config\gh"
New-Item -ItemType Directory -Force $env:GH_CONFIG_DIR | Out-Null
gh auth login
gh auth status
Native Windows gh otherwise defaults to its AppData directory, which Ralph
does not mount. Keep GH_CONFIG_DIR set when you invoke ralph-ghafk from this
PowerShell session; set it again before the invocation if you open a new one.
On Linux, macOS, and WSL, keep the exported GH_CONFIG_DIR in the same shell forgh auth status and ralph-ghafk; export it again in a new shell before either
command. This pins gh to the configuration directory Ralph mounts even whenXDG_CONFIG_HOME differs.
For gh auth login pick: GitHub.com → HTTPS → Y (authenticate Git) →Login with web browser. Copy the one-time code, openhttps://github.com/login/device in the host browser, paste it, and approve.
Verify back on the host
Verify the credentials for the provider you selected.
Claude credentials
Linux / macOS / WSL:
ls -la ~/.claude/.credentials.json ~/.claude.json
PowerShell:
Get-ChildItem "$HOME\.claude\.credentials.json","$HOME\.claude.json"
Codex credentials
codex login status
Because cli_auth_credentials_store = "file", verify that a successful login
also created the credential file without printing its reusable secret.
ls -la ~/.codex/auth.json
GitHub credentials (ralph-ghafk only)
Linux / macOS / WSL:
export GH_CONFIG_DIR="$HOME/.config/gh"
gh auth status
PowerShell:
$env:GH_CONFIG_DIR = "$HOME\.config\gh"
gh auth status
Run the matching command from the same shell context as Ralph. On PowerShell,
keep GH_CONFIG_DIR set for the subsequent ralph-ghafk invocation. These
commands verify the active GitHub account without displaying the reusable
credential stored in hosts.yml.
Re-login / token expired
Re-run claude /login inside the container, codex login from the matching host
shell, or the host gh auth login flow above as appropriate. Provider login
updates the selected provider's writable host store; host GitHub login updates~/.config/gh, which Ralph later mounts read-only.
ralph-afk — plan/PRD loop
Usage
ralph-afk "<plan-and-prd>" <iterations>
(Or via the shim: ./node_modules/@daonhan/ralph/scripts/afk.sh "<plan-and-prd>" <iterations>.)
Also supports:
ralph-afk --help(or-h) — usage, flags, env vars.ralph-afk --version(or-V) — print bin + core version and exit.ralph-afk --print-config— print resolved workspace / docker context / image / docker-socket status and exit. Use for diagnostics before launching a real loop.<plan-and-prd>— a single string forwarded verbatim as{{ INPUTS }}in the template. Conventionally paths to plan and PRD files.<iterations>— max loop iterations. Exits early if implementer emits the sentinel.
Example
ralph-afk "./docs/plans/inventory.md ./docs/prd/PRD-Inventory.md" 10
From PowerShell on Windows:
wsl bash -c "ralph-afk './docs/plans/inventory.md ./docs/prd/PRD-Inventory.md' 10"
What happens per iteration
- Render template
packages/core/templates/afk.md:!?`git log -n 5 …|||No commits found`→ recent commits (try-shell){{ INPUTS }}→ the plan/PRD string@include:prompt.md→ the agent playbook (inlined by the Node renderer, no shell)
- Implementer stage (gate) —
docker run ralph-sandbox <selected-agent> …with the rendered prompt streamed in via a tempfile under.ralph-tmp/(avoids Windows 32 KB argv limit). Provider events are normalized and rendered live; the terminal completion is captured. - Sentinel check — if the completion contains
<promise>NO MORE TASKS</promise>, printRalph complete after <N> iterations.and exit 0. - Reviewer stage — runs
packages/core/templates/review.md. Reads the HEAD commit (thegit show --statsummary inline, the full patch spilled to.ralph-tmp/spill-…/head.diffvia@spill?:head.diff), then either commits afix(review): …patch or emits<review>OK</review>/<review>SKIP</review>and stops. Single pass; never amends the implementer's commit.
ralph-ghafk — GitHub-issue loop
Usage
export GH_CONFIG_DIR="$HOME/.config/gh"
ralph-ghafk <iterations>
No plan/PRD arg — context comes from open GitHub issues.
What happens per iteration
- Render template
packages/core/templates/ghafk.md:!?`git log -n 5 …|||No commits found`→ recent commits (try-shell)!?`gh issue list --state open --limit 50 --json number,title,labels|||[]`→ a lean inline index of open issues (number / title / labels)@spill?:issues.json=`gh issue list … --json number,title,body,labels,comments`→ full issue bodies + comments written to.ralph-tmp/spill-…/issues.json; the agentReads that file before picking a task@include:ghprompt.md→ the agent playbook (inlined by the Node renderer, no shell)
- ghafk-implementer stage (gate) — agent picks one open AFK issue, implements it, commits, closes / comments on the issue.
- Sentinel check — same as
ralph-afk. - Reviewer stage — same as
ralph-afk.
Running AFK
Both bins are designed to chew through long runs unattended. Five AFK flags wire that up:
| Flag | Default | What it does |
|---|---|---|
--no-keep-alive |
off (wake-lock acquired) | Skip the OS wake-lock for the loop's lifetime. |
--max-retries <N> |
3 |
Per-stage retry budget on transient failures. 0 restores fail-fast. |
--detach |
off | Fork the loop into a background process, print pid + log path, and exit. |
--log <path> |
<workspace>/.ralph-tmp/logs/detached-<parent-pid>.log |
Override the detached log target. Only meaningful with --detach. |
--notify |
off | OS toast + terminal bell on loop completion or unrecoverable failure. |
Canonical overnight recipe:
ralph-afk --detach --notify "<plan-and-prd>" 50
This forks into the background, holds an OS wake-lock so the host doesn't sleep, retries transient stage failures up to 3× with exponential backoff (5s / 30s / 2m), and raises a toast + bell when the run finishes (sentinel hit or iteration cap reached) or fails (signal, uncaught exception). Tail the log from any shell:
tail -f <workspace>/.ralph-tmp/logs/detached-*.log
Full per-OS notes (wake-lock mechanism, BurntToast install, WSL2 caveat, etc.) live in docs/keep-alive.md.
Consuming the package in another repo
Global install (recommended — run from anywhere)
npm i -g @daonhan/ralph
After install, both bins are on your $PATH:
cd /path/to/some/workspace
ralph-afk "<plan-and-prd>" 5
ralph-ghafk 5
The bundled Dockerfile (shipped inside @daonhan/ralph-core) is the default RALPH_DOCKER_CONTEXT, so the docker build fallback works even when you invoke from a workspace that has no Dockerfile of its own.
Per-repo install
# in your workspace repo
npm i -D @daonhan/ralph # or: pnpm add -D @daonhan/ralph
./node_modules/.bin/ralph-afk "<plan-and-prd>" 5
Bootstrap on demand (no install)
npx -y @daonhan/ralph ralph-afk "<plan-and-prd>" 5
Environment variables
| Variable | Default | Purpose |
|---|---|---|
RALPH_WORKSPACE |
process.cwd() |
Host path bind-mounted at /home/agent/workspace. Also where .ralph-tmp/ is written. |
RALPH_DOCKER_CONTEXT |
bundled @daonhan/ralph-core dir |
Build context for the docker build fallback. Only consulted if docker pull fails. Must contain Dockerfile. Defaults to the npm-installed core dir, which ships Dockerfile. |
RALPH_IMAGE |
docker.io/daonhan/ralph-sandbox:latest |
Full image reference. ensureImage does inspect → pull → build (fallback). |
RALPH_IMAGE_TAG |
(legacy) | Deprecated alias for RALPH_IMAGE. Honored if RALPH_IMAGE unset. |
RALPH_AGENT |
claude |
Agent fallback when --agent is absent: claude or codex. |
RALPH_RESULT_GRACE_MS |
30000 |
Milliseconds to wait after the provider completion event before force-killing a docker child that fails to exit on its own. 0 disables the timer (original wait-forever behavior). Invalid values (non-finite, negative) fall back to the default. |
RALPH_DOCKER_SOCK |
(on if a socket is found) | Set to 0 to disable bind-mounting the host Docker socket into the sandbox. Mounted by default so Testcontainers inside the container can spawn sibling containers — this grants the sandbox root-equivalent access to the host Docker daemon. Disable when running untrusted prompts. |
RALPH_DOCKER_SOCK_PATH |
(auto-detected) | Explicit host docker.sock path. Auto-detection (when unset) tries DOCKER_HOST (unix:// only), then /var/run/docker.sock, Docker Desktop, Colima, Rancher Desktop, and rootless Docker/Podman socket locations. |
RALPH_MODEL |
Claude claude-opus-5[1m]; isolated Codex uses gpt-5.6-sol/high |
Model override for the selected agent. Claude falls back to the model pinned in host ~/.claude/settings.json, then Ralph's own default instead of the sandbox CLI's frozen one — except under third-party routing (CLAUDE_CODE_USE_BEDROCK/_VERTEX/_FOUNDRY), where the container CLI still resolves. |
DOCKER_HOST |
(unset) | A unix:///… value is parsed for the docker-socket bind-mount; tcp:// / npipe:// / ssh:// are not bind-mountable. |
XDG_RUNTIME_DIR |
(unset) | Searched for rootless Docker/Podman sockets during auto-detection. |
NO_COLOR / TERM=dumb |
(unset) | Disable ANSI color in Ralph's own output. Color is also auto-disabled when stdout/stderr is not a TTY, so piping to a file stays clean. |
Local development (this monorepo)
Full contributor guide — dev loop, tests, adding a stage, releasing — lives in CONTRIBUTING.md. The essentials:
pnpm install # links workspace, hoists devDeps
pnpm -r build # compiles packages/core/dist
pnpm -r typecheck # no-emit type check
pnpm -r test # packages/core runs `vitest run` (apps/cli has no tests)
pnpm test # root: `node --test` over scripts/*.test.mjs
A husky pre-commit hook runs lint-staged (prettier --ignore-unknown --write on staged files) then pnpm typecheck on every commit.
Build artifacts
packages/core/dist/— compiled.js+.d.ts. Required for bothpnpm packandpnpm publish.apps/clihas no build step — bin shims are hand-written JS.
Pack tarballs (smoke-test before publish)
(cd packages/core && pnpm pack --pack-destination /tmp)
(cd apps/cli && pnpm pack --pack-destination /tmp)
# Install both in a throwaway repo to verify the published artifacts work
mkdir /tmp/ralph-test && cd /tmp/ralph-test
npm init -y
npm i -D /tmp/daonhan-ralph-core-*.tgz /tmp/daonhan-ralph-*.tgz
./node_modules/.bin/ralph-afk # → prints usage
Global install from local checkout (dev shortcut)
pnpm link --global is brittle inside this workspace (pnpm 9 rewrites the dependent's manifest). Use the pack-then-install path instead:
pnpm -r build
(cd packages/core && pnpm pack --pack-destination /tmp/ralph-packs)
(cd apps/cli && pnpm pack --pack-destination /tmp/ralph-packs)
npm i -g /tmp/ralph-packs/daonhan-ralph-core-*.tgz \
/tmp/ralph-packs/daonhan-ralph-*.tgz
ralph-afk # → Usage: ralph-afk <plan-and-prd> <iterations>
Re-run after each source change. To uninstall: npm uninstall -g @daonhan/ralph @daonhan/ralph-core.
Publish
Publishing is automated — you don't run pnpm publish by hand. Land work on main with Conventional Commits; release-please opens one Release PR per component, and merging that PR cuts the component tag (ralph-core-v* / ralph-v* / ralph-sandbox-v*) that triggers publish-npm.yml / publish-image.yml. See RELEASING.md for the full flow, required secrets, version policy, and rollback runbook.
Escape hatch (only if the pipeline is unavailable):
pnpm -r publish --access public # topological order; workspace:^ rewritten to semver
Use a local checkout in another repo (no publish)
Use the pack-then-install path above. It exposes ralph-afk / ralph-ghafk globally; no per-workspace step needed.
Customizing the pipeline
Add a stage
- Add an entry to
STAGESinpackages/core/src/stages.ts:linter: { name: "linter", template: "lint.md", permissionMode: "bypassPermissions" } satisfies Stage, - Create
packages/core/templates/lint.mdusing the same!`cmd`+{{ INPUTS }}syntax. - Wire it into the chain in
main.ts/gh-main.ts:stages: [STAGES.implementer, STAGES.linter, STAGES.reviewer], pnpm -r buildand republish.
Only the first stage is the gate (sentinel-checked). Subsequent stages always run after a non-sentinel gate result. Ralph runs the selected provider without interactive approval (permissionMode: "bypassPermissions" for Claude; --dangerously-bypass-approvals-and-sandbox for Codex). With the Docker socket disabled, persistent host-write exposure still includes the workspace mount and, for Claude, the read-write credential store (Codex credentials are mounted read-only); GitHub CLI config is read-only.
Change the template syntax
Renderer is in packages/core/src/render.ts. Tags supported today:
!`<shell cmd>`— executed viabash(Linux/macOS/WSL/Git Bash) orcmd.exe(Windows native fallback) withcwd = workspaceDir. stdout (trailing newline trimmed) replaces the tag. Failures throw and abort the iteration.!?`<shell cmd>|||<fallback>`— try-shell. Same as!but stderr is suppressed and a non-zero exit returns the literal fallback string. Use this for cross-platform safety — avoids depending on shell-specific2>/dev/null || echo "…"idioms.@spill[?]:<name>=`<shell cmd>[|||<fallback>]`— run<cmd>and write its stdout to a file<name>in the per-stage spill dir (.ralph-tmp/spill-…/), substituting the container-relative path./.ralph-tmp/spill-…/<name>into the prompt for the agent toRead. The?form suppresses stderr and writes<fallback>on non-zero exit;<name>must be a plain filename (no path separators, no..). Use for large outputs that would bloat the prompt —review.mdspills the full HEAD patch,ghafk.mdthe full issue bodies.@include:<rel-or-abs-path>— inline a file (via NodereadFileSync). Path resolved against the template's own directory when relative. No shell. Use this for bundled playbooks, not for live shell output.{{ INPUTS }}— replaced with theinputsfield passed intorunLoop.
Tags expand in a fixed order: @include → @spill → !? → ! → {{ INPUTS }}.
On Windows, the renderer prefers bash.exe (Git for Windows / WSL passthrough) over cmd.exe. The !? tag makes commands tolerant either way.
Override the image
Set RALPH_IMAGE=registry.example.com/my-image:tag before invoking the shim, or edit the default in packages/core/src/runner.ts. The runner does inspect → pull → build against whatever ref is set; legacy RALPH_IMAGE_TAG still works for backward compatibility.
Change feedback loops or task priority
The agent playbooks are self-contained: packages/core/templates/prompt.md (plan/PRD source + progress recording, for ralph-afk) and ghprompt.md (issue triage + close/comment, for ralph-ghafk). Each carries its own task-priority ladder, feedback loops, commit rules, and final rules. afk.md / ghafk.md each @include their respective playbook. Edit the playbook for a loop to change its task priority or feedback loops.
Stopping a run
- Natural stop: implementer emits
<promise>NO MORE TASKS</promise>. - Manual stop:
Ctrl+C.runLoopinstallsSIGINT/SIGTERMhandlers that abort the active stage (viaAbortController, killing the docker child), release the OS wake-lock, fire the--notifytoast if enabled, and exit130(SIGINT) /143(SIGTERM). Tempfiles under.ralph-tmp/.run-*.mdand the per-stagespill-*/dir are removed by thefinallyblock inrunner.ts; a hardSIGKILLmay leave them — safe to delete, gitignored.
Troubleshooting
Cannot find module '@daonhan/ralph-core'—@daonhan/ralphwas installed but its dep didn't resolve. Re-runnpm install(orpnpm install) in the workspace, or usenpx -y @daonhan/ralphto let npx fetch a clean copy.@esbuild/win32-x64 package is present but this platform needs @esbuild/linux-x64—node_modules/installed from the wrong OS. Deletenode_modules/+ lockfile and reinstall under WSL.Not logged in · Please run /login— Claude credentials are missing inside the container. Run the interactivedocker run … claude /loginstep from "First-run setup".- Codex reports that login is missing — ensure
cli_auth_credentials_store = "file", runcodex loginfrom the same shell environment as Ralph (per the same-shell rule), and confirmcodex login statussucceeds and~/.codex/auth.jsonexists in that environment's home. - Codex fails with
Operation not permitted (os error 1)/EPERMat startup — the container'sCODEX_HOMEis sitting on a Windows bind mount, which cannot host the unix socket and symlinks Codex creates at startup. Current Ralph avoids this by copying credentials into a container-localCODEX_HOME; upgrade@daonhan/ralphif you see this. - Codex config, MCP servers, or hooks are missing — isolated Codex intentionally ignores
~/.codex/config.toml; opt in with--codex-user-configand ensure configured commands and paths work inside Linux Docker. - An explicit Codex model fails — fix or remove
RALPH_MODEL. Ralph does not silently fall back togpt-5.6-solor another model after an explicit model failure. - The Claude stage fails on the model itself (unknown model, or one your plan cannot use) — Ralph sent its own default because neither
RALPH_MODELnor your host~/.claude/settings.jsonpinned one. Runralph-afk --print-configto see the model and where it came from, then setRALPH_MODEL=<model you have access to>or pick an explicit (non-"(default)") entry in/model. gh issue listfails withnot a git repository— the workspace has no.git. Theghafk.mdtemplate uses|| echo "[]"fallback so the iteration still proceeds, butghcannot detect the target repo. Initialize the repo, or push first.MSB3248duringdotnet build/dotnet test— virtiofs/9p quirk on Windows-mounted source. The agent retries automatically per the recipe inpackages/core/templates/prompt.md; manual repro:dotnet test <path-to-test-csproj> \ -m:1 \ /p:UseSharedCompilation=false \ /p:BuildInParallel=false \ /p:BaseIntermediateOutputPath=/tmp/ralph-obj/<name>/ \ /p:BaseOutputPath=/tmp/ralph-bin/<name>/docker runexit 1 with no selected-agent output — image stale. Force refresh:docker rmi docker.io/daonhan/ralph-sandbox:latest docker pull docker.io/daonhan/ralph-sandbox:latestdocker pull failed … and no Dockerfile at …— the default image ref isn't reachable (offline, registry down, or you set a custom$RALPH_IMAGEthat doesn't exist) AND no Dockerfile is at$RALPH_DOCKER_CONTEXT. Fix one of: connectivity,RALPH_IMAGE, or place a Dockerfile at$RALPH_DOCKER_CONTEXT.pull access denied … repository does not exist—$RALPH_IMAGEpoints at a private repo or a typo. Eitherdocker login, switch to a public image, or unsetRALPH_IMAGEto use the default.- Loop hangs after a stage's final assistant message (no next iteration, no error) — the selected CLI inside the sandbox emitted its completion event but failed to exit. After
RALPH_RESULT_GRACE_MS(default 30000ms), the runner kills the lingering docker child, keeps the captured completion, and continues the loop. Bump or disable the timer via the environment when diagnosing. To inspect or stop the container manually before the timer expires:
The sandbox runs withdocker ps --filter ancestor=docker.io/daonhan/ralph-sandbox:latest docker kill <container-id>--rm, so the container is removed after it exits.
Files in this folder
| File / dir | Purpose |
|---|---|
apps/cli/scripts/afk.sh |
Optional shim — plan/PRD loop. Falls back to npx @daonhan/ralph ralph-afk. Shipped in the npm tarball. |
apps/cli/scripts/ghafk.sh |
Optional shim — GitHub-issue loop. Calls ralph-ghafk. |
packages/core/templates/prompt.md |
Agent playbook for ralph-afk. Shipped in core tarball. |
packages/core/templates/ghprompt.md |
Agent playbook for ralph-ghafk. Shipped in core tarball. |
packages/core/templates/Dockerfile |
Builds ralph-sandbox image: Node 22 + Python 3.11/venv + uv/uvx + .NET SDK 10 + gh + Claude Code + pinned Codex CLI. Shipped in @daonhan/ralph-core tarball. |
.dockerignore |
Shrinks build context (consumed at repo root for CI builds). |
package.json |
Monorepo root (private). Shared devDeps + pnpm workspace scripts. |
pnpm-workspace.yaml |
Declares apps/* and packages/* as workspace members. |
tsconfig.base.json |
Shared TS compiler options inherited by every package. |
apps/cli/ |
@daonhan/ralph — CLI bin entries (ralph-afk, ralph-ghafk). |
packages/core/src/main.ts |
Exports runAfk(argv). |
packages/core/src/gh-main.ts |
Exports runGhAfk(argv). |
packages/core/src/loop.ts |
Iteration driver. Runs stage chain; first stage is the gate. |
packages/core/src/render.ts |
Template renderer (!`cmd` + {{ INPUTS }}). |
packages/core/src/runner.ts |
docker run wrapper + NDJSON stream + credential mounts. Image lookup: inspect → pull → build. Reads RALPH_IMAGE. |
.github/workflows/publish-image.yml |
CI: build + push linux/amd64 ralph-sandbox to Docker Hub on workflow_dispatch, ralph-sandbox-v* tag (release-please primary), or legacy image-v* tag. |
.github/workflows/publish-npm.yml |
CI: publish @daonhan/ralph-core / @daonhan/ralph to npm on ralph-core-v* / ralph-v* tags; enriches the GitHub Release with the .tgz, SBOM, and cosign attestation. |
.github/workflows/release-please.yml |
CI: on push to main, opens a per-component Release PR; merging it cuts the tag that triggers the publish workflows. |
RELEASING.md |
Single source of truth for releasing all three components (npm packages + image): release-please flow, version policy, secrets, rollback runbook. |
CONTRIBUTING.md |
Maintainer / contributor guide: dev loop, tests, adding a stage, release pipeline. |
QUICKSTART.md |
Zero-to-first-loop getting-started guide for new users. |
docs/ARCHITECTURE.md |
Internals / runtime data-flow reference for library extenders and core contributors. |
packages/core/src/cli-help.ts |
Flag parsing (parseFlags); --help / --version / --print-config output. |
packages/core/src/retry.ts |
withRetries — per-stage retry with exponential backoff (default 3). |
packages/core/src/keepalive.ts |
OS wake-lock acquire/release for the loop's lifetime (--no-keep-alive to skip). |
packages/core/src/detach.ts |
--detach fork-and-exit into a background process. |
packages/core/src/notify.ts |
--notify OS toast + terminal bell on loop terminal events. |
packages/core/src/stages.ts |
Stage registry — implementer, ghafkImplementer, reviewer. |
packages/core/src/index.ts |
Barrel re-export — runAfk, runGhAfk, runLoop, STAGES, renderTemplate, … |
packages/core/templates/afk.md |
ralph-afk prompt template. |
packages/core/templates/ghafk.md |
ralph-ghafk prompt template. |
packages/core/templates/review.md |
Reviewer prompt template. |
License
MIT (c) Paul Nguyen.
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found