Thanks for working on Ralph Loop. This is an npm-workspaces monorepo with two published packages and a shared test suite.
packages/
├── ralph/ # ralph-loop — the runtime CLI (the product)
│ ├── src/ # module map below
│ ├── assets/prompts/ # default agent prompts (eta templates)
│ ├── schema/ # generated JSON schema for ralph.config.json
│ └── scripts/gen-schema.ts
└── create-ralph-loop/ # the scaffolder
├── src/cli.ts
└── template/ # what gets written into a scaffolded project
e2e/ # real-git, mock-adapter end-to-end loop tests
npm install
npm run build # tsc for both packages + regenerates schema/ralph.config.schema.json
npm test # vitest: unit suites + the e2e loop suite
npm run test:watch- Node ≥ 18, CommonJS. Use normal imports (no
.jsextensions) andnode:-prefixed builtins. - Never spawn processes directly. Go through
packages/ralph/src/util/proc.ts(run,runShell,spawnDetached,commandExists,killTree). It uses cross-spawn for argv spawns (Windows.cmdresolution) and Node's native spawn for shell commands (cross-spawn misreports shell exit code 1 as ENOENT on Windows). - Validate external/parsed data with zod; fail closed. Unparseable agent output must never throw — degrade to a rejecting outcome.
- Prompts render with eta (
<%= it.x %>), not handlebars, despite the history.
| Area | Files | Responsibility |
|---|---|---|
| Foundation | config/schema.ts, features/schema.ts, adapters/types.ts, events/types.ts |
zod schemas + shared type contracts (imported everywhere). |
| Adapters | adapters/{claude,codex,aider,mock,registry}.ts |
Wrap agent CLIs behind RunnerAdapter. |
| Gates | gates/{baseline,command,featureIntegrity,diffSize,index}.ts |
Mechanical checks over a GateContext. |
| Features | features/{store,dag,migrate}.ts |
Harness-owned feature state, DAG selection, v1→v2 migration. |
| Dev server | devserver/manager.ts |
Cross-platform dev-server lifecycle. |
| Prompts | prompts/{render,blocks}.ts |
Template resolution + <ralph-*> block parsing. |
| Verify / replan / garden | verify/verifier.ts, replan/replanner.ts, garden/gardener.ts |
The three supporting agent roles. |
| Run | run/{loop,iteration,checkpoint,state,types}.ts |
The orchestrator state machine. |
| Support | budget/tracker.ts, notify/, events/log.ts, util/* |
Budgets, notifications, telemetry, proc/git/paths/logger. |
| CLI | cli.ts |
commander wiring; assembles the RunContext. |
The loop is the only consumer of most modules, so a subagent implementing one module only needs the foundation types — the loop reconciles them.
- Create
packages/ralph/src/adapters/<name>.tsexporting a class that implementsRunnerAdapter(adapters/types.ts):name,isAvailable()(viacommandExists), andinvoke(req)returning anAgentResult.- Build argv, pass the prompt via
run(..., { input })(stdin) to avoid argv-length limits. - Map
req.permissionTier(readonly/edit/full) to the CLI's own flags. - Extract token/cost into
usagewhen the CLI reports it; returnusage: undefinedotherwise (the loop falls back to iteration/time budgets). - Keep parsing in an exported pure function and unit-test it against a fixture.
- Build argv, pass the prompt via
- Register it in
adapters/registry.ts(getAdapterswitch). - It's now selectable via any role in
ralph.config.json("adapter": "<name>").
- Create
packages/ralph/src/gates/<name>.tsexporting a class implementingGate(gates/types.ts):name+run(ctx: GateContext): GateResult. Gates are git-pure — read only fromctx(changedFiles,diffStat,featuresHash*,baseline); only command gates spawn a subprocess (viarunShell). For baseline-relative behavior, compare current failures toctx.baselineand block only on new signatures. - Wire it into
buildGates()ingates/index.ts(respect a config toggle). - Add a colocated
*.test.ts.
Defaults live in packages/ralph/assets/prompts/*.md (eta). A project can
override any of them at <specDir>/prompts/<name>.md — prompts/render.ts
prefers the override. If you add a template that emits a structured block, add a
parse* function + zod schema in prompts/blocks.ts (fail-closed).
- Unit: colocated
*.test.tsnext to each module. Run one file withnpx vitest run <path>(esbuild transpiles per-file; no full build needed). - End-to-end:
e2e/loop.e2e.test.tsdrivesrunLoopwith theMockAdapteragainst a real temp git repo — the reference for how the pieces compose (happy path, gate/verifier failure + revert + retry + block, integrity tamper, dependency ordering, replan). Add scenarios here when changing loop behavior. - CI runs
build+teston ubuntu and windows; keep both green.
- Conventional-commit prefixes (
feat(ralph):,fix:,docs:,chore:). - Branch off
main; open a PR. CI must pass on both OSes.