| name | ralph-dashboard |
|---|---|
| description | Monitor and control Ralph Loop AI agent sessions via the Ralph Dashboard REST API. Use to check project status, view iterations/stats, start/stop/pause loops, inject instructions, manage plans/specs, browse git history, and run notification-driven recovery workflows before escalating to a human/user. |
Interact with a Ralph Dashboard instance to monitor and control Ralph Loop sessions.
If the dashboard isn't running yet, follow these steps. If it's already deployed, skip to Setup.
# 1. Clone and install
git clone https://github.com/Endogen/ralph-dashboard.git
cd ralph-dashboard
# 2. Install dashboard + CLI
./scripts/install.sh
# If the CLI is not found, add the wrapper directory to PATH
export PATH="$HOME/.local/bin:$PATH"
# 3. Create runtime config and first user (interactive)
ralph-dashboard init
# 4. Validate environment
ralph-dashboard doctor
# 5. Start the dashboard (choose one)
# 5a. Linux/systemd: install + start user service
ralph-dashboard service install --user --start
# 5b. macOS/manual: run uvicorn directly
set -a
source ~/.config/ralph-dashboard/env
set +a
cd backend
.venv/bin/uvicorn app.main:app --host 127.0.0.1 --port "$RALPH_PORT"For production deployment with systemd and nginx, see the README.
To use the API, you need:
- Base URL of the dashboard (e.g.
https://ralph.example.comorhttp://localhost:8420) - Username and password for JWT authentication
Store credentials somewhere the agent can access them (e.g. TOOLS.md, env vars, or a config file). Never hardcode passwords in scripts.
All API endpoints (except /api/health, /api/auth/login, /api/auth/refresh) require a Bearer JWT token.
curl -s -X POST "$BASE_URL/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"username":"USER","password":"PASS"}' | jqResponse:
{
"access_token": "eyJ...",
"refresh_token": "eyJ...",
"token_type": "bearer"
}Use access_token in all subsequent requests as Authorization: Bearer <access_token>.
When the access token expires, refresh it:
curl -s -X POST "$BASE_URL/api/auth/refresh" \
-H "Content-Type: application/json" \
-d '{"refresh_token":"eyJ..."}' | jqReturns a new access_token.
TOKEN="<access_token>"
AUTH="Authorization: Bearer $TOKEN"curl -s "$BASE_URL/api/health" | jq
# {"status": "ok"}curl -s -H "$AUTH" "$BASE_URL/api/projects" | jqResponse: array of project summaries:
[
{
"id": "my-project",
"name": "my-project",
"path": "/home/user/projects/my-project",
"status": "running"
}
]Status values: running, paused, stopped, complete.
The id is a slug derived from the directory name. Use it in all project-specific endpoints.
curl -s -H "$AUTH" "$BASE_URL/api/projects/{id}" | jqcurl -s -X POST -H "$AUTH" -H "Content-Type: application/json" \
"$BASE_URL/api/projects" \
-d '{"path":"/path/to/project"}' | jqThe project directory must contain a .ralph/ subdirectory.
curl -s -X DELETE -H "$AUTH" "$BASE_URL/api/projects/{id}"curl -s -H "$AUTH" "$BASE_URL/api/projects/{id}/iterations?status=all&limit=50&offset=0" | jqQuery params:
status:all(default),success,errorlimit: 1–500 (default 50)offset: pagination offset
Response:
{
"iterations": [
{
"number": 5,
"max_iterations": 50,
"start_timestamp": "2026-02-08T01:23:41+01:00",
"end_timestamp": "2026-02-08T01:26:00+01:00",
"duration_seconds": 139,
"tokens_used": 62.698,
"status": "success",
"has_errors": false,
"errors": [],
"tasks_completed": ["1.5"],
"commit": "abc1234",
"test_passed": true
}
],
"total": 15
}curl -s -H "$AUTH" "$BASE_URL/api/projects/{id}/iterations/{number}" | jqReturns the same fields as above plus log_output (string with full terminal output for that iteration).
curl -s -H "$AUTH" "$BASE_URL/api/projects/{id}/stats" | jqResponse:
{
"total_iterations": 15,
"total_tokens": 940.5,
"total_cost_usd": 5.64,
"total_duration_seconds": 2100,
"avg_iteration_duration_seconds": 140,
"avg_tokens_per_iteration": 62.7,
"tasks_done": 12,
"tasks_total": 18,
"errors_count": 2,
"projected_completion": "2026-02-08T04:00:00+01:00",
"projected_total_cost_usd": 8.46,
"velocity": {
"tasks_per_hour": 3.4,
"tasks_remaining": 6,
"hours_remaining": 1.76
},
"health_breakdown": {
"productive": 12,
"partial": 1,
"failed": 2
},
"tokens_by_phase": [
{"phase": "Phase 1: Core", "tokens": 450.2}
]
}curl -s -H "$AUTH" "$BASE_URL/api/projects/{id}/report" | jq -r '.content'Returns a human-readable markdown report of the project's progress.
curl -s -H "$AUTH" "$BASE_URL/api/projects/{id}/system" | jqResponse:
{
"process": {
"pid": 12345,
"rss_mb": 85.2,
"children_rss_mb": 312.4,
"total_rss_mb": 397.6,
"cpu_percent": 12.5,
"child_count": 3
},
"system": {
"ram_total_mb": 16384.0,
"ram_used_mb": 8192.0,
"ram_available_mb": 8192.0,
"ram_percent": 50.0,
"cpu_load_1m": 1.2,
"cpu_load_5m": 0.8,
"cpu_load_15m": 0.6,
"cpu_core_count": 8,
"disk_total_gb": 500.0,
"disk_used_gb": 120.0,
"disk_free_gb": 380.0,
"disk_percent": 24.0,
"uptime_seconds": 864000.0
}
}Process metrics are gathered from the PID in .ralph/ralph.pid. If the loop isn't running, process.pid is null and memory/CPU values are 0.
curl -s -H "$AUTH" "$BASE_URL/api/projects/{id}/plan" | jqReturns the parsed IMPLEMENTATION_PLAN.md with phases, tasks, completion status.
curl -s -X PUT -H "$AUTH" -H "Content-Type: application/json" \
"$BASE_URL/api/projects/{id}/plan" \
-d '{"content":"# Implementation Plan\n\n## Phase 1\n- [ ] Task 1\n- [x] Task 2\n"}' | jqcurl -s -X POST -H "$AUTH" -H "Content-Type: application/json" \
"$BASE_URL/api/projects/{id}/start" \
-d '{"max_iterations":50,"cli":"codex","flags":"--full-auto","test_command":"pytest"}' | jqAll fields are optional — defaults come from .ralph/config.json.
Set max_iterations to 0 for unlimited iterations.
Response:
{
"project_id": "my-project",
"pid": 12345,
"command": ["./scripts/ralph.sh", "50"]
}curl -s -X POST -H "$AUTH" "$BASE_URL/api/projects/{id}/stop" | jq
# {"stopped": true}curl -s -X POST -H "$AUTH" "$BASE_URL/api/projects/{id}/pause" | jq
# {"paused": true}curl -s -X POST -H "$AUTH" "$BASE_URL/api/projects/{id}/resume" | jq
# {"resumed": true}curl -s -X POST -H "$AUTH" -H "Content-Type: application/json" \
"$BASE_URL/api/projects/{id}/inject" \
-d '{"message":"Use async SQLAlchemy sessions instead of sync"}' | jqThe message is written to .ralph/inject.md and automatically appended to AGENTS.md at the start of the next iteration.
curl -s -H "$AUTH" "$BASE_URL/api/projects/{id}/config" | jqResponse:
{
"cli": "codex",
"flags": "--full-auto",
"max_iterations": 20,
"test_command": "pytest",
"model_pricing": {"codex": 0.006, "claude": 0.015}
}curl -s -X PUT -H "$AUTH" -H "Content-Type: application/json" \
"$BASE_URL/api/projects/{id}/config" \
-d '{"cli":"claude","flags":"--dangerously-skip-permissions","max_iterations":30,"test_command":"pytest -q","model_pricing":{"claude":0.015}}' | jqmax_iterations: 0 in config means unlimited loop iterations.
curl -s -H "$AUTH" "$BASE_URL/api/projects/{id}/files/agents" | jq
curl -s -H "$AUTH" "$BASE_URL/api/projects/{id}/files/prompt" | jqResponse:
{"name": "AGENTS.md", "content": "# AGENTS.md\n..."}curl -s -X PUT -H "$AUTH" -H "Content-Type: application/json" \
"$BASE_URL/api/projects/{id}/files/agents" \
-d '{"content":"# AGENTS.md\n\nUpdated content..."}' | jqcurl -s -H "$AUTH" "$BASE_URL/api/projects/{id}/specs" | jqResponse:
[
{"name": "overview.md", "size": 1234, "modified": "2026-02-08T01:00:00"}
]curl -s -H "$AUTH" "$BASE_URL/api/projects/{id}/specs/{name}" | jqcurl -s -X POST -H "$AUTH" -H "Content-Type: application/json" \
"$BASE_URL/api/projects/{id}/specs" \
-d '{"name":"auth.md","content":"# Auth Spec\n..."}' | jqcurl -s -X PUT -H "$AUTH" -H "Content-Type: application/json" \
"$BASE_URL/api/projects/{id}/specs/{name}" \
-d '{"content":"# Updated spec\n..."}' | jqcurl -s -X DELETE -H "$AUTH" "$BASE_URL/api/projects/{id}/specs/{name}"curl -s -H "$AUTH" "$BASE_URL/api/projects/{id}/git/log?limit=20&offset=0" | jqcurl -s -H "$AUTH" "$BASE_URL/api/projects/{id}/git/diff/{commit_hash}" | jqcurl -s -H "$AUTH" "$BASE_URL/api/projects/{id}/notifications" | jqResponse:
[
{
"timestamp": "2026-02-08T02:30:00+01:00",
"prefix": "DONE",
"message": "All tasks complete.",
"status": "delivered",
"iteration": 15,
"details": null,
"source": "pending-notification.txt"
}
]Use this section when an orchestration agent sits between Ralph and a human/user.
Goal:
- react automatically to loop notifications
- attempt safe recovery steps
- escalate to a human/user only when automation cannot safely resolve the issue
Ralph writes .ralph/pending-notification.txt, and the dashboard records notification history in .ralph/notifications/events.jsonl.
Treat the dashboard notification history endpoint as the source of truth:
curl -s -H "$AUTH" "$BASE_URL/api/projects/{id}/notifications" | jqWhen a new notification arrives:
- Identify
project_idand latest notification (prefix,message,iteration,details). - Fetch current state:
GET /api/projects/{id}GET /api/projects/{id}/statsGET /api/projects/{id}/iterations?status=all&limit=20GET /api/projects/{id}/iterations/{iteration}(ifiterationis known)
- Apply prefix-based policy:
PROGRESSorDONE: record status, no intervention required.DECISION: ask the human/user for a decision, thenPOST /injectwith approved guidance.ERRORorBLOCKED: attempt bounded auto-recovery (below), then re-check health.
- If recovery fails or confidence is low, escalate to a human/user with concise context and recommended next action.
For ERROR / BLOCKED notifications, use conservative limits:
- max 3 automated recovery attempts per incident
- max 1 restart/resume action per attempt
- require improved signal before next attempt (e.g. no new error in next iteration)
Suggested attempt sequence:
- If project status is
paused, callPOST /api/projects/{id}/resume. - If project status is
stoppedand work is incomplete, callPOST /api/projects/{id}/start. - Inject focused remediation guidance:
POST /api/projects/{id}/inject- include explicit constraints (reproduce failure, isolate root cause, patch minimally, rerun tests, report if still blocked).
- Wait for next iteration/notification and evaluate:
- if healthy progression resumes, clear incident
- if repeated failure persists, escalate to a human/user
When escalating, include:
- project id and current status
- last failing iteration number
- short error summary
- actions already attempted by automation
- clear request for human/user input or decision
PROJECT_ID="my-project"
curl -s -H "$AUTH" "$BASE_URL/api/projects/$PROJECT_ID" | jq
curl -s -H "$AUTH" "$BASE_URL/api/projects/$PROJECT_ID/stats" | jq
curl -s -H "$AUTH" "$BASE_URL/api/projects/$PROJECT_ID/notifications" | jq '.[0]'
curl -s -H "$AUTH" "$BASE_URL/api/projects/$PROJECT_ID/iterations?status=all&limit=5" | jqUse these outputs to decide whether to resume/restart, inject guidance, or escalate.
This section covers the complete workflow for turning a project description into a running Ralph loop monitored by the dashboard. Follow these steps in order.
mkdir -p ~/projects/my-project
cd ~/projects/my-project
git initThe directory must be under one of the paths in RALPH_PROJECT_DIRS (default: ~/projects).
Create a specs/ directory with one or more markdown files describing what to build. Start with an overview, then break down backend and frontend if applicable.
mkdir -p specsspecs/overview.md — the main spec:
# Project Name
## Goal
[Paste or expand the project description into a clear goal statement]
## Tech Stack
### Backend
- Python 3.12+, FastAPI, SQLAlchemy 2.0 (async), SQLite, pytest
### Frontend
- React 19, TypeScript, Vite, Tailwind CSS 4, shadcn/ui
## Success Criteria
- [ ] Criterion 1
- [ ] Criterion 2
## Architecture
[Describe the high-level architecture: directory structure, API design, data models]Add specs/backend.md and specs/frontend.md for detailed requirements if the project is large enough. Be specific about data models, API endpoints, UI components, and behavior.
Tip: The more detailed your specs, the better Ralph performs. Vague specs lead to vague implementations.
Create IMPLEMENTATION_PLAN.md in the project root. This is the task list Ralph works through — it picks the highest-priority incomplete task each iteration.
# Implementation Plan
STATUS: IN PROGRESS
## Phase 1: Project Setup
- [ ] 1.1: Initialize backend project (FastAPI, pyproject.toml, virtual env)
- [ ] 1.2: Create database models
- [ ] 1.3: Initialize frontend project (React, Vite, TypeScript, Tailwind)
## Phase 2: Core Backend
- [ ] 2.1: Implement user authentication
- [ ] 2.2: Implement CRUD endpoints for main resource
- [ ] 2.3: Add search and filtering
## Phase 3: Frontend UI
- [ ] 3.1: Create app layout with navigation
- [ ] 3.2: Build main list/grid view
- [ ] 3.3: Build detail view
## Phase 4: Testing & Polish
- [ ] 4.1: Write backend tests (≥80% coverage)
- [ ] 4.2: Write frontend tests (≥80% coverage)
- [ ] 4.3: Final UI polish and responsive fixesCritical format rules:
- Tasks MUST use
- [ ](unchecked) or- [x](done) checkbox syntax - Task IDs MUST be numeric with dots:
1.1,1.2,2.1, etc. — Ralph uses these to track which tasks were completed in each iteration - Format:
- [ ] 1.1: Description of the task - Group tasks into phases with
## Phase N: Nameheaders - Order tasks by dependency — Ralph works top-to-bottom
- Keep tasks granular (30min–2hr of AI work each)
This file gives the AI agent project context and the commands it needs to run for backpressure (lint, test, build).
# AGENTS.md
## Project
[One-paragraph project description]
## Commands
- **Install backend**: `cd backend && python3 -m venv .venv && source .venv/bin/activate && pip install -e ".[dev]"`
- **Install frontend**: `cd frontend && npm install --legacy-peer-deps`
- **Test backend**: `cd backend && .venv/bin/pytest tests/ -q --tb=short`
- **Test frontend**: `cd frontend && npx vitest run --reporter=verbose`
- **Build frontend**: `cd frontend && npm run build`
- **Lint backend**: `cd backend && .venv/bin/ruff check . --fix`
## Backpressure
Run after each implementation:
1. Lint (if backend changes)
2. Run tests (if backend changes)
3. Type check (if frontend changes)
4. Build (if frontend changes)
## Architecture Notes
[Key architectural decisions the agent should know about]
## Learnings
*(Agent appends operational learnings here during the loop)*The Backpressure section is important — these are the commands Ralph's agent runs after each change to catch errors before committing. If tests fail, the agent retries before moving on.
This is the prompt fed to the AI tool every iteration. It tells the agent what to do and how to behave.
# Ralph BUILDING Loop
## Goal
[What you're building — 1-2 sentences]
## Context
- Read: specs/*.md for detailed requirements
- Read: IMPLEMENTATION_PLAN.md for the current task list
- Read: AGENTS.md for project context, commands, and learnings
## Rules
1. Pick the highest priority incomplete task from IMPLEMENTATION_PLAN.md
2. Investigate relevant code before changing
3. Implement the task fully
4. Run backpressure commands from AGENTS.md
5. If tests pass: commit with clear message, mark task done in IMPLEMENTATION_PLAN.md
6. If tests fail: try to fix (max 3 attempts), then notify
7. Update AGENTS.md with any operational learnings
## Tech Stack
[List exact versions so the agent doesn't guess]
## Notifications
When you need input or hit a blocker, write to .ralph/pending-notification.txt:
```json
{"prefix":"ERROR","message":"Brief description","details":"Full context..."}Prefixes: DECISION, ERROR, BLOCKED, PROGRESS, DONE
When all tasks are done, add STATUS: COMPLETE to IMPLEMENTATION_PLAN.md.
### Step 6: Initial commit
```bash
git add -A
git commit -m "initial project setup with specs and plan"
Ralph needs a git repo — it commits after each successful iteration and tracks diffs.
Option A: Via the dashboard API (if dashboard is running):
# Login first
TOKEN=$(curl -s -X POST "$BASE_URL/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"username":"USER","password":"PASS"}' | jq -r '.access_token')
# Start the loop
curl -s -X POST "$BASE_URL/api/projects/my-project/start" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"max_iterations": 0, "cli": "codex", "flags": "--full-auto"}' | jqOption B: Directly via ralph.sh:
cd ~/projects/my-project
/path/to/ralph.sh --max-iterations 0 --cli codex --full-autoThe loop creates .ralph/ automatically on first run. The dashboard picks up the project within seconds.
Once running, use the dashboard API to monitor progress:
# Check stats
curl -s -H "Authorization: Bearer $TOKEN" "$BASE_URL/api/projects/my-project/stats" | \
jq '{tasks: "\(.tasks_done)/\(.tasks_total)", iterations: .total_iterations, cost: .total_cost_usd}'
# Inject instructions if the agent needs guidance
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
"$BASE_URL/api/projects/my-project/inject" \
-d '{"message":"Use async SQLAlchemy sessions, not sync."}'
# Check for notifications (agent asking for help)
curl -s -H "Authorization: Bearer $TOKEN" "$BASE_URL/api/projects/my-project/notifications" | jqmy-project/
├── specs/
│ └── overview.md # What to build (detailed)
├── IMPLEMENTATION_PLAN.md # Phased task checklist
├── AGENTS.md # Build commands + context
├── PROMPT.md # Loop prompt template
└── .git/ # Must be a git repo
Ralph creates .ralph/ on first run. Everything else (iterations.jsonl, ralph.log, config.json) is generated automatically.
curl -s -H "$AUTH" "$BASE_URL/api/projects" | jq '[.[] | select(.status == "running")]'# Project status + stats in two calls
PROJECT_ID="my-project"
curl -s -H "$AUTH" "$BASE_URL/api/projects/$PROJECT_ID" | jq
curl -s -H "$AUTH" "$BASE_URL/api/projects/$PROJECT_ID/stats" | jq '{tasks: "\(.tasks_done)/\(.tasks_total)", iterations: .total_iterations, cost: .total_cost_usd, velocity: .velocity.tasks_per_hour, errors: .errors_count}'When the agent asks a question (DECISION/QUESTION notification), answer it:
curl -s -X POST -H "$AUTH" -H "Content-Type: application/json" \
"$BASE_URL/api/projects/$PROJECT_ID/inject" \
-d '{"message":"Decision: Use PostgreSQL instead of SQLite for the database layer."}' | jq# Get total iterations, then fetch the last one
TOTAL=$(curl -s -H "$AUTH" "$BASE_URL/api/projects/$PROJECT_ID/iterations?limit=1" | jq '.total')
curl -s -H "$AUTH" "$BASE_URL/api/projects/$PROJECT_ID/iterations/$TOTAL" | jq -r '.log_output'curl -s -H "$AUTH" "$BASE_URL/api/projects/$PROJECT_ID/report" | jq -r '.content'The dashboard automatically discovers projects by scanning configured directories (default: ~/projects) for any subdirectory containing .ralph/. No manual registration is needed — just ensure your project has a .ralph/ directory and lives under a scanned path.
Projects can also be registered manually via the API if they live outside the scanned directories.
- Project IDs are slug-ified directory names (e.g.
my-projectfrom~/projects/my-project/) - Token counts are in thousands (k-tokens) as reported by the CLI tools
- WebSocket endpoint at
/api/ws?token=<access_token>provides real-time events (iteration completions, plan updates, log appends, status changes) — useful for live monitoring but not covered here since agents typically poll - All timestamps are ISO 8601 with timezone