Run Claude in a loop. Like Ralph Wiggum, the defaults are insane (unlimited iterations, unlimited time).
# Build from source
make build
# Or install to GOPATH/bin
make install
# Or go install directly
go install github.com/hev/ralph/cmd/ralph@latestralph [OPTIONS] -h, --help Show this help message
-p, --prompt FILE Path to prompt file (default: ./prompt.md)
-n, --max-iterations N Max loop iterations (default: 0 = unlimited)
-t, --max-time SECONDS Max total runtime in seconds (default: 0 = unlimited)
-d, --agent-dir DIR Scratchpad directory (default: ./.agent)
-c, --cooldown SECONDS Delay between iterations (default: 1)
-q, --quiet Disable verbose output
--dry-run Show what would run without executing
--config FILE Path to config file (default: ./ralph.yaml)
-v, --version Show version
OTEL Options:
--otel-enabled Enable metrics export (default: false)
--otel-endpoint URL OTLP endpoint (default: localhost:4317)
--metrics-prefix PREFIX Metric name prefix (default: ralph)
--project-name NAME Override project label (default: cwd basename)
Worktree Options:
-w, --worktree Run in a git worktree (default: false)
-b, --branch NAME Branch name for worktree (default: auto-generate)
-k, --keep-worktree Keep worktree after completion (default: false)
# Run forever with defaults
ralph
# Run for 5 iterations
ralph -n 5
# Run for 1 hour
ralph -t 3600
# Use custom prompt file
ralph -p ~/tasks/build.md
# 10 iterations, 5s cooldown
ralph -n 10 -c 5
# With metrics enabled
ralph --otel-enabled --otel-endpoint localhost:4317
# Use custom config file
ralph --config ~/myconfig.yaml
# Run in a worktree (auto-generated branch)
ralph -w
# Run in worktree with specific branch
ralph -w -b feature/my-task
# Run in worktree, keep after completion
ralph -w -kRalph supports a two-tier configuration system with global and local config files.
- Global config:
~/.config/ralph/ralph.yaml- Shared settings across all projects - Local config:
./ralph.yaml- Project-specific overrides
Both files are loaded automatically (global first, then local). Local values override global values.
You can also specify a single config file with --config (bypasses two-tier loading).
Global (~/.config/ralph/ralph.yaml):
# Shared across all projects
slack:
enabled: true
bot_token: xoxb-... # Your bot token - shared across projectsLocal (./ralph.yaml):
# Project-specific settings
slack:
channel: C0123456789 # This project's channel
notify_users: U0123,U0456 # Users to notify for this project# Core options
prompt: ./prompt.md
max_iterations: 10
max_time: 3600
agent_dir: ./.agent
cooldown: 5
verbose: true
# OpenTelemetry
otel:
enabled: true
endpoint: localhost:4317
metrics_prefix: ralph
project_name: my-project
# Slack notifications
slack:
enabled: true
webhook_url: https://hooks.slack.com/services/...
bot_token: xoxb-...
channel: C0123456789
notify_users: U0123,U0456
# Custom scratchpad instructions (replaces the default)
scratchpad_prompt: "Use {{.AgentDir}} for notes. Track tasks in {{.AgentDir}}/TODO.md."Configuration is loaded in this order (later sources override earlier):
- Default values
- Global config file (
~/.config/ralph/ralph.yaml) - Local config file (
./ralph.yaml) - Environment variables (for Slack options)
- Command-line flags
Ralph runs Claude Code in a loop with --dangerously-skip-permissions. Each iteration:
- Loads your prompt file
- Appends scratchpad instructions (use
.agent/TODO.mdfor tracking) - Runs Claude with streaming JSON output
- Parses and displays colored output
- Waits for cooldown period
- Repeats until limits reached or interrupted
The scratchpad instructions tell Claude to:
- Use the agent directory as a scratchpad
- Track progress in
TODO.mdusing checkboxes (- [ ]pending,- [-]in-progress,- [x]done) - Work on one task at a time and focus on minimal context
- Run tests before and after changes (no regressions allowed)
- Keep todo-item artifacts under
.agent/items/<item-name>/and clean up when done - Commit after completing and cleaning up each item
- If reviewing a fresh plan, validate ordering and dependencies
- If reviewing a completed plan, verify implementation and add improvement ideas
- Keep
prompt.mdup to date for the next agent iteration
You can customize these instructions with the scratchpad_prompt config option. Use {{.AgentDir}} as a placeholder for the agent directory path.
Ralph can export metrics to OpenTelemetry for monitoring in Grafana.
| Metric | Type | Description |
|---|---|---|
ralph_iterations_total |
Counter | Total iterations completed |
ralph_iteration_duration_seconds |
Histogram | Time per iteration |
ralph_session_duration_seconds |
Gauge | Current session runtime |
ralph_commits_total |
Counter | Git commits made during session |
ralph_todos_pending |
Gauge | Current pending todo items |
ralph_todos_completed |
Gauge | Current completed todo items |
ralph_claude_errors_total |
Counter | Claude execution errors |
ralph_active_sessions |
Gauge | Currently running ralph instances |
All metrics include project and session_id labels.
# Start OTEL collector, Prometheus, and Grafana
make up
# Run ralph with metrics
make run-otel
# View Grafana dashboard
open http://localhost:3000
# Login: admin/adminmake downRalph can send notifications to Slack when sessions start, todos are completed, and sessions end.
There are two ways to configure Slack notifications:
Use an Incoming Webhook for basic notifications. This method posts messages but doesn't support threaded replies.
- Create a Slack app at https://api.slack.com/apps
- Enable Incoming Webhooks and create a webhook for your channel
- Copy the webhook URL
export RALPH_SLACK_WEBHOOK_URL="https://hooks.slack.com/services/T.../B.../xxx"
ralph --slack-enabledUse a bot token for full functionality including threaded replies for todo completions.
- Create a Slack app at https://api.slack.com/apps
- Under OAuth & Permissions, add these scopes:
chat:write- Send messageschat:write.public- Post to channels without joining
- Install the app to your workspace
- Copy the Bot User OAuth Token (starts with
xoxb-) - Get the channel ID (right-click channel > View channel details > copy ID at bottom)
export RALPH_SLACK_BOT_TOKEN="xoxb-..."
export RALPH_SLACK_CHANNEL="C0123456789"
ralph --slack-enabled| Flag | Environment Variable | Description |
|---|---|---|
--slack-enabled |
- | Enable Slack notifications |
--slack-webhook-url |
RALPH_SLACK_WEBHOOK_URL |
Webhook URL for posting messages |
--slack-bot-token |
RALPH_SLACK_BOT_TOKEN |
Bot token for API access |
--slack-channel |
RALPH_SLACK_CHANNEL |
Channel ID (required with bot token) |
--slack-notify-users |
RALPH_SLACK_NOTIFY_USERS |
Comma-separated user IDs to @mention on completion |
Session Start - Posted when ralph begins:
- Project name and GitHub URL (if available)
- tmux session name (if running in tmux)
- Session ID and configured limits
Todo Completed (bot token only) - Threaded reply when a todo item is checked off:
- Todo text that was completed
- Progress (X/Y complete)
- Iteration number, commit count, duration
Session End - Summary when ralph finishes:
- Total iterations, duration, commits
- Todo completion percentage
- Exit reason (completed, max iterations, timeout, interrupted)
- @mentions configured users
# Basic webhook setup
ralph --slack-enabled --slack-webhook-url "https://hooks.slack.com/services/..."
# Full bot setup with user notifications
ralph --slack-enabled \
--slack-bot-token "xoxb-..." \
--slack-channel "C0123456789" \
--slack-notify-users "U0123456789,U9876543210"
# Using environment variables
export RALPH_SLACK_BOT_TOKEN="xoxb-..."
export RALPH_SLACK_CHANNEL="C0123456789"
export RALPH_SLACK_NOTIFY_USERS="U0123456789"
ralph --slack-enabledRalph can optionally run in a git worktree for better isolation. This is opt-in; the default "insane" mode runs in the current directory.
- Isolation: Each Ralph session works in its own directory
- Parallel sessions: Multiple Ralphs can work on different branches simultaneously
- Clean state: Fresh worktree avoids contamination from previous work
- Easy cleanup: Just delete the worktree when done
# Default: run in current directory (insane mode)
ralph
# Run in a worktree (auto-generated branch name)
ralph --worktree
ralph -w
# Specify branch name
ralph --worktree --branch feature/my-task
ralph -w -b feature/my-task
# Keep worktree after completion (skip cleanup)
ralph --worktree --keep-worktree
ralph -w -kWhen worktree mode is enabled:
- Creates a new git worktree at
/tmp/ralph-worktrees/<branch-name> - Creates a new branch (or uses existing if specified)
- Copies the prompt file to the worktree
- Changes to the worktree directory
- Runs Claude in the worktree context
- On completion: pushes the branch to remote
- Removes the worktree (unless
--keep-worktree)
# ralph.yaml
worktree:
enabled: false # Default: insane mode (no worktree)
base_dir: /tmp/ralph-worktrees # Where to create worktrees
branch_prefix: ralph/ # Prefix for auto-generated branch names
cleanup: true # Delete worktree on completion| Flag | Short | Description |
|---|---|---|
--worktree |
-w |
Enable worktree mode |
--branch NAME |
-b |
Branch name for worktree (default: auto-generate) |
--keep-worktree |
-k |
Keep worktree after completion |
ralph/
├── cmd/ralph/main.go # CLI entry point
├── internal/
│ ├── config/config.go # Configuration
│ ├── runner/runner.go # Main loop logic
│ ├── claude/
│ │ ├── client.go # Claude process execution
│ │ └── parser.go # JSON stream parser
│ ├── metrics/
│ │ ├── collector.go # OTEL metrics setup
│ │ └── tracker.go # Metric tracking helpers
│ ├── slack/
│ │ ├── client.go # Slack API client
│ │ ├── messages.go # Message formatting
│ │ └── notifier.go # High-level notification logic
│ ├── todo/parser.go # TODO.md parsing
│ ├── git/tracker.go # Git commit counting
│ └── worktree/worktree.go # Git worktree management
├── grafana/
│ └── ralph-dashboard.json # Grafana dashboard
├── docker-compose.yml # Observability stack
├── otel-collector-config.yaml
├── prometheus.yml
└── Makefile
# Build
make build
# Run tests
make test
# Clean
make clean
# Build for all platforms
make build-all"I'm in danger!" - Ralph Wiggum
