From 03810971eb386ca418af9df706da8c581ce5ebce Mon Sep 17 00:00:00 2001 From: Kasper Junge Date: Mon, 8 Jun 2026 20:07:28 +0200 Subject: [PATCH 1/2] docs: lead with loop engineering so the concept ranks and reads clearly Reframe the README and docs landing around "loop engineering" as the headline identity: ralphify is the runtime, ralph loops is the open standard format, and the standard is what makes loops portable. Add a prominent "What is loop engineering?" section to the landing page and optimize its title/description/keywords for the term. Rename the five jobs to "the five jobs of loop engineering" and sharpen the share job around the standard format. Co-Authored-By: Claude Opus 4.8 --- README.md | 16 +++++++------- docs/getting-started.md | 13 ++++++------ docs/index.md | 46 +++++++++++++++++++++++++++-------------- 3 files changed, 45 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index eaa890b..0c6f600 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,13 @@ Documentation

-**Ralphify runs ralph loops.** +**Ralphify is the runtime for loop engineering.** -Designing autonomous agent loops — *loop engineering* — is becoming how people get real work out of coding agents. The [ralph loops format](https://ralphloops.io/) is how you write one. Ralphify is how you run it. +*Loop engineering* — designing autonomous agent loops instead of prompting turn by turn — is becoming how people get real work out of coding agents. You write a loop once and let it drive the agent for hours, one commit at a time. -A **ralph loop** is a portable directory that defines an autonomous agent loop — a prompt, the commands to run between iterations, and any files the agent needs. It's an open format ([ralphloops.io](https://ralphloops.io/)): one required file, `RALPH.md`. Ralphify is the CLI that runs it. +**[Ralph loops](https://ralphloops.io/) is the open, standard format for writing one.** A ralph loop is a portable directory — a prompt, the commands to run between iterations, and any files the agent needs — with one required file: `RALPH.md`. Because the format is a standard, a loop is portable: write it once, commit the directory, and anyone can run it. + +**Ralphify is the runtime that runs it.** ``` grow-coverage/ @@ -40,7 +42,7 @@ Each iteration, write tests for one untested module, then stop. ralph run grow-coverage # loops until Ctrl+C ``` -Each iteration starts with a **fresh context window** and **current data**: ralphify runs the commands, fills in the `{{ placeholders }}`, pipes the prompt to your agent, and loops. Walk away, come back to a pile of commits. +That's loop engineering in three lines: a prompt, a command for live data, and the runtime. Each iteration starts with a **fresh context window** and **current data** — ralphify runs the commands, fills in the `{{ placeholders }}`, pipes the prompt to your agent, and loops. Walk away, come back to a pile of commits. *Works with any agent CLI. Swap `claude -p` for Codex, Aider, or your own — just change the `agent` field.* @@ -56,9 +58,9 @@ Any of these gives you the `ralph` command. --- -## The five things you do with ralphify +## The five jobs of loop engineering -Everything in ralphify is one of these five jobs. That's the whole tool. +Loop engineering with ralphify is one of these five jobs — write, feed, run, steer, share. That's the whole tool. ### 1. Write a ralph @@ -142,7 +144,7 @@ The prompt body is re-read from disk every iteration. Edit `RALPH.md` while the ### 5. Share a ralph -A ralph is just a directory in the [ralph loops format](https://ralphloops.io/), so it's portable. Commit it to git, push it, and anyone can clone the repo and run it by name: +This is what the standard format buys you. Because a ralph is just a directory in the open [ralph loops format](https://ralphloops.io/), it's portable — and anyone with ralphify can run it. Commit it to git, push it, and your loop engineering becomes someone else's starting point: ```bash git clone https://github.com/owner/repo # grab a shared ralph diff --git a/docs/getting-started.md b/docs/getting-started.md index 56d56e3..ddb7f92 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -9,12 +9,12 @@ keywords: loop engineering, loop engineering tutorial, ralph loops tutorial, ins !!! tldr "TL;DR" `uv tool install ralphify` → `ralph scaffold my-ralph` → edit the RALPH.md → `ralph run my-ralph -n 1 --log-dir ralph_logs` to test → add a `commands` entry for your test suite → `ralph run my-ralph` to loop. The agent sees fresh command output each iteration and fixes what it breaks. -What you're about to build is a loop: you write it in the [ralph loops format](https://ralphloops.io/), and ralphify runs it. This tutorial walks through the five things you do with ralphify — **write** a ralph, **feed** it live data, **run** the loop, **steer** it while it runs, and **share** it. By the end you'll have a self-healing loop that validates its own work. +What you're about to do is loop engineering: you write a loop in the open [ralph loops format](https://ralphloops.io/), and ralphify — the runtime — runs it. This tutorial walks through the five jobs — **write** a ralph, **feed** it live data, **run** the loop, **steer** it while it runs, and **share** it. By the end you'll have a self-healing loop that validates its own work. ## Prerequisites - **Python 3.11+** -- **An AI coding agent CLI** — this tutorial uses [Claude Code](https://docs.anthropic.com/en/docs/claude-code). Install it with `npm install -g @anthropic-ai/claude-code`. Ralphify also works with [other agents](agents.md). +- **An AI coding agent CLI** — this tutorial uses [Claude Code](https://docs.anthropic.com/en/docs/claude-code). Install it with `npm install -g @anthropic-ai/claude-code`. Ralphify also works with [other agents](cli.md#using-different-agents). - **A project with a test suite** (we'll use this for the feedback loop) ## Step 1: Install ralphify @@ -142,7 +142,7 @@ cat ralph_logs/001_*.log If the agent produced useful work, you're ready to add test feedback. !!! info "Something not working?" - If the agent errored or didn't do anything useful, check [Troubleshooting](troubleshooting.md) for common issues — agent hangs, missing commands, and frontmatter mistakes are all covered there. + If the agent errored or didn't do anything useful, check [Troubleshooting](cli.md#troubleshooting) for common issues — agent hangs, missing commands, and frontmatter mistakes are all covered there. ## Step 4: Add a test command @@ -296,7 +296,6 @@ This is the most powerful part of ralph loops — you're steering a running agen ## Next steps -- [Cookbook](cookbook.md) — copy-pasteable setups for coding, docs, research, and more -- [How it Works](how-it-works.md) — what happens inside each iteration -- [Troubleshooting](troubleshooting.md) — when things don't work as expected -- [CLI Reference](cli.md) — all commands and options +- [Reference](cli.md) — the CLI, RALPH.md format, how the loop works, agents, troubleshooting, and the Python API +- [How the loop works](cli.md#how-the-loop-works) — what happens inside each iteration +- [Examples](https://github.com/computerlovetech/ralphify/tree/main/examples) — copy-pasteable ralph loops for coding, docs, research, and more diff --git a/docs/index.md b/docs/index.md index 3d1095f..8d3182c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,7 +1,7 @@ --- -title: Ralphify — the runtime for ralph loops -description: Ralphify runs ralph loops — the open format for loop engineering and autonomous agent loops (ralphloops.io). The ralph loops format is how you write a loop; ralphify is the runtime that runs it. -keywords: ralphify, loop engineering, ralph loops, ralph loops format, RALPH.md, ralphloops.io, agent loop runtime, autonomous agent loop +title: Ralphify — the runtime for loop engineering +description: Ralphify is the runtime for loop engineering. Ralph loops (ralphloops.io) is the open, standard format for writing an autonomous agent loop; ralphify is the runtime that runs it. +keywords: loop engineering, ralphify, ralph loops, ralph loops format, RALPH.md, ralphloops.io, agent loop runtime, autonomous agent loop hide: - toc --- @@ -11,12 +11,14 @@ hide:

-Ralphify runs ralph loops. +Ralphify is the runtime for loop engineering.

-Designing autonomous agent loops — *loop engineering* — is becoming how people get real work out of coding agents. The [ralph loops format](https://ralphloops.io/) is how you write one. Ralphify is how you run it. +*Loop engineering* — designing autonomous agent loops instead of prompting turn by turn — is becoming how people get real work out of coding agents. You write a loop once and let it drive the agent for hours, one commit at a time. -A **ralph loop** is a portable directory that defines an autonomous agent loop — a prompt, the commands to run between iterations, and any files the agent needs. It's an open format ([ralphloops.io](https://ralphloops.io/)): a directory whose one required file is `RALPH.md`. **Ralphify** is the CLI that runs it. +**[Ralph loops](https://ralphloops.io/) is the open, standard format for writing one.** A ralph loop is a portable directory — a prompt, the commands to run between iterations, and any files the agent needs — whose one required file is `RALPH.md`. Because the format is a standard, a loop is portable: write it once, commit the directory, and anyone with ralphify can run it. + +**Ralphify is the runtime that runs it.** ``` grow-coverage/ @@ -52,6 +54,19 @@ Each iteration starts with a **fresh context window** and **current data** — r --- +## What is loop engineering? + +**Loop engineering is the practice of designing autonomous agent loops** — instead of steering a coding agent turn by turn, you engineer a loop that drives it for you. A loop has three parts: a **prompt** (what the agent should do each pass), **commands** that pull in live data (test results, coverage, git log), and a **runtime** that assembles the prompt, runs the agent, and repeats with fresh context. + +Loop engineering is becoming how people get sustained, autonomous work out of coding agents — hours of progress, one commit at a time. It rests on two pieces: + +- **[Ralph loops](https://ralphloops.io/) — the format.** An open, standard way to write a loop as a portable directory (`RALPH.md`). Because it's a standard, loops are shareable and reusable across people and projects. +- **Ralphify — the runtime.** The `ralph` CLI that runs a loop: run commands → assemble the prompt → pipe it to your agent → loop. + +Write the loop once in the standard format, and ralphify runs it for as long as you want. + +--- + ## Install === "uv (recommended)" @@ -74,9 +89,9 @@ Each iteration starts with a **fresh context window** and **current data** — r --- -## The five things you do with ralphify +## The five jobs of loop engineering -Everything in ralphify is one of these five jobs. That's the whole tool. +Loop engineering with ralphify is one of these five jobs — write, feed, run, steer, share. That's the whole tool.
@@ -98,7 +113,7 @@ Everything in ralphify is one of these five jobs. That's the whole tool. `commands` run each iteration; their output fills `{{ commands. }}` in the prompt. The agent always sees current test results, coverage, and git log — a self-healing feedback loop. - [How it works →](how-it-works.md) + [How it works →](cli.md#how-the-loop-works) - :material-play-circle-outline:{ .lg .middle } **3. Run the loop** @@ -124,7 +139,7 @@ Everything in ralphify is one of these five jobs. That's the whole tool. --- - A ralph is a portable directory in the [ralph loops format](https://ralphloops.io/). Commit it to git and push — anyone can clone the repo and run it by name. + This is what the standard format buys you. A ralph is a portable directory in the open [ralph loops format](https://ralphloops.io/) — commit it, push it, and anyone with ralphify can run it. ```bash git clone https://github.com/owner/repo @@ -135,9 +150,9 @@ Everything in ralphify is one of these five jobs. That's the whole tool. --- -## Why loops +## Why loop engineering works -A single agent run fixes a bug or writes a function. The leverage of a ralph loop is **sustained, autonomous work** — running for hours, one commit at a time, while you do something else. +A single agent run fixes a bug or writes a function. The leverage of loop engineering is **sustained, autonomous work** — a ralph loop runs for hours, one commit at a time, while you do something else. - **Fresh context, no decay.** Each iteration starts with a clean context window. The agent reads the current state of the codebase every loop — no conversation bloat, no degradation. - **Commands as feedback.** Command output feeds into the prompt each iteration. When tests fail, the agent sees the failure and fixes it next cycle. @@ -149,13 +164,12 @@ A single agent run fixes a bug or writes a function. The leverage of a ralph loo ## Requirements - Python 3.11+ -- [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) (or [any agent CLI](agents.md) that accepts piped input) +- [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) (or [any agent CLI](cli.md#using-different-agents) that accepts piped input) --- ## Next steps - **[Getting Started](getting-started.md)** — from install to a running loop in 10 minutes -- **[How it Works](how-it-works.md)** — what happens inside each iteration -- **[Cookbook](cookbook.md)** — copy-pasteable ralph loops to start from -- **[The ralph loops format](https://ralphloops.io/)** — the open spec ralphify implements +- **[Reference](cli.md)** — the CLI, RALPH.md format, how the loop works, agents, troubleshooting, and the Python API +- **[The ralph loops format](https://ralphloops.io/)** — the open, standard spec ralphify implements From 49bdf58a8bd84d80eb1d9802301d0b8e12f07147 Mon Sep 17 00:00:00 2001 From: Kasper Junge Date: Mon, 8 Jun 2026 20:07:36 +0200 Subject: [PATCH 2/2] docs: collapse the nav into a single Reference page so it's easier to navigate Cut the docs from nine pages to four (Home, Getting Started, Reference, Changelog). Fold how-it-works, agents, quick-reference, troubleshooting, and the Python API into one comprehensive Reference page, and drop the cookbook page in favor of pointing at the runnable examples/ directory. Rewire all internal links to the new anchors. Co-Authored-By: Claude Opus 4.8 --- docs/404.md | 7 +- docs/agents.md | 223 --------------- docs/api.md | 396 ------------------------- docs/cli.md | 449 +++++++++++++++++++++++++---- docs/contributing/index.md | 4 +- docs/cookbook.md | 275 ------------------ docs/how-it-works.md | 189 ------------ docs/quick-reference.md | 233 --------------- docs/troubleshooting.md | 571 ------------------------------------- mkdocs.yml | 15 +- 10 files changed, 393 insertions(+), 1969 deletions(-) delete mode 100644 docs/agents.md delete mode 100644 docs/api.md delete mode 100644 docs/cookbook.md delete mode 100644 docs/how-it-works.md delete mode 100644 docs/quick-reference.md delete mode 100644 docs/troubleshooting.md diff --git a/docs/404.md b/docs/404.md index b1ab61e..2ef1445 100644 --- a/docs/404.md +++ b/docs/404.md @@ -15,11 +15,8 @@ This page doesn't exist — it may have been moved or the URL might have a typo. **Try one of these instead:** -- [**Home**](index.md) — overview, install, and quickstart +- [**Home**](index.md) — overview, install, and the five jobs - [**Getting Started**](getting-started.md) — from install to a running loop -- [**Quick Reference**](quick-reference.md) — everything at a glance -- [**Cookbook**](cookbook.md) — copy-pasteable ralph setups -- [**CLI Reference**](cli.md) — all commands and options -- [**Troubleshooting**](troubleshooting.md) — common issues and fixes +- [**Reference**](cli.md) — the CLI, RALPH.md format, how the loop works, agents, and troubleshooting Or use the **search bar** above to find what you're looking for. diff --git a/docs/agents.md b/docs/agents.md deleted file mode 100644 index 29abd31..0000000 --- a/docs/agents.md +++ /dev/null @@ -1,223 +0,0 @@ ---- -title: How to Run Claude Code, Aider, or Codex in an Autonomous Loop -description: Set up Claude Code, Aider, Codex CLI, or any AI coding agent to run autonomously in a loop with ralphify. Copy-pasteable configs, wrapper scripts, and a comparison table. -keywords: loop engineering, run claude code in loop, aider autonomous mode, codex cli automation, AI coding agent loop, autonomous claude code, aider loop setup, run AI agent automatically, pipe prompt to coding agent, claude code non-interactive, aider no-interactive mode, codex exec stdin, automate AI coding agent ---- - -# Using with Different Agents - -!!! tldr "TL;DR" - Set the `agent` field in your RALPH.md to any CLI that reads a prompt from stdin and exits when done. **Claude Code** (`claude -p --dangerously-skip-permissions`) is the default with the deepest integration. **Aider** needs a `bash -c` wrapper to pass stdin as `--message`. **Codex CLI** works natively with `codex exec`. For anything else, write a short wrapper script. - -Ralphify works with **any CLI that reads a prompt from stdin and exits when done**. Claude Code is the default, but you can use any tool that follows this contract. - -This page shows how to configure the [`agent` frontmatter field](quick-reference.md#frontmatter-fields) in your RALPH.md for popular agents and how to write your own wrapper. - -## Agent comparison - -| Agent | Stdin support | Streaming | Wrapper needed | -|---|---|---|---| -| [Claude Code](#claude-code) | Native (`-p`) | Yes — real-time activity tracking | No | -| [Aider](#aider) | Via bash wrapper | No | Yes (`bash -c`) | -| [Codex CLI](#codex-cli) | Native (`exec`) | No | No | -| [Custom](#custom-wrapper-script) | You implement it | No | Yes (script) | - -If you're not sure which to pick: **start with Claude Code.** It has the deepest integration, the best autonomous coding capabilities, and is the default. - -## What ralphify needs from an agent - -Every iteration, ralphify runs your agent like this: - -```bash -echo "" | -``` - -Your agent must: - -1. **Read a prompt from stdin** — the full assembled prompt is piped in -2. **Do work in the current directory** — edit files, run commands, make commits -3. **Exit when done** — exit code 0 means success, non-zero means failure - -That's it. No special protocol, no API — just stdin in, work done, process exits. - -## Claude Code - -The default and recommended agent. - -```markdown ---- -agent: claude -p --dangerously-skip-permissions ---- -``` - -| Flag | Purpose | -|---|---| -| `-p` | Non-interactive mode — reads prompt from stdin, prints output, exits | -| `--dangerously-skip-permissions` | Skips approval prompts so the agent can work autonomously | - -Install Claude Code: - -```bash -npm install -g @anthropic-ai/claude-code -``` - -!!! info "Why `--dangerously-skip-permissions`?" - Without this flag, Claude Code pauses to ask for approval before editing files, running commands, or making commits. In an autonomous loop, nobody is there to approve — so the agent would hang forever. [Commands](how-it-works.md#2-run-commands-and-capture-output) in your RALPH.md act as your guardrails instead. - -### Automatic streaming mode - -When ralphify detects that the agent command starts with `claude`, it automatically adds `--output-format stream-json --verbose` to the command. You don't need to add these flags yourself. - -This enables ralphify to: - -- Parse Claude Code's structured JSON output line by line -- Track agent activity in real time -- Extract the final result text from the agent's response - -## Aider - -[Aider](https://aider.chat) is an AI pair-programming tool that works with multiple LLM providers. - -```markdown ---- -agent: bash -c 'aider --yes-always --no-auto-commits --message "$(cat -)"' ---- -``` - -| Flag | Purpose | -|---|---| -| `--yes-always` | Auto-approve all changes (no interactive prompts) | -| `--no-auto-commits` | Let your prompt control when commits happen | -| `--message "..."` | Pass the prompt as a message instead of stdin | - -!!! note "Why the bash wrapper?" - Aider doesn't natively read prompts from stdin. The `bash -c` wrapper reads stdin with `cat -` and passes it as a `--message` argument. - -### Aider with a specific model - -```markdown ---- -agent: bash -c 'aider --yes-always --no-auto-commits --model claude-sonnet-4-6 --message "$(cat -)"' ---- -``` - -## Codex CLI - -[OpenAI Codex CLI](https://github.com/openai/codex) supports non-interactive use natively via its `exec` subcommand. - -```markdown ---- -agent: codex exec --sandbox danger-full-access - ---- -``` - -| Flag | Purpose | -|---|---| -| `exec` | Non-interactive mode — designed for piped/scripted use | -| `--sandbox danger-full-access` | Full filesystem access for autonomous operation | -| `-` | Read prompt from stdin | - -## Custom wrapper script - -For full control, write a wrapper script that reads stdin and calls your agent however it needs to be called. - -**`ralph-agent.sh`** - -```bash -#!/bin/bash -set -e - -# Read the prompt from stdin -PROMPT=$(cat -) - -# Call your agent however it works -my-custom-agent --input "$PROMPT" --auto-approve -``` - -```bash -chmod +x ralph-agent.sh -``` - -**`my-ralph/RALPH.md`** - -```markdown ---- -agent: ./ralph-agent.sh ---- - -Your prompt here. -``` - -## Testing your setup - -Verify the agent works outside of ralphify first. The command depends on which agent you're using: - -=== "Claude Code" - - ```bash - echo "Say hello and nothing else" | claude -p --dangerously-skip-permissions - ``` - - ```text - Hello! - ``` - -=== "Aider" - - ```bash - echo "Say hello and nothing else" | bash -c 'aider --yes-always --no-auto-commits --message "$(cat -)"' - ``` - -=== "Codex CLI" - - ```bash - echo "Say hello and nothing else" | codex exec --sandbox danger-full-access - - ``` - -If the agent prints a response and exits, your setup is working. If it hangs or errors, fix the agent installation before continuing. - -Then test through ralphify with a single iteration using [`ralph run`](cli.md#ralph-run): - -```bash -ralph run my-ralph -n 1 --log-dir ralph_logs -``` - -!!! tip "Non-Claude-Code agents" - Disable auto-commits if your prompt handles commits — most agents have this feature, and it conflicts with prompt-driven commit instructions. Use [`--timeout`](cli.md#ralph-run) as a safety net in case the agent enters an unexpected interactive mode. - -## How agent output works - -Ralphify streams agent output line-by-line in both execution modes. In an interactive terminal, output streams live to the console by default — press `p` to silence it and `p` again to resume. See [Peeking at live agent output](cli.md#peeking-at-live-agent-output) for details. - -When [`--log-dir`](cli.md#ralph-run) is set, output is captured to a log file and also echoed after each iteration completes. Live peek still works the same way in that mode. - -### Streaming mode (Claude Code) - -When the agent command starts with `claude`, ralphify parses the agent's structured JSON output line by line. This enables additional features beyond live output: - -- **Activity tracking** — the terminal shows what the agent is doing (tool calls, reasoning) in real time -- **Result text extraction** — the agent's final response is captured separately -- **Verbose logging** — with `--log-dir`, logs include the agent's internal tool calls and reasoning - -### Blocking mode (all other agents) - -For non-Claude agents, ralphify spawns the process and drains stdout and stderr through background reader threads. You see the agent's plain text output line by line as it's produced. - -Both modes support all ralphify features (commands, timeouts, iteration tracking, live peek). The difference is that Claude Code gets structured activity tracking on top of the raw output. - -## Adapting other tools - -Many AI coding tools don't read from stdin directly but can be adapted with a bash wrapper. The pattern is: - -```bash -bash -c ' --message "$(cat -)"' -``` - -The `cat -` reads the piped prompt from stdin and passes it as a command-line argument. This works for any tool that accepts a prompt via a flag (like `--message`, `--input`, `--prompt`). - -If the tool has no way to accept a prompt non-interactively, a [custom wrapper script](#custom-wrapper-script) is the escape hatch — you can use the prompt text however the tool needs it. - -## Next steps - -- [Getting Started](getting-started.md) — set up your first loop with the agent you just configured -- [Troubleshooting](troubleshooting.md) — when the agent hangs, produces no output, or exits unexpectedly diff --git a/docs/api.md b/docs/api.md deleted file mode 100644 index e87d3d3..0000000 --- a/docs/api.md +++ /dev/null @@ -1,396 +0,0 @@ ---- -title: Run AI Agent Loops from Python — Ralphify API -description: Embed autonomous AI coding loops in Python scripts and pipelines. Run loops programmatically, manage concurrent agent runs, listen to events, and control loops from code. -keywords: run AI agent from Python, programmatic coding agent loop, Python AI automation, embed agent loop in script, concurrent AI agent runs, ralphify Python API, RunConfig, run_loop ---- - -# Python API - -!!! tldr "TL;DR" - `run_loop(config, state)` runs the same loop as `ralph run` but from Python. Build a `RunConfig` with your agent, commands, and options. Pass a `RunState` to inspect progress and control the loop (pause, resume, stop). Add an `EventEmitter` to react to events. Use `RunManager` for concurrent runs. - -All public API is available from the top-level `ralphify` package. The API runs the same [core loop](how-it-works.md) as the [CLI](cli.md), so everything you can do with `ralph run` you can do from Python. - -## Quick start - -```python -from pathlib import Path -from ralphify import run_loop, RunConfig, RunState - -config = RunConfig( - agent="claude -p --dangerously-skip-permissions", - ralph_dir=Path("my-ralph"), - ralph_file=Path("my-ralph/RALPH.md"), - commands=[], - max_iterations=3, -) -state = RunState(run_id="my-run") -run_loop(config, state) -``` - -This runs the same loop as [`ralph run my-ralph -n 3`](cli.md#ralph-run). When the loop finishes, `state` contains the results. - ---- - -## `run_loop(config, state, emitter=None)` - -The main loop. Reads [RALPH.md](quick-reference.md), runs commands, assembles prompts, pipes them to the agent, and repeats. **Blocks until the loop finishes.** - -| Parameter | Type | Description | -|---|---|---| -| `config` | `RunConfig` | All settings for the run | -| `state` | `RunState` | Observable state — counters, status, control methods | -| `emitter` | `EventEmitter | None` | Event listener. `None` uses `NullEmitter` (silent) | - ---- - -## `RunConfig` - -All settings for a single run. Fields match the [CLI options](cli.md#ralph-run). - -```python -from pathlib import Path -from ralphify import RunConfig, Command - -config = RunConfig( - agent="claude -p --dangerously-skip-permissions", - ralph_dir=Path("my-ralph"), - ralph_file=Path("my-ralph/RALPH.md"), - commands=[ - Command(name="tests", run="uv run pytest -x"), - Command(name="lint", run="uv run ruff check ."), - ], - args={"dir": "./src", "focus": "performance"}, - max_iterations=10, - delay=2.0, - timeout=300, - stop_on_error=True, - log_dir=Path("ralph_logs"), - project_root=Path("."), -) -``` - -| Field | Type | Default | Description | -|---|---|---|---| -| `agent` | `str` | -- | Full agent command string | -| `ralph_dir` | `Path` | -- | Path to the ralph directory | -| `ralph_file` | `Path` | -- | Path to the RALPH.md file | -| `commands` | `list[Command]` | `[]` | Commands to run each iteration | -| `args` | `dict[str, str]` | `{}` | User argument values | -| `max_iterations` | `int | None` | `None` | Max iterations (`None` = unlimited) | -| `delay` | `float` | `0` | Seconds to wait between iterations | -| `timeout` | `float | None` | `None` | Max seconds per iteration | -| `stop_on_error` | `bool` | `False` | Stop loop if agent exits non-zero or times out | -| `log_dir` | `Path | None` | `None` | Directory for iteration log files | -| `credit` | `bool` | `True` | Append co-author trailer instruction to prompt | -| `project_root` | `Path` | `Path(".")` | Project root directory | - -`RunConfig` is mutable — you can change fields mid-run, and the loop picks up changes at the next iteration boundary. - ---- - -## `Command` - -A command that runs each iteration. - -```python -from ralphify import Command - -cmd = Command(name="tests", run="uv run pytest -x") -cmd_slow = Command(name="integration", run="uv run pytest tests/integration", timeout=300) -``` - -| Field | Type | Default | Description | -|---|---|---|---| -| `name` | `str` | (required) | Identifier used in [`{{ commands. }}`](how-it-works.md#3-resolve-placeholders-with-command-output) placeholders. Letters, digits, hyphens, and underscores only. | -| `run` | `str` | (required) | Shell command to execute | -| `timeout` | `float` | `60` | Max seconds before the command is killed | - ---- - -## `RunState` - -Observable state for a running loop. Thread-safe control methods let you stop, pause, and resume from another thread. - -```python -state = RunState(run_id="my-run") -run_loop(config, state) - -print(state.status) # RunStatus.COMPLETED -print(state.completed) # 4 -print(state.failed) # 1 -print(state.timed_out_count) # 0 (subset of failed) -print(state.total) # 5 (completed + failed) -print(state.iteration) # 5 (current iteration number) -print(state.started_at) # datetime or None -``` - -| Property | Type | Description | -|---|---|---| -| `run_id` | `str` | Unique identifier for this run | -| `status` | `RunStatus` | Current lifecycle status | -| `iteration` | `int` | Current iteration number (starts at 0) | -| `completed` | `int` | Number of successful iterations | -| `failed` | `int` | Number of failed iterations (includes timed out) | -| `timed_out_count` | `int` | Number of timed-out iterations (subset of `failed`) | -| `total` | `int` | `completed + failed` | -| `started_at` | `datetime | None` | When the run started | -| `paused` | `bool` | Whether the run is currently paused | -| `stop_requested` | `bool` | Whether a stop has been requested | - -!!! note "Counter invariant" - `timed_out_count` is a **subset** of `failed`, not an independent category. A timed-out iteration increments both `timed_out_count` and `failed`. Therefore `completed + failed == total`. - -### Control methods - -Thread-safe methods for controlling the loop from another thread: - -```python -state.request_stop() # Stop after current iteration -state.request_pause() # Pause between iterations -state.request_resume() # Resume a paused loop -``` - ---- - -## `RunStatus` - -Lifecycle status of a run. - -| Status | Value | Description | -|---|---|---| -| `PENDING` | `"pending"` | Created but not yet started | -| `RUNNING` | `"running"` | Loop is executing iterations | -| `PAUSED` | `"paused"` | Paused between iterations, waiting for resume | -| `STOPPED` | `"stopped"` | Stopped by user via `request_stop()` | -| `COMPLETED` | `"completed"` | Reached `max_iterations` or finished naturally | -| `FAILED` | `"failed"` | Stopped by `--stop-on-error` after a failed/timed-out iteration, or crashed with an unhandled exception | - ---- - -## `StopReason` - -A `Literal` type indicating why a run stopped. Used in the `RUN_STOPPED` event's `reason` field. - -| Value | Description | -|---|---| -| `"completed"` | Reached `max_iterations` or finished naturally | -| `"error"` | Crashed with an unhandled exception | -| `"user_requested"` | Stopped via `request_stop()` or `Ctrl+C` | - -```python -from ralphify import StopReason -``` - ---- - -## Event system - -The loop emits structured events at each step. Implement the `EventEmitter` protocol (a single `emit(event)` method) to listen. - -### Custom emitter - -```python -from pathlib import Path -from ralphify import Event, EventType, RunConfig, RunState, run_loop - - -class MyEmitter: - def emit(self, event: Event) -> None: - if event.type == EventType.ITERATION_COMPLETED: - print(f"Iteration {event.data['iteration']} done in {event.data['duration_formatted']}") - - -config = RunConfig( - agent="claude -p --dangerously-skip-permissions", - ralph_dir=Path("my-ralph"), - ralph_file=Path("my-ralph/RALPH.md"), - commands=[], - max_iterations=3, -) -state = RunState(run_id="observed-run") -run_loop(config, state, emitter=MyEmitter()) -``` - -### `Event` - -Each event carries: - -| Field | Type | Description | -|---|---|---| -| `type` | `EventType` | What happened | -| `run_id` | `str` | Which run produced this event | -| `data` | `dict` | Event-specific payload | -| `timestamp` | `datetime` | UTC timestamp | - -Use `event.to_dict()` to serialize to a JSON-compatible dict. - -### `EventType` reference - -#### Run lifecycle - -| Event | Data fields | -|---|---| -| `RUN_STARTED` | `ralph_name`, `commands` (int count), `max_iterations`, `timeout`, `delay` | -| `RUN_STOPPED` | `reason` (`StopReason`), `total`, `completed`, `failed`, `timed_out_count` | -| `RUN_PAUSED` | -- | -| `RUN_RESUMED` | -- | - -#### Iteration lifecycle - -| Event | Data fields | -|---|---| -| `ITERATION_STARTED` | `iteration` | -| `ITERATION_COMPLETED` | `iteration`, `returncode`, `duration`, `duration_formatted`, `detail`, `log_file`, `result_text` | -| `ITERATION_FAILED` | same as `ITERATION_COMPLETED` | -| `ITERATION_TIMED_OUT` | same as `ITERATION_COMPLETED` (`returncode` is `None`) | - -#### Commands - -| Event | Data fields | -|---|---| -| `COMMANDS_STARTED` | `iteration`, `count` | -| `COMMANDS_COMPLETED` | `iteration`, `count` | - -#### Prompt assembly - -| Event | Data fields | -|---|---| -| `PROMPT_ASSEMBLED` | `iteration`, `prompt_length` | - -#### Agent output - -| Event | Data fields | -|---|---| -| `AGENT_ACTIVITY` | `iteration`, `raw` (dict — one stream-json line from the agent) | -| `AGENT_OUTPUT_LINE` | `iteration`, `line` (raw text), `stream` (`"stdout"` or `"stderr"`) | - -`AGENT_ACTIVITY` fires only for Claude Code (streaming mode) and carries parsed JSON events. `AGENT_OUTPUT_LINE` fires for **all agents** and carries each raw output line as it's produced — this is what powers [live peek](cli.md#peeking-at-live-agent-output). - -#### Other - -| Event | Data fields | -|---|---| -| `LOG_MESSAGE` | `message`, `level` (`"info"` / `"error"`), `traceback` (optional) | - -### Built-in emitters - -| Emitter | Description | -|---|---| -| `NullEmitter` | Discards all events silently. Default when no emitter is passed. | -| `QueueEmitter` | Pushes events into a `queue.Queue` for async consumption. | -| `FanoutEmitter` | Broadcasts each event to multiple emitters. | -| `BoundEmitter` | Wraps any emitter with a fixed `run_id` so you don't have to construct `Event` objects manually. Has `log_info(message)`, `log_error(message, traceback=...)`, and `agent_output_line(line, stream, iteration)` convenience methods. | - -```python -from ralphify import QueueEmitter, FanoutEmitter, BoundEmitter - -# Consume events from a queue -q_emitter = QueueEmitter() -run_loop(config, state, emitter=q_emitter) -while not q_emitter.queue.empty(): - event = q_emitter.queue.get() - print(event.to_dict()) - -# Broadcast to multiple listeners -fanout = FanoutEmitter([q_emitter, MyEmitter()]) -run_loop(config, state, emitter=fanout) - -# Emit events without constructing Event objects -bound = BoundEmitter(q_emitter, run_id="my-run") -bound(EventType.ITERATION_STARTED, {"iteration": 1}) -bound.log_info("Starting phase two") -bound.log_error("Something failed", traceback=tb) -``` - ---- - -## Concurrent runs with `RunManager` - -`RunManager` is a thread-safe registry for launching and controlling multiple ralph loops concurrently. Each run gets its own daemon thread and event queue. - -```python -from pathlib import Path -from ralphify import RunManager, RunConfig, Command - -manager = RunManager() - -docs_config = RunConfig( - agent="claude -p --dangerously-skip-permissions", - ralph_dir=Path("docs-ralph"), - ralph_file=Path("docs-ralph/RALPH.md"), - commands=[Command(name="build", run="mkdocs build --strict")], - max_iterations=5, -) -tests_config = RunConfig( - agent="claude -p --dangerously-skip-permissions", - ralph_dir=Path("tests-ralph"), - ralph_file=Path("tests-ralph/RALPH.md"), - commands=[Command(name="tests", run="uv run pytest -x")], - max_iterations=3, -) - -docs_run = manager.create_run(docs_config) -tests_run = manager.create_run(tests_config) - -manager.start_run(docs_run.state.run_id) -manager.start_run(tests_run.state.run_id) - -# Check progress -for run in manager.list_runs(): - print(f"{run.state.run_id}: {run.state.status.value} — {run.state.completed} done") - -# Control a run -manager.pause_run(docs_run.state.run_id) -manager.resume_run(docs_run.state.run_id) -manager.stop_run(docs_run.state.run_id) -``` - -### `ManagedRun` - -Each run created by `RunManager` is wrapped in a `ManagedRun` — a bundle of config, state, and event queue. - -| Field | Type | Description | -|---|---|---| -| `config` | `RunConfig` | The run's configuration | -| `state` | `RunState` | Observable state — inspect progress or call control methods | -| `emitter` | `QueueEmitter` | Event queue — drain `emitter.queue` to consume events | - -#### Adding custom listeners - -Register additional emitters to receive events from a run. Add listeners **before** calling `start_run()`: - -```python -from ralphify import RunManager, RunConfig, ManagedRun - -class SlackNotifier: - def emit(self, event): - if event.type == EventType.RUN_STOPPED: - notify_slack(f"Run {event.run_id} finished") - -manager = RunManager() -run = manager.create_run(config) -run.add_listener(SlackNotifier()) # receives all events alongside the queue -manager.start_run(run.state.run_id) -``` - -When extra listeners are registered, events are broadcast to both the built-in queue and all custom listeners via a `FanoutEmitter`. - -### `RunManager` methods - -| Method | Description | -|---|---| -| `create_run(config)` | Create a `ManagedRun` from a `RunConfig`. Does not start it. | -| `start_run(run_id)` | Start the run in a daemon thread. | -| `stop_run(run_id)` | Signal the run to stop after the current iteration. | -| `pause_run(run_id)` | Pause the run between iterations. | -| `resume_run(run_id)` | Resume a paused run. | -| `list_runs()` | Return a snapshot of all registered runs. | -| `get_run(run_id)` | Look up a run by ID. | - ---- - -## Next steps - -- [**How the loop works**](how-it-works.md) — understand the iteration cycle that `run_loop` executes -- [**Cookbook**](cookbook.md) — real-world ralph examples you can adapt for your own projects diff --git a/docs/cli.md b/docs/cli.md index b06b7c9..425e7b6 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -1,15 +1,21 @@ --- -title: "CLI Reference: Run AI Coding Agents in Autonomous Loops" -description: "Complete CLI reference for the ralph command — run autonomous AI coding loops, scaffold new agent prompts, and configure RALPH.md frontmatter options." -keywords: run AI agent in loop CLI, autonomous coding agent command line, ralph run command, ralph scaffold, RALPH.md frontmatter format, AI coding loop options, agent timeout iterations, user arguments CLI, ralphify CLI reference +title: Reference — Ralphify CLI, RALPH.md, the loop, agents & Python API +description: Complete ralphify reference — the ralph CLI commands, RALPH.md frontmatter format, how each loop iteration works, configuring different agents, troubleshooting, and the Python API. +keywords: loop engineering, ralph CLI reference, RALPH.md format, ralph run, ralph scaffold, ralph loops format, how loop engineering works, run claude code in loop, ralphify python api, ralphify troubleshooting --- -# CLI Reference +# Reference + +Everything beyond [the five jobs](index.md#the-five-jobs-of-loop-engineering) lives here: the CLI, the `RALPH.md` format, how a loop iteration works under the hood, how to wire up different agents, troubleshooting, and the Python API. !!! tldr "TL;DR" **`ralph run -n 5`** runs the loop. **`ralph scaffold `** creates a ralph from a template. Pass user args as `--name value` flags. Everything is configured in a single [`RALPH.md`](#ralphmd-format) file with YAML frontmatter. -## `ralph` +--- + +## CLI + +### `ralph` With no subcommand, prints the banner and help text. @@ -26,19 +32,11 @@ ralph --help # Show help | `--show-completion` | | Print the completion script (for manual setup) | | `--help` | | Show help and exit | -### Shell completion - -```bash -ralph --install-completion bash # or zsh, fish -``` - -Restart your shell after installing. Use `--show-completion` to print the script for manual setup. - ---- +Install shell completion with `ralph --install-completion bash` (or `zsh`, `fish`), then restart your shell. -## `ralph run` +### `ralph run` -Start the [autonomous coding loop](how-it-works.md). +Start the loop. ```bash ralph run my-ralph # Run forever (Ctrl+C to stop) @@ -59,9 +57,9 @@ ralph run my-ralph --dir ./src # Pass user args to the ralph | `--timeout` | `-t` | none | Max seconds per iteration | | `--log-dir` | `-l` | none | Directory for iteration log files | -### User arguments +#### User arguments -User arguments are passed as named flags after the ralph path. Use `{{ args. }}` [placeholders](how-it-works.md#3-resolve-placeholders-with-command-output) in your RALPH.md to reference them. +User arguments are passed as named flags after the ralph path. Use `{{ args. }}` [placeholders](#placeholders) in your RALPH.md to reference them. Named flags (`--name value`) work without any frontmatter declaration. The `args` field is only required when you want to pass **positional** arguments — it tells ralphify which names to map them to: @@ -94,33 +92,24 @@ ralph run research -- --verbose ./src # dir="--verbose", focus="./src" ``` -Use `--` when a positional value starts with `--` and would otherwise be parsed as a flag. +Use `--` when a positional value starts with `--` and would otherwise be parsed as a flag. Missing args resolve to an empty string. -Missing args resolve to an empty string. +#### Stopping the loop -### Stopping the loop - -Press `Ctrl+C` to stop the loop. Ralphify uses two-stage signal handling: +Press `Ctrl+C` to stop. Ralphify uses two-stage signal handling: | Action | Behavior | |---|---| | `Ctrl+C` (first) | Finishes the current iteration gracefully, then stops the loop | | `Ctrl+C` (second) | Force-kills the agent process and exits immediately | -The first press lets the agent complete its current work (e.g. finish a commit). If you don't want to wait, press `Ctrl+C` again to terminate immediately. - -The loop also stops automatically when: - -- All `-n` iterations have completed -- `--stop-on-error` is set and the agent exits non-zero or times out +The loop also stops automatically when all `-n` iterations have completed, or when `--stop-on-error` is set and the agent exits non-zero or times out. -### Peeking at live agent output +#### Peeking at live agent output -When you run `ralph run` in an interactive terminal, the agent's stdout and stderr stream live to the console by default. Press `p` to silence the stream (useful for quieter loops) and press `p` again to resume it. The default is off whenever the output is not a real terminal (piped, redirected, or captured in CI), so `ralph run ... | cat` is unaffected. +When you run `ralph run` in an interactive terminal, the agent's stdout and stderr stream live to the console by default. Press `p` to silence the stream and `p` again to resume it. The default is off whenever the output is not a real terminal (piped, redirected, or captured in CI), so `ralph run ... | cat` is unaffected. -The compact peek panel shows the ten most recent activity lines to keep the terminal tidy. When you need to see earlier activity from the current iteration — or the activity from a previous iteration — press **shift+P** to enter **full-screen peek**, a scrollable view of the entire buffer that takes over the terminal on the alt screen. - -Inside full-screen peek: +The compact peek panel shows the ten most recent activity lines. Press **shift+P** to enter **full-screen peek**, a scrollable view of the entire buffer: | Key | Action | |-----|--------| @@ -130,38 +119,24 @@ Inside full-screen peek: | `[` / `]` | Browse to the previous / next iteration | | `q` or `P` | Exit back to the compact view | -Activity keeps streaming into the buffer while you scroll, so follow mode (`G` or scrolling back to the bottom) catches up to the tail. Iteration ends no longer drop you out of the alt screen — the finished iteration moves into a bounded history so you can keep browsing it (and the new live iteration) with `[` / `]`. Status lines and result text printed during the session are buffered and replayed when you exit fullscreen, so the terminal scrollback stays intact. - -Live streaming works with line-buffered agents such as Claude Code, OpenAI Codex, Aider, and any other process that writes one line at a time. For Claude running in stream-json mode you'll see the raw JSON events; for other agents you'll see the agent's plain output. Agents that repaint their own terminal UI (full-screen curses or TUI apps) are not supported — ralphify pipes their stdio, so they detect a non-TTY and fall back to plain output. - -When `--log-dir` is set, output is captured to the log file and also echoed after each iteration completes; live peek still works the same way in that mode. - -Some runtimes block-buffer their stdout when it is piped, which can make lines appear in bursts rather than as they are produced. If you notice stuttering, set `PYTHONUNBUFFERED=1` (or the equivalent for your agent) in the environment where you launch `ralph`. - ---- +Live streaming works with line-buffered agents such as Claude Code, OpenAI Codex, and Aider. Agents that repaint their own terminal UI (full-screen curses or TUI apps) are not supported — ralphify pipes their stdio, so they detect a non-TTY and fall back to plain output. If lines appear in bursts rather than as produced, set `PYTHONUNBUFFERED=1` (or the equivalent) in the environment where you launch `ralph`. -## `ralph scaffold` +### `ralph scaffold` Scaffold a new ralph with a ready-to-customize template. ```bash -ralph scaffold my-task # Creates my-task/RALPH.md with a generic template +ralph scaffold my-task # Creates my-task/RALPH.md with a template ralph scaffold # Creates RALPH.md in the current directory ``` -| Argument | Default | Description | -|---|---|---| -| `[NAME]` | none | Directory name. If omitted, creates RALPH.md in the current directory | - -The generated template includes an example command (`git-log`), an example arg (`focus`), and a prompt body with placeholders for both. Edit it, then run [`ralph run`](#ralph-run). See [Getting Started](getting-started.md) for a full walkthrough. - -Errors if `RALPH.md` already exists at the target location. +The generated template includes an example command (`git-log`), an example arg (`focus`), and a prompt body with placeholders for both. Errors if `RALPH.md` already exists at the target location. See [Getting Started](getting-started.md) for a full walkthrough. --- ## RALPH.md format -The `RALPH.md` file is the single configuration and prompt file for a ralph. It uses YAML frontmatter for settings and the body for the prompt text. +The `RALPH.md` file is the single configuration and prompt file for a ralph — the shape is defined by the open [ralph loops format](https://ralphloops.io/). It uses YAML frontmatter for settings and the body for the prompt text. ```markdown --- @@ -194,17 +169,16 @@ Your instructions here. Reference args with {{ args.dir }}. ### Commands -Each command has these fields: - | Field | Type | Default | Description | |---|---|---|---| | `name` | string | (required) | Identifier used in `{{ commands. }}` placeholders. Letters, digits, hyphens, and underscores only. | | `run` | string | (required) | Shell command to execute each iteration (supports `{{ args. }}` placeholders). Commands starting with `./` run from the ralph directory; others run from the project root. | | `timeout` | number | `60` | Max seconds before the command is killed | -Commands run in order. Output (stdout + stderr) is captured regardless of exit code. Commands are parsed with `shlex.split()` — no shell features (pipes, redirections, `&&`). For shell features, point the `run` field at a script. See the [Cookbook](cookbook.md) for real-world command patterns. +Commands run in order. Output (stdout + stderr) is captured regardless of exit code. -If a command exceeds its timeout, the process is killed and the captured output up to that point is used. +!!! warning "No shell features in commands" + Commands are parsed with `shlex.split()` and run directly, not through a shell — pipes (`|`), redirections (`>`), and chaining (`&&`) won't work in the `run` field. Point the `run` field at a script instead: `run: ./my-script.sh`. ### Placeholders @@ -216,14 +190,363 @@ If a command exceeds its timeout, the process is killed and the captured output | `{{ ralph.iteration }}` | Current iteration number (1-based) | | `{{ ralph.max_iterations }}` | Total iterations if `-n` was set, empty otherwise | -`ralph.*` placeholders are automatically available — no frontmatter configuration needed. +`ralph.*` placeholders are automatically available — no frontmatter configuration needed. **Only commands referenced by a placeholder appear in the prompt** — an unreferenced command still runs each iteration, but its output is excluded. Unmatched placeholders resolve to an empty string. + +--- + +## How the loop works + +This is the lifecycle of one iteration — the format you write, and the runtime that runs it. + +### The six steps of each iteration + +1. **Re-read the prompt from disk.** The prompt body (everything below the frontmatter) is read **every iteration**, so you can edit the prompt — add rules, change the task — while the loop runs. Frontmatter (`agent`, `commands`, `args`) is parsed once at startup; restart to change it. +2. **Run commands and capture output.** Each command runs in order and captures stdout + stderr **regardless of exit code** — a `pytest -x` that exits non-zero is exactly what you want in the prompt. Commands run from the project root by default; commands starting with `./` run from the ralph directory. Each has a 60s default timeout (override with `timeout`). +3. **Resolve placeholders.** `{{ commands. }}`, `{{ args. }}`, and `{{ ralph.* }}` are replaced with their values. `{{ args.* }}` is resolved both in the prompt body and in command `run` strings. +4. **Assemble the final prompt.** The resolved body becomes a single text string. HTML comments (``) are stripped — use them for notes to yourself. By default a **co-author trailer instruction** is appended asking the agent to add `Co-authored-by: Ralphify ` to commits; disable with `credit: false`. +5. **Pipe the prompt to the agent** via stdin (`echo "" | claude -p ...`). The agent works in the current directory and exits; ralphify waits. When the agent command starts with `claude`, ralphify automatically adds `--output-format stream-json --verbose` for structured streaming. +6. **Loop back with fresh context.** The next iteration starts from step 1 with a clean context window and fresh command output. + +### What gets re-read vs. what stays fixed + +| What | When read | Why it matters | +|---|---|---| +| Prompt body | Every iteration | Edit the prompt while the loop runs — the next iteration follows your new instructions | +| Command output | Every iteration | The agent always sees fresh data (latest git log, current test status, etc.) | +| Frontmatter (`agent`, `commands`, `args`) | Once at startup | Restart to pick up changes | +| User arguments | Once at startup | Passed via CLI flags, constant for the run | + +### The self-healing feedback loop + +When iteration 1 breaks a test, iteration 2's `{{ commands.tests }}` shows the failure: + +```markdown +## Test results + +FAILED tests/test_auth.py::test_login - AssertionError: expected 200, got 401 +============================= 1 failed, 4 passed in 1.45s ==================== + +If tests are failing, fix them before starting new work. +``` + +The agent sees the failure and the instruction to fix it first. That's the self-healing loop: the agent breaks something, the command captures the failure, the agent fixes it next iteration. + +--- + +## Using different agents + +Ralphify works with **any CLI that reads a prompt from stdin and exits when done**. Every iteration, ralphify runs `echo "" | `. Your agent must read the prompt from stdin, do work in the current directory, and exit (0 = success, non-zero = failure). + +| Agent | Stdin support | Streaming | Wrapper needed | +|---|---|---|---| +| [Claude Code](#claude-code) | Native (`-p`) | Yes — real-time activity tracking | No | +| [Aider](#aider) | Via bash wrapper | No | Yes (`bash -c`) | +| [Codex CLI](#codex-cli) | Native (`exec`) | No | No | +| [Custom](#custom-wrapper-script) | You implement it | No | Yes (script) | + +If you're not sure: **start with Claude Code.** It's the default and has the deepest integration. + +### Claude Code + +```markdown +--- +agent: claude -p --dangerously-skip-permissions +--- +``` + +`-p` enables non-interactive mode (reads prompt from stdin, prints output, exits). `--dangerously-skip-permissions` skips approval prompts so the agent works autonomously — without it, the agent would hang forever waiting for approval that nobody is there to give. Install with `npm install -g @anthropic-ai/claude-code`. When the command starts with `claude`, ralphify automatically adds `--output-format stream-json --verbose` for activity tracking and result-text extraction. + +### Aider + +[Aider](https://aider.chat) doesn't natively read prompts from stdin, so wrap it with `bash -c` and `cat -`: + +```markdown +--- +agent: bash -c 'aider --yes-always --no-auto-commits --message "$(cat -)"' +--- +``` + +`--yes-always` auto-approves changes; `--no-auto-commits` lets your prompt control commits. Add `--model claude-sonnet-4-6` (or another) to pick a model. + +### Codex CLI + +[OpenAI Codex CLI](https://github.com/openai/codex) supports non-interactive use natively: + +```markdown +--- +agent: codex exec --sandbox danger-full-access - +--- +``` + +`exec` is non-interactive mode, `--sandbox danger-full-access` grants full filesystem access, and `-` reads the prompt from stdin. + +### Custom wrapper script + +For anything else, write a script that reads stdin and calls your agent: + +```bash +#!/bin/bash +set -e +PROMPT=$(cat -) +my-custom-agent --input "$PROMPT" --auto-approve +``` + +```markdown +--- +agent: ./ralph-agent.sh +--- +``` -Unmatched placeholders resolve to an empty string. +The general pattern for tools that accept a prompt via a flag is `bash -c ' --message "$(cat -)"'`. Test any agent outside ralphify first (`echo "Say hello" | `), then through ralphify with `ralph run my-ralph -n 1 --log-dir ralph_logs`. --- -## Next steps +## Examples + +The [`examples/`](https://github.com/computerlovetech/ralphify/tree/main/examples) directory in the repo has real, runnable ralph loops you can copy: autonomous ML research, test coverage, bug hunting, deep research, code migration, security scanning, documentation, and continuous codebase improvement. Each is a `RALPH.md` (some with helper scripts) you can adapt by swapping the task and commands. + +--- + +## Troubleshooting + +!!! tldr "Quick checklist" + 1. Run `ralph run my-ralph -n 1` — it validates your setup and shows clear errors + 2. Test the agent outside ralphify: `echo "Say hello" | claude -p` + 3. Use `--log-dir ralph_logs` to capture output for debugging + 4. Commands don't support shell features (pipes, `&&`) — use a wrapper script instead + +### Setup + +- **"is not a directory, RALPH.md file, or installed ralph"** — the path doesn't resolve to a valid ralph. `ralph run` accepts a directory containing `RALPH.md`, a direct path to a `RALPH.md` file, or the name of an installed ralph in `.agents/ralphs/`. Check the path exists. +- **"Missing or empty 'agent' field"** — add `agent: claude -p --dangerously-skip-permissions` to your frontmatter. +- **"Agent command 'claude' not found on PATH"** — the agent CLI isn't installed or isn't on your PATH. Verify with `claude --version`. + +### Loop behavior + +- **Agent hangs or produces no output** — run the agent directly (`echo "Say hello" | claude -p`). If it hangs there, the issue is the agent CLI. If it works standalone, add `--timeout 300` to kill stalled iterations. +- **Agent exits non-zero every iteration** — capture output with `ralph run my-ralph -n 1 --log-dir ralph_logs` and read the log. Common causes: missing agent auth, a prompt asking for a failing command, or a too-large prompt exceeding the context window. +- **Agent runs but doesn't commit** — ralphify doesn't commit for the agent. Add explicit commit instructions to your prompt and ensure the agent has git permission (`--dangerously-skip-permissions` for Claude Code). +- **Loop runs too fast / no useful work** — the prompt is likely too vague or has no concrete task source. Tell the agent it's in a loop with no memory, and point it at `TODO.md`, `PLAN.md`, or failing tests. +- **Output not streaming** — press `p` to toggle live peek (you may have silenced it). Streaming is disabled in non-TTY environments (piped/CI) by design; use `--log-dir`. If output appears in bursts, set `PYTHONUNBUFFERED=1`. + +### Frontmatter errors + +- **"Invalid YAML in frontmatter"** — usually a missing colon, bad indentation, or an unquoted special character (`:`, `#`, `{`, `[`). Wrap risky values in quotes. +- **"Frontmatter must be a YAML mapping"** — frontmatter must be `key: value` pairs, not a bare string or list. +- **"Each command must have 'name' and 'run' fields"** — every command needs both `name` and `run`, each a non-empty string. +- **"Malformed 'agent' field"** — usually an unmatched quote in the `agent` value. +- **"'credit' must be true or false"** — `credit` accepts only YAML booleans (`true`/`false`), not `"yes"` or `0`. +- **"... name contains invalid characters"** — command and arg names may only contain letters, digits, hyphens, and underscores. +- **"Duplicate arg name" / "Duplicate command name"** — names must be unique within a `RALPH.md`. +- **"'commands' must be a list" / "'args' must be a list of strings"** — use list syntax (`args: [focus]`; `commands:` as a list of `{name, run}` mappings). Quote `args` items that look like numbers or booleans. +- **"Command '...' has invalid timeout"** — `timeout` must be a positive number of seconds. + +### Argument errors + +- **"Positional argument '...' requires args declared in frontmatter"** — either add an `args` field, or use `--name value` flag syntax (which works without declaration). +- **"Too many positional arguments"** — you passed more positional values than the `args` field declares. +- **"Flag '--...' requires a value"** — a `--name` flag was passed without a value. + +### Command errors + +- **"Command '...' has invalid syntax"** — malformed shell syntax in `run`, usually an unmatched quote. Point it at a script for complex quoting. +- **"Command '...' binary not found"** — the binary isn't installed or on PATH. If it's in a virtualenv, prefix with `uv run`. +- **Pipes or redirections not working** — `run` is parsed with `shlex` and run directly, so `|`, `2>&1`, `&&`, and `$VAR` won't work. Wrap them in a `.sh` script and point the command at it. +- **Output looks truncated** — the command hit its 60s timeout. Increase it (`timeout: 300`) or speed up the command. +- **Command output missing from prompt** — check the placeholder name matches the command `name` exactly, that the command produces output, and that you used `commands` (plural). + +### Common questions + +- **Run multiple loops in parallel?** Yes, on **separate branches** to avoid git conflicts (`git checkout -b feature-a && ralph run ...`). For programmatic control, use the Python [`RunManager`](#concurrent-runs-with-runmanager). +- **What to commit?** Commit `RALPH.md` and any helper `*.sh` scripts. Add `ralph_logs/` to `.gitignore`. +- **Edit RALPH.md while running?** Yes — the prompt body is re-read each iteration. Frontmatter needs a restart. +- **Disable the co-author credit?** Set `credit: false` in frontmatter. + +If your problem isn't here, run `ralph run my-ralph -n 1` to validate, capture a run with `--log-dir`, or file an issue at [github.com/computerlovetech/ralphify](https://github.com/computerlovetech/ralphify/issues). + +--- + +## Python API + +All public API is available from the top-level `ralphify` package. It runs the same loop as the CLI, so everything you can do with `ralph run` you can do from Python. + +```python +from pathlib import Path +from ralphify import run_loop, RunConfig, RunState + +config = RunConfig( + agent="claude -p --dangerously-skip-permissions", + ralph_dir=Path("my-ralph"), + ralph_file=Path("my-ralph/RALPH.md"), + commands=[], + max_iterations=3, +) +state = RunState(run_id="my-run") +run_loop(config, state) +``` + +### `run_loop(config, state, emitter=None)` + +The main loop. Reads `RALPH.md`, runs commands, assembles prompts, pipes them to the agent, and repeats. **Blocks until the loop finishes.** + +| Parameter | Type | Description | +|---|---|---| +| `config` | `RunConfig` | All settings for the run | +| `state` | `RunState` | Observable state — counters, status, control methods | +| `emitter` | `EventEmitter \| None` | Event listener. `None` uses `NullEmitter` (silent) | + +### `RunConfig` + +All settings for a single run. Fields match the [CLI options](#ralph-run). + +| Field | Type | Default | Description | +|---|---|---|---| +| `agent` | `str` | — | Full agent command string | +| `ralph_dir` | `Path` | — | Path to the ralph directory | +| `ralph_file` | `Path` | — | Path to the RALPH.md file | +| `commands` | `list[Command]` | `[]` | Commands to run each iteration | +| `args` | `dict[str, str]` | `{}` | User argument values | +| `max_iterations` | `int \| None` | `None` | Max iterations (`None` = unlimited) | +| `delay` | `float` | `0` | Seconds to wait between iterations | +| `timeout` | `float \| None` | `None` | Max seconds per iteration | +| `stop_on_error` | `bool` | `False` | Stop loop if agent exits non-zero or times out | +| `log_dir` | `Path \| None` | `None` | Directory for iteration log files | +| `credit` | `bool` | `True` | Append co-author trailer instruction to prompt | +| `project_root` | `Path` | `Path(".")` | Project root directory | + +`RunConfig` is mutable — change fields mid-run and the loop picks them up at the next iteration boundary. + +### `Command` + +```python +from ralphify import Command + +cmd = Command(name="tests", run="uv run pytest -x") +cmd_slow = Command(name="integration", run="uv run pytest tests/integration", timeout=300) +``` + +`name` and `run` are required; `timeout` defaults to `60` seconds. + +### `RunState` + +Observable state for a running loop. Thread-safe control methods let you stop, pause, and resume from another thread. + +| Property | Type | Description | +|---|---|---| +| `run_id` | `str` | Unique identifier for this run | +| `status` | `RunStatus` | Current lifecycle status | +| `iteration` | `int` | Current iteration number (starts at 0) | +| `completed` | `int` | Number of successful iterations | +| `failed` | `int` | Number of failed iterations (includes timed out) | +| `timed_out_count` | `int` | Number of timed-out iterations (subset of `failed`) | +| `total` | `int` | `completed + failed` | +| `started_at` | `datetime \| None` | When the run started | +| `paused` | `bool` | Whether the run is currently paused | +| `stop_requested` | `bool` | Whether a stop has been requested | + +`timed_out_count` is a **subset** of `failed`, so `completed + failed == total`. Control methods: `state.request_stop()`, `state.request_pause()`, `state.request_resume()`. + +### `RunStatus` + +`PENDING`, `RUNNING`, `PAUSED`, `STOPPED`, `COMPLETED`, `FAILED`. `FAILED` means stopped by `--stop-on-error` after a failed/timed-out iteration, or crashed with an unhandled exception. + +### `StopReason` + +A `Literal` used in the `RUN_STOPPED` event's `reason` field: `"completed"`, `"error"`, or `"user_requested"`. + +### Event system + +The loop emits structured events at each step. Implement the `EventEmitter` protocol (a single `emit(event)` method) to listen. + +```python +from pathlib import Path +from ralphify import Event, EventType, RunConfig, RunState, run_loop + + +class MyEmitter: + def emit(self, event: Event) -> None: + if event.type == EventType.ITERATION_COMPLETED: + print(f"Iteration {event.data['iteration']} done in {event.data['duration_formatted']}") + + +config = RunConfig( + agent="claude -p --dangerously-skip-permissions", + ralph_dir=Path("my-ralph"), + ralph_file=Path("my-ralph/RALPH.md"), + commands=[], + max_iterations=3, +) +run_loop(config, RunState(run_id="observed-run"), emitter=MyEmitter()) +``` + +Each `Event` carries `type` (`EventType`), `run_id`, `data` (dict), and `timestamp`. Use `event.to_dict()` to serialize. + +#### `EventType` reference + +| Group | Event | Data fields | +|---|---|---| +| Run | `RUN_STARTED` | `ralph_name`, `commands`, `max_iterations`, `timeout`, `delay` | +| Run | `RUN_STOPPED` | `reason` (`StopReason`), `total`, `completed`, `failed`, `timed_out_count` | +| Run | `RUN_PAUSED` / `RUN_RESUMED` | — | +| Iteration | `ITERATION_STARTED` | `iteration` | +| Iteration | `ITERATION_COMPLETED` | `iteration`, `returncode`, `duration`, `duration_formatted`, `detail`, `log_file`, `result_text` | +| Iteration | `ITERATION_FAILED` | same as `ITERATION_COMPLETED` | +| Iteration | `ITERATION_TIMED_OUT` | same (`returncode` is `None`) | +| Commands | `COMMANDS_STARTED` / `COMMANDS_COMPLETED` | `iteration`, `count` | +| Prompt | `PROMPT_ASSEMBLED` | `iteration`, `prompt_length` | +| Agent | `AGENT_ACTIVITY` | `iteration`, `raw` (one stream-json line; Claude Code only) | +| Agent | `AGENT_OUTPUT_LINE` | `iteration`, `line`, `stream` (`"stdout"`/`"stderr"`; all agents) | +| Other | `LOG_MESSAGE` | `message`, `level`, `traceback` (optional) | + +#### Built-in emitters + +| Emitter | Description | +|---|---| +| `NullEmitter` | Discards all events. Default when no emitter is passed. | +| `QueueEmitter` | Pushes events into a `queue.Queue` for async consumption. | +| `FanoutEmitter` | Broadcasts each event to multiple emitters. | +| `BoundEmitter` | Wraps any emitter with a fixed `run_id`; has `log_info`, `log_error`, and `agent_output_line` convenience methods. | + +```python +from ralphify import QueueEmitter, FanoutEmitter, BoundEmitter + +q_emitter = QueueEmitter() +run_loop(config, state, emitter=q_emitter) +while not q_emitter.queue.empty(): + print(q_emitter.queue.get().to_dict()) + +fanout = FanoutEmitter([q_emitter, MyEmitter()]) +bound = BoundEmitter(q_emitter, run_id="my-run") +bound(EventType.ITERATION_STARTED, {"iteration": 1}) +``` + +### Concurrent runs with `RunManager` + +`RunManager` is a thread-safe registry for launching and controlling multiple loops concurrently. Each run gets its own daemon thread and event queue. + +```python +from pathlib import Path +from ralphify import RunManager, RunConfig, Command + +manager = RunManager() +config = RunConfig( + agent="claude -p --dangerously-skip-permissions", + ralph_dir=Path("docs-ralph"), + ralph_file=Path("docs-ralph/RALPH.md"), + commands=[Command(name="build", run="mkdocs build --strict")], + max_iterations=5, +) +run = manager.create_run(config) +manager.start_run(run.state.run_id) + +for r in manager.list_runs(): + print(f"{r.state.run_id}: {r.state.status.value} — {r.state.completed} done") + +manager.pause_run(run.state.run_id) +manager.resume_run(run.state.run_id) +manager.stop_run(run.state.run_id) +``` + +Each run is wrapped in a `ManagedRun` bundling `config`, `state`, and a `QueueEmitter`. Register extra listeners **before** `start_run()` with `run.add_listener(listener)` — events are then broadcast to both the queue and your listeners via a `FanoutEmitter`. -- [How it Works](how-it-works.md) — what happens inside each iteration -- [Quick Reference](quick-reference.md) — condensed cheat sheet for daily use -- [Python API](api.md) — run loops programmatically instead of via the CLI +`RunManager` methods: `create_run(config)`, `start_run(run_id)`, `stop_run(run_id)`, `pause_run(run_id)`, `resume_run(run_id)`, `list_runs()`, `get_run(run_id)`. diff --git a/docs/contributing/index.md b/docs/contributing/index.md index a340e10..40f9229 100644 --- a/docs/contributing/index.md +++ b/docs/contributing/index.md @@ -85,7 +85,7 @@ INFO - Building documentation to directory: site INFO - Documentation built in 1.91 seconds ``` -The `--strict` flag treats warnings as errors. The CI pipeline uses this flag, so make sure your changes build cleanly before submitting. For guidance on what each docs page should cover, see [Keeping docs surfaces in sync](../quick-reference.md) and the existing page structure. +The `--strict` flag treats warnings as errors. The CI pipeline uses this flag, so make sure your changes build cleanly before submitting. For guidance on what each docs page should cover, see the [Reference](../cli.md) page and the existing page structure. ## Working on the website @@ -160,5 +160,5 @@ Docs deploy automatically to GitHub Pages on every push to `main` that changes f - [Codebase Map](codebase-map.md) — architecture overview and module-by-module guide - [CLI Reference](../cli.md) — understand the commands you'll be extending -- [Python API](../api.md) — the public API surface that contributors maintain +- [Python API](../cli.md#python-api) — the public API surface that contributors maintain - [Changelog](../changelog.md) — see what's been released and what's in progress diff --git a/docs/cookbook.md b/docs/cookbook.md deleted file mode 100644 index df13e9c..0000000 --- a/docs/cookbook.md +++ /dev/null @@ -1,275 +0,0 @@ ---- -title: Ralph Loop Recipes -description: Copy-pasteable ralph loop setups for autonomous ML research, test coverage, code migration, security scanning, deep research, documentation, bug fixing, and codebase improvement. -keywords: loop engineering examples, ralphify cookbook, autonomous coding recipes, RALPH.md examples, documentation loop, bug fixing loop, codebase improvement, deep research agent, code migration loop, security scanning agent, test coverage automation, autoresearch, autonomous ML research ---- - -# Cookbook - -!!! tldr "TL;DR" - 8 copy-pasteable ralph loops: [autoresearch](#autoresearch), [codebase improvement](#codebase-improvement), [documentation](#documentation-loop), [bug hunting](#bug-hunter), [deep research](#deep-research), [code migration](#code-migration), [security scanning](#security-scan), and [test coverage](#test-coverage). Each is a real, runnable example from the `examples/` directory. - -Once you know [the five things you do with ralphify](index.md#the-five-things-you-do-with-ralphify), these are starting points to copy. Each recipe is a real, runnable ralph loop from the [`examples/`](https://github.com/computerlovetech/ralphify/tree/main/examples) directory. - -All recipes use **Claude Code** as the agent. To use a different agent, swap the `agent` field — see [Using with Different Agents](agents.md). - -!!! tip "User arguments in recipes" - Many recipes accept CLI arguments like `--focus` or `--target`. These aren't built-in flags — they're **user arguments** declared in each recipe's `args` field. When you pass `--focus "test coverage"`, the value replaces `{{ args.focus }}` in the prompt. See [User Arguments](cli.md#user-arguments) for details. - ---- - -## Run autonomous ML experiments {: #autoresearch } - -An autonomous ML research loop inspired by [Karpathy's autoresearch](https://github.com/karpathy/autoresearch). The agent runs experiments on a training script to minimize validation loss — modifying code, training for 5 minutes, keeping improvements and reverting failures. This recipe uses helper scripts as commands to surface experiment state each iteration. - -**`autoresearch/RALPH.md`** - -```markdown ---8<-- "examples/autoresearch/RALPH.md" -``` - -??? note "Helper scripts that surface experiment state each iteration" - - **`autoresearch/show-results.sh`** - - ```bash - --8<-- "examples/autoresearch/show-results.sh" - ``` - - **`autoresearch/show-last-run.sh`** - - ```bash - --8<-- "examples/autoresearch/show-last-run.sh" - ``` - -```bash -ralph run autoresearch --train_script train.py --prepare_script prepare.py -``` - -```text -▶ Running: autoresearch - 2 commands · unlimited iterations - -── Iteration 1 ── - Commands: 2 ran -✓ Iteration 1 completed (312.4s) - -── Iteration 2 ── - Commands: 2 ran -✓ Iteration 2 completed (287.1s) -``` - -The `train_script` and `prepare_script` args let you point the ralph at any autoresearch-style project. The agent handles everything autonomously: establishing a baseline on the first iteration, then running experiments indefinitely. Each iteration is one hypothesis tested — modify the train script, train, evaluate, keep or revert. - ---- - -## Improve code quality continuously {: #codebase-improvement } - -A loop that continuously improves code quality without changing functionality. It runs [commands](quick-reference.md#command-fields) (tests, type checking, lint) each iteration to give the agent a [self-healing feedback loop](how-it-works.md#how-broken-code-gets-fixed-automatically), then picks one improvement to make. - -**`improve-codebase/RALPH.md`** - -```markdown ---8<-- "examples/improve-codebase/RALPH.md" -``` - -```bash -ralph run improve-codebase -n 5 --focus "focus on test coverage" --log-dir ralph_logs -``` - -```text -▶ Running: improve-codebase - 3 commands · max 5 iterations - -── Iteration 1 ── - Commands: 3 ran -✓ Iteration 1 completed (48.2s) - → ralph_logs/001_20250115-142301.log - -── Iteration 2 ── - Commands: 3 ran -✓ Iteration 2 completed (55.7s) - → ralph_logs/002_20250115-143112.log -``` - ---- - -## Write and improve documentation automatically {: #documentation-loop } - -A loop focused on writing and improving documentation. - -**`docs/RALPH.md`** - -```markdown ---8<-- "examples/docs/RALPH.md" -``` - -```bash -ralph run docs --focus "focus on the API reference pages" --log-dir ralph_logs -``` - -```text -▶ Running: docs - 1 command · unlimited iterations - -── Iteration 1 ── - Commands: 1 ran -✓ Iteration 1 completed (63.5s) - → ralph_logs/001_20250120-091502.log -``` - ---- - -## Find and fix bugs automatically {: #bug-hunter } - -A loop that discovers bugs and fixes them. The agent reads the codebase, finds a real bug (edge case, off-by-one, missing validation), writes a failing test to prove it, then fixes it. - -**`bug-hunter/RALPH.md`** - -```markdown ---8<-- "examples/bug-hunter/RALPH.md" -``` - -```bash -ralph run bug-hunter -n 5 --focus "focus on input validation" --log-dir ralph_logs -``` - -```text -▶ Running: bug-hunter - 1 command · max 5 iterations - -── Iteration 1 ── - Commands: 1 ran -✓ Iteration 1 completed (71.3s) - → ralph_logs/001_20250118-103045.log -``` - ---- - -## Run structured AI research loops {: #deep-research } - -A structured research loop that builds up a report over many iterations. Uses shell scripts as commands to track maturity, show the question tree, and even run an editorial review agent that gives feedback between iterations. - -This is a more advanced ralph — it uses [`args`](cli.md#user-arguments) for the research topic, helper scripts (run with `./` [relative to the ralph directory](how-it-works.md#2-run-commands-and-capture-output)), and a [`timeout`](how-it-works.md#2-run-commands-and-capture-output) on the review command. - -**`research/RALPH.md`** - -```markdown ---8<-- "examples/research/RALPH.md" -``` - -??? note "Helper scripts — `show-focus.sh`, `show-questions.sh`, `review.sh`" - The helper scripts read from the workspace files and surface key state. The `review.sh` script pipes the full workspace to a separate Claude call that acts as an editorial reviewer — giving the research agent targeted feedback each iteration. - -```bash -ralph run research --workspace ai-safety --focus "current approaches to AI alignment" -``` - -```text -▶ Running: research - 4 commands · unlimited iterations - -── Iteration 1 ── - Commands: 4 ran -✓ Iteration 1 completed (185.6s) -``` - -This recipe shows several advanced patterns: commands that call scripts relative to the ralph directory (`./show-focus.sh`), a command with a `timeout`, a command that itself calls an AI agent (`review.sh` pipes to `claude -p`), and `args` used in the prompt body via [`{{ args.workspace }}` placeholders](how-it-works.md#3-resolve-placeholders-with-command-output). - ---- - -## Migrate code patterns across a codebase {: #code-migration } - -A loop for batch code transformations — migrating from one pattern to another across a codebase. The `remaining` command counts how many files still need migration, giving the agent a clear finish line. Use [`--stop-on-error`](cli.md#ralph-run) to halt the loop once all files are migrated. - -**`migrate/RALPH.md`** - -```markdown ---8<-- "examples/migrate/RALPH.md" -``` - -??? note "`count-remaining.sh` — tracks migration progress" - The script receives the pattern as an argument (resolved from `{{ args.old_pattern }}` in the `run` field) to find files that still need migration: - - ```bash - --8<-- "examples/migrate/count-remaining.sh" - ``` - -```bash -ralph run migrate --old_pattern "from utils import legacy_helper" \ - --new_pattern "from core.helpers import modern_helper" -``` - -```text -▶ Running: migrate - 1 command · unlimited iterations - -── Iteration 1 ── - Commands: 1 ran -✓ Iteration 1 completed (34.8s) -``` - -The `remaining` command gives the agent a shrinking counter and a list of files still needing attention, so it always knows where to focus next. - ---- - -## Automate security scanning and fixes {: #security-scan } - -An iterative security review loop. The agent runs a scanner each iteration, picks one finding, fixes it, and verifies the fix. Good for systematically hardening a codebase. Use [`-n`](cli.md#ralph-run) to limit iterations and [`--log-dir`](cli.md#ralph-run) to keep an audit trail. - -**`security/RALPH.md`** - -```markdown ---8<-- "examples/security/RALPH.md" -``` - -```bash -ralph run security -n 10 --log-dir ralph_logs -``` - -```text -▶ Running: security - 1 command · max 10 iterations - -── Iteration 1 ── - Commands: 1 ran -✓ Iteration 1 completed (42.9s) - → ralph_logs/001_20250122-160830.log -``` - -Swap `bandit` for your scanner of choice — `semgrep`, `npm audit`, `cargo audit`, etc. The pattern works the same: scan, pick a finding, fix it, log it. - ---- - -## Increase test coverage automatically {: #test-coverage } - -A loop that systematically increases test coverage. The agent sees the current coverage percentage and a list of uncovered functions, then writes tests for one module per iteration. The coverage command output feeds into the prompt via [`{{ commands.coverage }}`](how-it-works.md#3-resolve-placeholders-with-command-output) so the agent always knows where to focus. - -**`test-coverage/RALPH.md`** - -```markdown ---8<-- "examples/test-coverage/RALPH.md" -``` - -```bash -ralph run test-coverage -n 5 --target "focus on error handling paths" --log-dir ralph_logs -``` - -```text -▶ Running: test-coverage - 2 commands · max 5 iterations - -── Iteration 1 ── - Commands: 2 ran -✓ Iteration 1 completed (56.1s) - → ralph_logs/001_20250125-140210.log -``` - -The coverage report gives the agent a clear metric to improve and shows exactly which lines are missing, so it always knows where to focus. - ---- - -## Next steps - -- [CLI Reference](cli.md) — all `ralph run` options (`--timeout`, `--stop-on-error`, `--delay`, user args) -- [Troubleshooting](troubleshooting.md) — when the agent hangs, commands fail, or output looks wrong diff --git a/docs/how-it-works.md b/docs/how-it-works.md deleted file mode 100644 index 9beb68f..0000000 --- a/docs/how-it-works.md +++ /dev/null @@ -1,189 +0,0 @@ ---- -title: How Loop Engineering Works — The Ralph Loop Lifecycle -description: Step-by-step breakdown of loop engineering — what happens inside each ralph loop iteration — command execution, prompt assembly, agent piping, and the self-healing feedback cycle that auto-fixes broken code. -keywords: loop engineering, how loop engineering works, autonomous coding loop lifecycle, how AI coding agents work, self-healing code loop, AI agent feedback cycle, prompt assembly pipeline, ralph loop architecture, agentic coding workflow ---- - -# How the ralph loop works - -!!! tldr "TL;DR" - Each iteration: re-read `RALPH.md` from disk → run commands → replace `{{ placeholders }}` with output → pipe the assembled prompt to the agent → agent works and exits → repeat. The prompt body is re-read every iteration (so you can edit it live), but frontmatter is parsed once at startup. Failed commands still capture output — that's what makes the loop self-healing. - -Writing a loop is becoming a discipline of its own — *loop engineering*. This page shows what one iteration of a ralph loop actually does: the format you write, and the runtime that runs it. It breaks down the lifecycle — from command execution to prompt assembly to agent piping — so you can write better prompts, debug unexpected behavior, and understand the self-healing feedback cycle. - -## The six steps of each iteration - -Every iteration follows the same sequence. Here's what happens at each step. - -### 1. Re-read the prompt from disk - -The prompt body (everything below the frontmatter) is read from disk **every iteration**. This means you can edit the prompt text — add rules, change the task, adjust constraints — while the loop is running. Changes take effect on the next cycle. - -Frontmatter fields (`agent`, `commands`, `args`) are parsed once at startup. To change those, restart the loop. - -### 2. Run commands and capture output - -Each command defined in the `commands` frontmatter runs in order and captures its output (stdout + stderr). Commands run from the **project root** by default. Commands starting with `./` run relative to the **ralph directory** instead — useful for scripts bundled alongside your `RALPH.md`. - -Command output is captured **regardless of exit code** — a command like `pytest -x` exits non-zero when tests fail, but its output is exactly what you want in the prompt. - -Each command has a default timeout of 60 seconds. If a command takes longer, the process is killed and the output captured so far is used. Override this with the `timeout` field for slow commands (e.g., a large test suite): - -```yaml -commands: - - name: tests - run: uv run pytest -x - timeout: 300 # 5 minutes -``` - -### 3. Resolve placeholders with command output - -Each `{{ commands. }}` placeholder in the prompt body is replaced with the corresponding command's output. **Only commands referenced by a placeholder appear in the prompt** — if you define a command but don't use `{{ commands. }}` for it, the command still runs every iteration but its output is excluded. This forces you to place data deliberately rather than accidentally dumping everything into the prompt. - -Placeholders for `{{ args. }}` are replaced with user argument values from the CLI — both in the prompt body and in command `run` strings. - -`{{ ralph.* }}` placeholders provide runtime metadata — no frontmatter configuration needed: - -- `{{ ralph.name }}` — the ralph's directory name (e.g. `my-ralph`) -- `{{ ralph.iteration }}` — current iteration number (1-based) -- `{{ ralph.max_iterations }}` — total iterations if `-n` was set, empty otherwise - -These are useful for iteration-aware prompts — for example, telling the agent to wrap up on the last iteration, or including the ralph name in commit messages. - -Unmatched placeholders resolve to an empty string — you won't see raw `{{ }}` text in the assembled prompt. - -### 4. Assemble the final prompt - -The prompt body (everything below the YAML frontmatter in `RALPH.md`) with all placeholders resolved becomes the fully assembled prompt — a single text string ready for the agent. - -HTML comments (``) are stripped during assembly — they never reach the agent. Use them for notes to yourself, like why a rule exists or what to change next. - -By default, ralphify appends a **co-author trailer instruction** to the end of the prompt, asking the agent to include `Co-authored-by: Ralphify ` in its commit messages. This gives visibility into which commits were produced by a ralph loop. To disable it, set `credit: false` in the frontmatter. - -### 5. Pipe the prompt to the AI agent - -The assembled prompt is piped to the agent command via stdin: - -```bash -echo "" | claude -p --dangerously-skip-permissions -``` - -The agent reads the prompt, does work in the current directory (edits files, runs commands, makes commits), and exits. Ralphify waits for the agent process to finish. - -When the agent command starts with `claude`, ralphify automatically adds `--output-format stream-json --verbose` to enable structured streaming. This lets ralphify track agent activity in real time — you don't need to configure this yourself. - -### 6. Loop back with fresh context - -The loop starts the next iteration from step 1. The RALPH.md is re-read, commands run again with fresh output, and the agent gets a new prompt reflecting the current state of the codebase. - -## What gets re-read vs. what stays fixed - -| What | When read | Why it matters | -|---|---|---| -| Prompt body | Every iteration | Edit the prompt while the loop runs — the next iteration follows your new instructions | -| Command output | Every iteration | The agent always sees fresh data (latest git log, current test status, etc.) | -| Frontmatter (`agent`, `commands`, `args`) | Once at startup | Parsed when the loop starts. Restart to pick up changes. | -| User arguments | Once at startup | Passed via CLI flags, constant for the run | - -## How broken code gets fixed automatically - -Here's a concrete example. Given this `RALPH.md`: - -```markdown ---- -agent: claude -p --dangerously-skip-permissions -commands: - - name: git-log - run: git log --oneline -5 - - name: tests - run: uv run pytest -x ---- - -# Prompt - -## Recent commits - -{{ commands.git-log }} - -## Test results - -{{ commands.tests }} - -Read TODO.md and implement the next task. -If tests are failing, fix them first. -``` - -### Iteration 1 (tests passing) - -The assembled prompt piped to the agent: - -```markdown -# Prompt - -## Recent commits - -a1b2c3d feat: add user model -e4f5g6h fix: resolve database connection timeout -i7j8k9l docs: update API reference - -## Test results - -============================= 5 passed in 1.23s ============================== - -Read TODO.md and implement the next task. -If tests are failing, fix them first. -``` - -### Iteration 2 (tests broken by iteration 1) - -```markdown -# Prompt - -## Recent commits - -x1y2z3a feat: add login endpoint -a1b2c3d feat: add user model -e4f5g6h fix: resolve database connection timeout - -## Test results - -FAILED tests/test_auth.py::test_login - AssertionError: expected 200, got 401 -============================= 1 failed, 4 passed in 1.45s ==================== - -Read TODO.md and implement the next task. -If tests are failing, fix them first. -``` - -The agent sees the test failure and the instruction to fix it first. This is the **self-healing feedback loop**: the agent breaks something, the command captures the failure, and the agent sees it in the next iteration. - -## Command execution order and failure handling - -Commands run in the order they appear in the `commands` list in the RALPH.md frontmatter. All commands run regardless of whether earlier commands fail. - -```yaml -commands: - - name: tests # Runs first - run: uv run pytest -x - - name: lint # Runs second - run: uv run ruff check . - - name: git-log # Runs third - run: git log --oneline -10 -``` - -## How to stop the loop (Ctrl+C, limits, and errors) - -The loop continues until one of these happens: - -| Condition | What happens | -|---|---| -| `Ctrl+C` (first) | Gracefully finishes the current iteration, then stops the loop. The agent completes its work and the iteration result is recorded. | -| `Ctrl+C` (second) | Force-stops immediately — kills the agent process and exits. Use when you don't want to wait for the current iteration to finish. | -| `-n` limit reached | Loop stops after completing the specified number of iterations | -| `--stop-on-error` and agent exits non-zero or times out | Loop stops after the current iteration | -| `--timeout` exceeded | Agent process is killed, iteration is marked as timed out, loop continues (unless `--stop-on-error`) | - -## Next steps - -- [Getting Started](getting-started.md) — set up your first loop -- [CLI Reference](cli.md) — all commands and options -- [Troubleshooting](troubleshooting.md) — when things don't work as expected diff --git a/docs/quick-reference.md b/docs/quick-reference.md deleted file mode 100644 index 479c0ea..0000000 --- a/docs/quick-reference.md +++ /dev/null @@ -1,233 +0,0 @@ ---- -title: "RALPH.md Syntax and CLI Cheat Sheet — Ralphify Quick Reference" -description: "Cheat sheet for ralphify — RALPH.md frontmatter format, CLI flags for ralph run/scaffold, placeholder syntax for commands and args, and common loop patterns you can copy-paste." -keywords: RALPH.md format, RALPH.md frontmatter syntax, ralph run CLI flags, ralphify cheat sheet, AI coding agent loop syntax, ralph commands placeholder, ralph args placeholder, ralph scaffold, autonomous agent loop reference, ralphify quick reference ---- - -# Quick Reference - -Everything you need at a glance. Bookmark this page. - -## CLI commands - -```bash -ralph run my-ralph # Run loop forever (Ctrl+C to stop) -ralph run my-ralph/RALPH.md # Can also pass the file path directly -ralph run my-ralph -n 5 # Run 5 iterations -ralph run my-ralph -n 1 --log-dir logs # Single iteration with output capture -ralph run my-ralph --stop-on-error # Stop if agent exits non-zero or times out -ralph run my-ralph --delay 10 # Wait 10s between iterations -ralph run my-ralph --timeout 300 # Kill agent after 5 min per iteration -ralph run my-ralph --dir ./src # Pass user args to the ralph - -ralph scaffold my-task # Scaffold a ralph from template -ralph scaffold # Scaffold in current directory - -ralph --version # Show version -``` - -Full flag descriptions and examples → [CLI reference](cli.md) - -## Directory structure - -```text -my-ralph/ -└── RALPH.md # Prompt + configuration (required) -``` - -That's it. A ralph is a directory with a `RALPH.md` file. See [Getting Started](getting-started.md) to create your first one. - -## RALPH.md format - -```markdown ---- -agent: claude -p --dangerously-skip-permissions # (1)! -commands: # (2)! - - name: tests - run: uv run pytest -x - - name: lint - run: uv run ruff check . - - name: git-log - run: git log --oneline -10 -args: [dir, focus] # (3)! ---- - -# Prompt body - -{{ commands.git-log }} - -{{ commands.tests }} - -{{ commands.lint }} - -Your instructions here. Use {{ args.dir }} for user arguments. -``` - -1. **Required.** The full shell command to pipe the prompt to. `-p` enables non-interactive mode, `--dangerously-skip-permissions` lets the agent work autonomously. -2. **Optional.** Each command runs every iteration and its output fills the matching `{{ commands. }}` placeholder. -3. **Optional.** Declares positional argument names. Named flags (`--dir`, `--focus`) work without this — `args` is only needed for positional usage. -4. Everything below the `---` frontmatter is the prompt body. It's re-read from disk every iteration, so you can edit it while the loop runs. -5. Replaced with the command's stdout + stderr. Only commands with a matching placeholder appear in the assembled prompt. -6. Replaced with the `--dir` value from the CLI. Missing args resolve to an empty string. - -### Frontmatter fields - -| Field | Type | Required | Description | -|---|---|---|---| -| `agent` | string | yes | Full agent command (piped via stdin). See [agent configuration](agents.md) for supported agents. | -| `commands` | list | no | Commands to run each iteration | -| `args` | list | no | User argument names. Letters, digits, hyphens, and underscores only. | -| `credit` | bool | no | Append co-author trailer instruction to prompt (default: `true`) | - -### Command fields - -| Field | Type | Default | Description | -|---|---|---|---| -| `name` | string | (required) | Identifier for `{{ commands. }}`. Letters, digits, hyphens, and underscores only. | -| `run` | string | (required) | Shell command to execute (supports `{{ args. }}` placeholders). Commands starting with `./` run from the ralph directory; others run from the project root. | -| `timeout` | number | `60` | Max seconds before the command is killed | - -!!! warning "No shell features in commands" - Commands are run directly, not through a shell — pipes (`|`), redirections (`>`), and chaining (`&&`) won't work in the `run` field. Use a script instead: `run: ./my-script.sh` (scripts starting with `./` run from the ralph directory). - -## Placeholders - -### Command placeholders - -```markdown -{{ commands.tests }} -{{ commands.git-log }} -``` - -1. Replaced with the `tests` command's stdout + stderr, regardless of exit code. -2. Replaced with the `git-log` command's output. Only commands referenced by a placeholder appear in the assembled prompt — unreferenced commands still run but their output is excluded. - -- Unmatched placeholders resolve to empty string -- Must be `commands` (plural) - -For details on placeholder resolution, see [How it Works](how-it-works.md#3-resolve-placeholders-with-command-output). - -### User argument placeholders - -```markdown -{{ args.dir }} -{{ args.focus }} -``` - -1. Replaced with the `--dir` value from the CLI. Missing args resolve to empty string. -2. Replaced with the `--focus` value from the CLI. - -- Pass via `ralph run my-ralph --dir ./src --focus "perf"` or `--dir=./src` (named flags) -- Or positionally: `ralph run my-ralph ./src "perf"` (requires `args:` in frontmatter) -- Mixed: `ralph run my-ralph --focus "perf" ./src` — positional args skip names already provided via flags -- `--` ends flag parsing: `ralph run my-ralph -- --verbose ./src` treats `--verbose` as a positional value -- See [CLI reference → User arguments](cli.md#user-arguments) for full details on flag and positional parsing - -### ralph placeholders - -```markdown -{{ ralph.name }} -{{ ralph.iteration }} -{{ ralph.max_iterations }} -``` - -1. Ralph directory name (e.g. `my-ralph`). -2. Current iteration number (1-based). -3. Total iterations if `-n` was set, empty otherwise. - -- Automatically available — no frontmatter configuration needed -- Useful for progress tracking, naming logs, or adjusting behavior near the end of a run -- See [How it Works](how-it-works.md) for more on the loop lifecycle - -## The loop - -Each iteration: - -1. Re-read `RALPH.md` from disk -2. Run all commands in order, capture output -3. Resolve `{{ commands.* }}`, `{{ args.* }}`, and `{{ ralph.* }}` placeholders -4. Pipe assembled prompt to agent via stdin -5. Wait for agent to exit -6. Repeat - -### Stopping the loop - -| Action | What happens | -|---|---| -| `Ctrl+C` (once) | Finishes the current iteration gracefully, then stops | -| `Ctrl+C` (twice) | Force-kills the agent process and exits immediately | -| `p` | Toggle live peek of the agent's stdout (on by default in an interactive terminal — press to silence, press again to resume) | -| `P` (shift+p) | Open full-screen peek — scroll the buffer and browse iterations. `j/k` line, `space/b` page, `g/G` top/bottom, `[/]` prev/next iteration, `q` or `P` exits | -| `-n` limit reached | Stops after the specified number of iterations | -| `--stop-on-error` | Stops if agent exits non-zero or times out | - -## Live editing - -- The prompt body is re-read from disk every iteration — edit the prompt while the loop runs (frontmatter is parsed once at startup) -- HTML comments (``) are stripped from the prompt — use them for notes to yourself - -## Common patterns - -### Minimal ralph - -```markdown ---- -agent: claude -p --dangerously-skip-permissions ---- - -Read TODO.md and implement the next task. Commit when done. -``` - -### Self-healing with test feedback - -```markdown ---- -agent: claude -p --dangerously-skip-permissions -commands: - - name: tests - run: uv run pytest -x ---- - -{{ commands.tests }} - -Fix failing tests before starting new work. -Read TODO.md and implement the next task. -``` - -### Parameterized ralph - -```markdown ---- -agent: claude -p --dangerously-skip-permissions -args: [dir, focus] ---- - -Research the codebase at {{ args.dir }}. -Focus area: {{ args.focus }}. -``` - -```bash -ralph run research --dir ./api --focus "error handling" -``` - -### Debug a single iteration - -```bash -ralph run my-ralph -n 1 --log-dir ralph_logs -cat ralph_logs/001_*.log -``` - -### Run on a branch - -```bash -git checkout -b feature && ralph run my-ralph -``` - -More patterns and real-world examples → [Cookbook](cookbook.md) - -## Next steps - -- [Getting Started](getting-started.md) — set up your first ralph end-to-end -- [How it Works](how-it-works.md) — what happens inside each iteration -- [Cookbook](cookbook.md) — copy-pasteable ralphs for common tasks -- [Troubleshooting](troubleshooting.md) — common issues and how to fix them diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md deleted file mode 100644 index a981fdd..0000000 --- a/docs/troubleshooting.md +++ /dev/null @@ -1,571 +0,0 @@ ---- -title: "Fix Agent Hangs, Command Failures, and RALPH.md Errors — Ralphify Troubleshooting" -description: "Solve common ralphify issues: agent hangs or produces no output, RALPH.md frontmatter syntax errors, commands with pipes not working, missing placeholders, argument parsing failures, and iteration timeouts." -keywords: ralphify troubleshooting, fix agent hang, agent produces no output, RALPH.md frontmatter error, invalid YAML frontmatter, command timeout ralphify, command pipes not working shlex, ralph run error, agent command not found, debug ralph loop, fix autonomous coding agent, placeholder not resolving, ralph argument parsing ---- - -# Troubleshooting - -!!! tldr "Quick checklist" - 1. Run `ralph run my-ralph -n 1` — it validates your setup and shows clear errors - 2. Test the agent outside ralphify: `echo "Say hello" | claude -p` - 3. Use `--log-dir ralph_logs` to capture output for debugging - 4. Commands don't support shell features (pipes, `&&`) — use a wrapper script instead - -Common issues and how to fix them. If your problem isn't listed here, run [`ralph run`](cli.md#ralph-run) with `-n 1` — it validates your setup and shows clear errors. - -## Setup issues - -### "is not a directory, RALPH.md file, or installed ralph" - -The path you passed to [`ralph run`](cli.md#ralph-run) doesn't resolve to a valid ralph. The command accepts a **directory** containing `RALPH.md`, a **direct path** to a `RALPH.md` file, or the **name of an installed ralph** in `.agents/ralphs/`: - -```bash -ralph run my-ralph # directory containing RALPH.md -ralph run my-ralph/RALPH.md # direct path to the file -ralph run my-ralph # installed ralph in .agents/ralphs/my-ralph/ -``` - -If you're getting this error, check that the path exists and points to the right place: - -```bash -ls my-ralph/RALPH.md # local ralph -ls .agents/ralphs/my-ralph/RALPH.md # installed ralph -``` - -### "Missing or empty 'agent' field in RALPH.md frontmatter" - -Your `RALPH.md` frontmatter is missing the `agent` field or it's an empty string. Add it: - -```markdown ---- -agent: claude -p --dangerously-skip-permissions ---- -``` - -### "Agent command 'claude' not found on PATH" - -The agent CLI isn't installed or isn't in your shell's PATH. Verify by running `claude --version` directly. If it's installed but not found, check your PATH. See [supported agents](agents.md) for setup instructions. - -## Loop issues - -### Agent produces no output or seems to hang - -Try running the [agent command](agents.md) directly to see if it works outside of ralphify: - -```bash -echo "Say hello" | claude -p -``` - -If it hangs there too, the issue is with the agent CLI, not ralphify. If it works standalone but hangs via ralphify, try adding `--timeout` to kill stalled iterations: - -```bash -ralph run my-ralph --timeout 300 -``` - -### Agent exits non-zero every iteration - -Check the agent's output to understand why. Use `--log-dir` to capture output: - -```bash -ralph run my-ralph -n 1 --log-dir ralph_logs -cat ralph_logs/001_*.log -``` - -Common causes: - -- The agent CLI requires authentication that hasn't been set up -- The prompt asks the agent to run a command that fails -- The agent's context window is being exceeded by a very large prompt - -If you want the loop to stop on errors instead of continuing, use `--stop-on-error`: - -```bash -ralph run my-ralph --stop-on-error --log-dir ralph_logs -``` - -### Agent runs but doesn't commit - -Ralphify doesn't commit for the agent — committing is the agent's responsibility. Make sure your prompt includes explicit commit instructions: - -```markdown -## Process -- Run tests before committing -- Commit with a descriptive message like `feat: add X` -``` - -Also ensure the agent has permission to run git commands. With Claude Code, the `--dangerously-skip-permissions` flag handles this. - -### Loop runs too fast / agent not doing anything useful - -If iterations finish in seconds with no meaningful work, the agent may be exiting without taking action. Check the logs with [`--log-dir`](cli.md#ralph-run): - -```bash -ralph run my-ralph -n 1 --log-dir ralph_logs -cat ralph_logs/001_*.log -``` - -Common causes: - -- The prompt is too vague — tell the agent it's in a loop with no memory between iterations, and give it a specific task -- There's no concrete task source — point the prompt at something like `TODO.md`, `PLAN.md`, or failing tests -- The agent can't find what it's supposed to work on - -### Agent output not streaming to the terminal - -In an interactive terminal, agent output streams live by default. If you don't see any output between the iteration markers: - -- **Check the peek toggle** — press `p` to toggle live output on or off. You may have silenced it in a previous iteration. -- **Non-TTY environments** — live streaming is disabled when output is piped, redirected, or running in CI. This is intentional — use `--log-dir` to capture output instead. -- **Output appears in bursts** — some runtimes block-buffer stdout when piped. Set `PYTHONUNBUFFERED=1` in your environment to force line-buffered output. -- **Full-screen TUI agents** — agents that repaint their own terminal (curses-based tools) are not supported for live streaming. They detect a non-TTY and fall back to plain output, which may be minimal. - -## Frontmatter issues - -### "Invalid YAML in frontmatter" - -Your YAML frontmatter has a syntax error — usually a missing colon, bad indentation, or an unquoted special character. The error message includes the YAML parser's details: - -```text -Invalid YAML in frontmatter: while parsing a block mapping ... -``` - -Common fixes: - -```yaml -# ✗ Wrong — missing colon, bad indentation -agent claude -p -commands: -- name: tests -run: uv run pytest - -# ✓ Correct -agent: claude -p -commands: - - name: tests - run: uv run pytest -``` - -If your value contains special YAML characters (`:`, `#`, `{`, `[`), wrap it in quotes: - -```yaml -agent: "claude -p --dangerously-skip-permissions" -``` - -### "Frontmatter must be a YAML mapping" - -The frontmatter parsed as valid YAML but isn't a key-value mapping. This usually happens when the frontmatter is a plain string or a list instead of a mapping: - -```yaml -# ✗ Wrong — this is a plain string, not a mapping ---- -claude -p --dangerously-skip-permissions ---- - -# ✓ Correct — key: value pairs ---- -agent: claude -p --dangerously-skip-permissions ---- -``` - -### "Each command must have 'name' and 'run' fields" - -A command entry in `commands` is missing a required key. Every command needs both `name` and `run`: - -```yaml -# ✗ Wrong — missing 'run' -commands: - - name: tests - -# ✗ Wrong — missing 'name' -commands: - - run: uv run pytest -x - -# ✓ Correct -commands: - - name: tests - run: uv run pytest -x -``` - -The related error **"Command '...' must be a non-empty string"** means a `name` or `run` value is empty or not a string. - -### "Malformed 'agent' field in RALPH.md frontmatter" - -The `agent` value couldn't be parsed as a shell command — usually an unmatched quote: - -```yaml -# ✗ Wrong — unclosed quote -agent: claude -p "dangerously-skip-permissions - -# ✓ Correct -agent: claude -p --dangerously-skip-permissions -``` - -### "'credit' must be true or false" - -The `credit` field only accepts boolean values. YAML bare words `true`/`false` work, but quoted strings don't: - -```yaml -# ✗ Wrong -credit: "yes" -credit: 0 - -# ✓ Correct -credit: false -credit: true -``` - -### "Command name contains invalid characters" / "Arg name contains invalid characters" - -Command names and arg names may only contain letters, digits, hyphens, and underscores (`a-z`, `A-Z`, `0-9`, `-`, `_`). Names with dots, spaces, or special characters are rejected because they can't be used in [`{{ commands. }}` or `{{ args. }}` placeholders](how-it-works.md#3-resolve-placeholders-with-command-output). - -```yaml -# ✗ Wrong — dots and spaces aren't allowed -args: [my.focus, test subject] -commands: - - name: my.tests - run: uv run pytest -x - - name: test suite - run: uv run pytest -x - -# ✓ Correct — use hyphens or underscores -args: [my-focus, test_subject] -commands: - - name: my-tests - run: uv run pytest -x - - name: test_suite - run: uv run pytest -x -``` - -### "Duplicate arg name" / "Duplicate command name" - -Arg names and command names must be unique within a single `RALPH.md`. Duplicates are rejected at startup: - -```yaml -# ✗ Wrong — "tests" appears twice -commands: - - name: tests - run: uv run pytest -x - - name: tests - run: uv run pytest tests/integration - -# ✓ Correct — use distinct names -commands: - - name: unit-tests - run: uv run pytest -x - - name: integration-tests - run: uv run pytest tests/integration -``` - -The same applies to `args` — each name must appear only once. - -### "'commands' must be a list" or "'args' must be a list of strings" - -YAML scalars and lists look similar. A common mistake is writing a plain string where a list is expected: - -```yaml -# ✗ Wrong — this is a string, not a list -args: focus -commands: uv run pytest -x - -# ✓ Correct — use list syntax -args: [focus] -commands: - - name: tests - run: uv run pytest -x -``` - -`args` must be a list of strings (`[focus]` or `- focus`). `commands` must be a list of `{name, run}` mappings. - -### "'args' items must be strings, got non-string value" - -Every item in the `args` list must be a string. YAML can silently coerce bare values into numbers or booleans: - -```yaml -# ✗ Wrong — YAML reads 42 as an integer, true as a boolean -args: [focus, 42, true] - -# ✓ Correct — quote non-string-looking values, or just use plain names -args: [focus, count, enabled] -``` - -### "Command '...' has invalid timeout" - -The `timeout` field on a command must be a positive number (in seconds). Strings, zero, and negative values are rejected: - -```yaml -# ✗ Wrong -commands: - - name: tests - run: uv run pytest -x - timeout: "five minutes" - -# ✓ Correct -commands: - - name: tests - run: uv run pytest -x - timeout: 300 -``` - -## Argument issues - -### "Positional argument '...' requires args declared in RALPH.md frontmatter" - -You passed a bare value (not a `--name value` flag) to [`ralph run`](cli.md#ralph-run), but your RALPH.md doesn't have an `args` field. Either declare the args or use the `--name value` syntax: - -```bash -# ✗ Fails — no args declared, so positional values aren't allowed -ralph run my-ralph "error handling" - -# ✓ Option A — declare args in RALPH.md, then pass positionally -# (add `args: [focus]` to frontmatter) -ralph run my-ralph "error handling" - -# ✓ Option B — use flag syntax (works even without args declared) -ralph run my-ralph --focus "error handling" -``` - -### "Too many positional arguments" - -You passed more positional values than the `args` field declares: - -```bash -# RALPH.md has: args: [focus] -# ✗ Fails — only 1 arg declared, but 2 values given -ralph run my-ralph "error handling" "api module" - -# ✓ Correct — one positional value per declared arg -ralph run my-ralph "error handling" -``` - -### "Flag '--...' requires a value" - -A `--name` flag was passed without a value: - -```bash -# ✗ Fails — --focus has no value -ralph run my-ralph --focus - -# ✓ Correct -ralph run my-ralph --focus "error handling" -``` - -## Command issues - -### "Command '...' has invalid syntax" - -A command's `run` string has malformed shell syntax — usually an unmatched quote. The error message tells you which command failed: - -```text -Command 'tests' has invalid syntax: 'uv run pytest -x "unclosed'. Check the 'commands' field in your RALPH.md frontmatter. -``` - -Fix the quoting in the `run` value. If your command needs complex quoting, point it at a script instead — see [Command with pipes or redirections not working](#command-with-pipes-or-redirections-not-working). - -### "Command '...' binary not found" - -A command in your `commands` field references a binary that isn't installed or isn't on your PATH. The error message tells you which command failed: - -```text -Command 'lint' binary not found: 'mypy src/'. Check the 'commands' field in your RALPH.md frontmatter. -``` - -Verify the binary exists by running it directly: - -```bash -mypy --version -``` - -If it's installed in a virtual environment, prefix the command with `uv run` or the appropriate runner: - -```yaml -commands: - - name: lint - run: uv run mypy src/ -``` - -### Command with pipes or redirections not working - -Commands in the `run` field are parsed with `shlex` and run **directly** — not through a shell. Shell features like pipes (`|`), redirections (`2>&1`), chaining (`&&`), and variable expansion (`$VAR`) silently fail or produce unexpected results. The escape hatch is a script — wrap the shell features inside a `.sh` file and point the command at it. - -**Won't work:** - -```yaml -commands: - - name: tests - run: pytest --tb=line -q 2>&1 | tail -20 -``` - -**Fix:** Point the command at a script instead: - -```bash -#!/bin/bash -# scripts/run-tests.sh -pytest --tb=line -q 2>&1 | tail -20 -``` - -```bash -chmod +x scripts/run-tests.sh -``` - -```yaml -commands: - - name: tests - run: scripts/run-tests.sh -``` - -Commands without a `./` prefix run from the project root, so `scripts/run-tests.sh` resolves to `/scripts/run-tests.sh`. If you want to bundle the script next to your `RALPH.md`, use the `./` prefix instead — see [Working directory](how-it-works.md#2-run-commands-and-capture-output) for details. - -### Command always failing - -Run the command manually to see if it works: - -```bash -uv run pytest -x -``` - -If the command fails manually, the issue isn't with ralphify — fix the underlying test/lint failures first. - -Note that command output is included in the prompt **regardless of exit code**. A failing test command is often exactly what you want — the agent sees the failure and fixes it. - -### Command output looks truncated - -Each command has a default timeout of **60 seconds** (see [Run commands and capture output](how-it-works.md#2-run-commands-and-capture-output)). If your command takes longer (a large test suite, a slow build), it's killed at the timeout and only the output captured so far is used. The agent sees a notice appended to the output: - -```text -[Command 'tests' timed out after 60s — output may be incomplete] -``` - -**Fix:** Increase the timeout for slow commands: - -```yaml -commands: - - name: tests - run: uv run pytest -x - timeout: 300 # 5 minutes -``` - -You can also speed up the command itself — for example, running a subset of tests or filtering output via a [wrapper script](#command-with-pipes-or-redirections-not-working). - -### Command output missing from prompt - -If a `{{ commands.my-command }}` placeholder produces nothing in the prompt: - -1. Check the command name matches exactly: `{{ commands.my-command }}` requires a command with `name: my-command` -2. Verify the command produces output by running it manually -3. Must be `commands` (plural) — `{{ command.name }}` won't resolve - -??? note "No output visible during iteration" - Agent output streams live to the terminal by default — press `p` to toggle it on or off. If you're using `--log-dir`, output is also echoed after each iteration completes. See [Agent output not streaming](#agent-output-not-streaming-to-the-terminal) for more details. - -??? note "CLI flag validation errors" - ### "'-n' must be a positive integer" - - The `-n` flag sets how many iterations to run. It must be at least 1: - - ```bash - # ✗ Wrong - ralph run my-ralph -n 0 - - # ✓ Correct - ralph run my-ralph -n 1 - ``` - - ### "'--delay' must be non-negative" - - The `--delay` flag sets seconds to wait between iterations. It can be zero but not negative: - - ```bash - # ✗ Wrong - ralph run my-ralph --delay -5 - - # ✓ Correct - ralph run my-ralph --delay 0 - ralph run my-ralph --delay 30 - ``` - - ### "'--timeout' must be a positive number" - - The `--timeout` flag sets the per-iteration time limit in seconds. It must be greater than zero: - - ```bash - # ✗ Wrong - ralph run my-ralph --timeout 0 - - # ✓ Correct - ralph run my-ralph --timeout 300 - ``` - -## Common questions - -### Can I run multiple loops in parallel? - -Yes, but they should work on **separate branches** to avoid git conflicts: - -```bash -# Terminal 1 -git checkout -b feature-a && ralph run feature-a-ralph - -# Terminal 2 -git checkout -b feature-b && ralph run feature-b-ralph -``` - -For programmatic control over concurrent runs, use the [Python API's `RunManager`](api.md#concurrent-runs-with-runmanager). - -### What files should I commit? - -| File / directory | Commit? | Why | -|---|---|---| -| `my-ralph/RALPH.md` | **Yes** | The ralph definition | -| `my-ralph/*.sh` | **Yes** | Helper scripts referenced by commands | -| `ralph_logs/` | **No** | Iteration logs — add to `.gitignore` | - -### Can I edit RALPH.md while the loop runs? - -Yes. The prompt body (everything below the frontmatter) is re-read every iteration — edit the prompt text and changes take effect on the next cycle. Frontmatter fields (`agent`, `commands`, `args`) are parsed once at startup, so changing those requires restarting the loop. - -### How do I disable the co-author credit in commits? - -By default, ralphify appends an instruction asking the agent to add `Co-authored-by: Ralphify ` to commit messages. To disable it, set `credit: false` in your RALPH.md frontmatter: - -```yaml ---- -agent: claude -p --dangerously-skip-permissions -credit: false ---- -``` - -### How do I make a ralph reusable across projects? - -Use `args` to parameterize your ralph instead of hardcoding project-specific values: - -```markdown ---- -agent: claude -p --dangerously-skip-permissions -args: [dir, focus] -commands: - - name: tests - run: uv run pytest {{ args.dir }} ---- - -Focus on {{ args.dir }}. Priority: {{ args.focus }} -``` - -```bash -ralph run my-ralph --dir ./api --focus "error handling" -ralph run my-ralph --dir ./frontend --focus "accessibility" -``` - -See [User arguments](cli.md#user-arguments) for more on parameterizing ralphs. - -## Getting more help - -1. Run `ralph run my-ralph -n 1` to validate your setup — it shows clear errors -2. Use `ralph run my-ralph -n 1 --log-dir ralph_logs` to capture a single iteration for debugging -3. Check the [CLI reference](cli.md) for all available options -4. File an issue at [github.com/computerlovetech/ralphify](https://github.com/computerlovetech/ralphify/issues) - -## Next steps - -- [How it works](how-it-works.md) — understand the iteration lifecycle to debug more effectively -- [Cookbook](cookbook.md) — working examples you can adapt for your project diff --git a/mkdocs.yml b/mkdocs.yml index 9f4365a..5a77d82 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -108,18 +108,9 @@ extra: nav: - Home: index.md - - Guide: - - Getting Started: getting-started.md - - How it Works: how-it-works.md - - Using with Different Agents: agents.md - - Cookbook: cookbook.md - - Reference: - - Quick Reference: quick-reference.md - - CLI: cli.md - - Python API: api.md - - Help: - - Troubleshooting: troubleshooting.md - - Changelog: changelog.md + - Getting Started: getting-started.md + - Reference: cli.md + - Changelog: changelog.md - Contributing: - contributing/index.md - Codebase Map: contributing/codebase-map.md