OpenSpec + Ralph Loop integration for iterative development with opencode.
Quick Start Guide - Get up and running in 5 minutes!
OpenSpec provides excellent structure for planning (proposal → specs → design → tasks) but leaves execution manual. This package provides an iterative development loop driven by an internal mini Ralph implementation that works with OpenCode and eliminates the need for any external Ralph runtime. When auto-commit is enabled, the runner owns task commits; when --no-commit is enabled, the prompt contract and runtime both leave changes uncommitted.
The runtime prompt is self-contained: it does not depend on Cursor-only slash commands or editor-local skills.
Examples below assume current published releases of spec-and-loop,
@fission-ai/openspec, and opencode-ai. Node.js >=24 is required.
The supported OS contract is Linux and macOS.
npm install -g spec-and-loopPrerequisites: You need OpenSpec and the OpenCode AI agent installed:
npm install -g @fission-ai/openspec opencode-aiAlternative OpenCode install methods:
# Install script (general use)
curl -fsSL https://opencode.ai/install | bash
# Homebrew (macOS / Linux)
brew install anomalyco/tap/opencode# 1. Initialize OpenSpec in your project
openspec init
# 2. Ralphify your project (enables Ralph-friendly artifact generation)
ralph-run init
# 3. Create a new change
openspec new change add-user-auth
# 4. Review and complete the OpenSpec artifacts
# (openspec/changes/add-user-auth/proposal.md)
# (openspec/changes/add-user-auth/design.md)
# (openspec/changes/add-user-auth/specs/*/spec.md)
# (openspec/changes/add-user-auth/tasks.md)
# 5. Run the ralph loop (executes tasks with opencode)
ralph-run --change add-user-authFor detailed step-by-step instructions, see QUICKSTART.md.
Spec-and-loop includes a comprehensive test suite to ensure reliability and cross-platform compatibility.
Testing Guide - Detailed instructions for running tests
# Run all tests
npm test
# Run unit tests only
npm run test:unit
# Run integration tests only
npm run test:integration
# Run tests with coverage
npm run test:coverage
# Run shellcheck linting
npm run lint- Linux: Tests run on Ubuntu (latest)
- macOS: Tests run on macOS (latest)
- Node.js: Tested on Node.js 24
All tests are run automatically via GitHub Actions on every push and pull request.
Before using spec-and-loop, ensure you have:
-
Node.js - For package installation (requires >=24.0.0)
node --version # Should be >=24.0.0 -
openspec - OpenSpec CLI for specification workflow
npm install -g @fission-ai/openspec
-
opencode - Agentic coding assistant
npm install -g opencode-ai
-
jq - Command-line JSON processor
# Ubuntu/Debian sudo apt install jq # macOS brew install jq
-
git - Version control (for commits per task)
git init
For complete installation instructions, see QUICKSTART.md.
openspec init- Initialize OpenSpec in current directoryopenspec new change <name>- Create a new change with artifact templatesopenspec list- List active changesopenspec status --change <name>- Show artifact completion status for a changeopenspec show <item-name>- Display a change or spec in detailopenspec validate <item-name>- Validate a change or specopenspec archive <change-name>- Archive a completed change
ralph-run init- Configure project for Ralph-friendly artifact generation (run once afteropenspec init)
ralph-run [OPTIONS]
OPTIONS:
--change <name> Specify the OpenSpec change to execute (default: auto-detect)
--max-iterations <n> Maximum iterations for Ralph loop (default: 50)
--no-commit Suppress automatic git commits during the loop
--verbose, -v Enable verbose mode for debugging
--help, -h Show this help message
OBSERVABILITY AND CONTROL:
--status Print the current loop status dashboard and exit
--add-context <text> Add pending context to inject into the next iteration and exit
--clear-context Clear any pending context and exit
SUBCOMMANDS:
init Configure project for Ralph-friendly artifact generation
openspec new change my-featureThis creates:
- proposal.md: Why you're making this change
- specs//spec.md: Detailed requirements for each capability
- design.md: Technical decisions and architecture
- tasks.md: Implementation tasks as checkboxes
After creating the change, manually complete the OpenSpec artifacts by filling in proposal.md, design.md, specs/*/spec.md, and tasks.md.
Example tasks.md:
## Implementation
- [ ] Create database schema
- [ ] Implement API endpoints
- [ ] Write unit tests
- [ ] Add documentationralph-run --change my-featureWhat happens:
- Validation: Checks for required OpenSpec artifacts and git repository
- PRD Generation: Converts proposal + specs + design → PRD format for internal use
- Setup: Creates .ralph directory, syncs tasks symlink, and sets up output capture
- Task Execution: For each incomplete task:
- Generates a context-rich prompt from the invocation-time PRD snapshot plus a fresh task snapshot and recent loop signals
- Runs
opencodewith the prompt via the internal mini Ralph engine - Captures output to temp directory for review and debugging
- Logs any errors to
.ralph/errors.mdwith timestamps - Expects standalone control lines:
<promise>READY_FOR_NEXT_TASK</promise>for task completion and<promise>COMPLETE</promise>when all tasks are done - Creates a runner-managed git commit for task-scoped changes when auto-commit is enabled; if the commit is blocked or fails, the anomaly is recorded in history and surfaced by
--status - Marks task complete in tasks.md
- Cleanup: Automatically removes old output directories (older than 7 days)
- Completion: All tasks done
# Check loop status
ralph-run --status
# Check remaining tasks
grep "^- \[ \]" openspec/changes/my-feature/tasks.md
# View git commits
git log --oneline
# Inject context into next iteration
ralph-run --add-context "Prefer async/await over callbacks"# 1. Initialize OpenSpec in your project
cd my-web-app
git init
openspec init
# 2. Ralphify your project
ralph-run init
# 3. Create a new change
openspec new change user-auth
# 4. Complete OpenSpec artifacts manually or use opencode skills
# (review and fill in proposal.md, design.md, specs/*/spec.md, tasks.md)
# 5. Execute with Ralph
ralph-run --change user-auth
# Output:
# [INFO] Found 15 tasks to execute
# [INFO] Executing task 1/15: Create User model with password field
# [INFO] Executing task 2/15: Implement password hashing
# ...
# 5. Verify implementation
git log --oneline # 15 commits, one per task
git diff HEAD~15 # See full implementation| Feature | Description |
|---|---|
| Structured Planning | OpenSpec workflow: proposal → specs → design → tasks |
| Agentic Execution | opencode executes tasks with full context |
| Iterative Loop | Each task builds on previous commits |
| Iteration Feedback | Recent failures and no-progress iterations inform the next pass |
| Granular History | Runner-managed commit per completed task when auto-commit succeeds |
| Auto-Resume | Interrupted? Run again — picks up where left off |
| Context Injection | --add-context injects guidance into the next iteration |
| Loop Status | --status shows active state, history, and struggle indicators |
| Error Tracking | Automatic error logging and archiving for debugging |
| Output Capture | Loop output captured to temp directories for review |
| Cross-Platform | Full support for Linux and macOS with portable operations |
| No External Ralph | Self-contained mini Ralph engine — no external ralph CLI needed |
spec-and-loop includes a first-party mini Ralph implementation (lib/mini-ralph/) that
provides the core iterative loop without any external Ralph dependency:
- Iterative execution: Each task builds on previous commits with full context
- State and history: Loop state, iteration history, and struggle indicators stored in each change's
.ralph/ - Prompt templates: Context-aware prompts generated from OpenSpec artifacts
- Completion promises: Loop exits when a completion signal is detected
- Task progression: Synchronized with
tasks.mdcheckboxes as the source of truth
The supported subset intentionally excludes upstream features that are out of scope for this repository's OpenSpec-first workflow (multi-agent rotation, plugin toggles, etc.).
| OpenSpec | opencode | Together |
|---|---|---|
| Structured planning | Agentic execution | Plan → Execute loop |
| Human-readable specs | AI-understandable context | Full context propagation |
| Task breakdown | Task implementation | Automatable workflow |
- Auto-resume: Interrupted? Run again — picks up where left off
- Context injection:
--add-context/--clear-contextvia each change's.ralph/ralph-context.md - Error recovery: Recent loop signals help guide subsequent tasks
- Error tracking: Automatic error logging to
.ralph/errors.mdwith timestamps and archiving - Task synchronization:
tasks.mdand the per-change.ralph/ralph-tasks.mdsymlink stay in sync - Output capture: Loop output captured to temp directories for review and debugging
- Cross-platform: Portable operations for Linux and macOS (stat, md5sum, realpath)
- Cleanup: Automatic cleanup of old output directories (older than 7 days)
- Idempotent: Run multiple times safely
Inject custom instructions into the next iteration:
ralph-run --add-context "Use Redis instead of Memcached"
# Check current pending context
ralph-run --status
# Clear pending context
ralph-run --clear-contextralph-run --statusShows: active loop state, current task, prompt summary, pending context, iteration history, and struggle indicators if the loop appears stuck. Inactive runs are distinguished as completed or stopped-incomplete, and the latest unresolved auto-commit anomaly is shown when present.
Run without automatic git commits (useful for reviewing changes before committing):
ralph-run --change my-feature --no-commitIn this mode the runner does not create commits, and the rendered prompt explicitly forbids the agent from running git add or git commit.
For debugging:
ralph-run --verbose --change my-featurecat openspec/changes/my-feature/.ralph/PRD.mdPRD.md is generated once when ralph-run starts and reused for the rest of
that run. Per-iteration freshness comes from re-reading tasks.md, recent loop
signals, and any pending --add-context injection.
If you customize the prompt template, preserve the standalone promise-line contract so only literal control lines advance the loop:
<promise>READY_FOR_NEXT_TASK</promise>
<promise>COMPLETE</promise>
# Find the latest output directory path
cat openspec/changes/my-feature/.ralph/.output_dir
# View stdout and stderr logs
cat openspec/changes/my-feature/.ralph/.output_dir/ralph-stdout.log
cat openspec/changes/my-feature/.ralph/.output_dir/ralph-stderr.logOutput is captured to temporary directories for debugging. Old output directories are automatically cleaned up after 7 days.
# View recent errors
cat openspec/changes/my-feature/.ralph/errors.md
# Archived errors are saved with timestamps
ls openspec/changes/my-feature/.ralph/errors_*.mdThis package integrates:
- OpenSpec: Structured specification workflow
- opencode: Agentic coding assistant for task execution
- Mini Ralph (
lib/mini-ralph/): Internal iterative loop engine
Each task execution includes:
- Invocation-time PRD snapshot: Proposal, design, and spec content captured in
.ralph/PRD.mdwhen the currentralph-runinvocation starts - Fresh task snapshot: Raw
tasks.mdcontent plus the current task and completed-task summary rendered each iteration - Recent loop signals: Compact reminders about prior failed or no-progress iterations
- Pending context: Any
--add-contextinjection
Synchronized tracking:
- tasks.md: Human-readable checkboxes
[ ]→[x](source of truth) - .ralph/ralph-tasks.md: Symlink to
tasks.mdfor the loop engine - Atomic updates: Checkboxes updated after each completed task
openspec/changes/<name>/
├── proposal.md # Your "why"
├── design.md # Your "how"
├── tasks.md # Your "what" (checkboxes, source of truth)
├── specs/ # Your requirements
│ ├── auth/
│ │ └── spec.md
│ └── api/
│ └── spec.md
└── .ralph/ # Internal loop state (auto-generated, per change)
├── PRD.md # Generated prompt snapshot from loop start
├── prompt-template.md # Template used for generating prompts
├── ralph-history.json # Iteration history and state
├── ralph-loop.state.json # Current loop state and iteration count
├── ralph-tasks.md # Symlink to ../tasks.md (syncs task state)
├── .output_dir # Path to latest output capture directory
├── ralph-context.md # (Optional) Pending context for next iteration
├── errors.md # (Optional) Error logs with timestamps
├── errors_*.md # (Optional) Archived error logs
└── *.md # (Optional) Research artifacts created during task execution
Note: Files marked as (Optional) are created only when needed:
ralph-context.md: Created when you use--add-contexterrors.mdanderrors_*.md: Created when errors occur during loop execution- Additional
*.mdfiles: Research artifacts created by opencode during task execution (e.g., verification outputs, analysis documents)
spec-and-loop is designed to work on both Linux and macOS. The script includes portable implementations for:
- File modification times: Uses
stat -f %mon macOS andstat -c %Yon Linux - MD5 hashing: Supports both
md5sum(Linux) andmd5 -q(macOS) - Path resolution: Falls back from
realpathtoreadlink -fto manual path construction - Temp directories: Uses
TMPDIRenvironment variable or/tmpas fallback - Cleanup: Portable
findandrmoperations for old output directories
Windows is not currently part of the supported runtime contract.
For common issues and solutions, see QUICKSTART.md#troubleshooting.
| Variable | Default | Description |
|---|---|---|
RALPH_BASE_PROMPT_WARN_BYTES |
4096 |
Byte threshold above which render() emits a one-line warning to stderr when {{base_prompt}} resolves to a large file. Set to 0 to silence warnings entirely. Invalid values fall back to 4096 with a one-time notice per process. |
RALPH_ITERATION_IDLE_TIMEOUT_MS |
300000 |
Milliseconds of silence on stdout+stderr before the per-iteration idle watchdog fires. Set to 0 to disable the watchdog entirely and restore pre-change behavior (no timeout). |
RALPH_ITERATION_KILL_GRACE_MS |
10000 |
Milliseconds the runner waits after sending SIGTERM to a timed-out iteration child before escalating to SIGKILL. |
This section covers two surfacing improvements added on top of the harden-auto-commit-against-ignored-paths change, which is the underlying mechanism that detects when .gitignore rules filter out loop-managed paths.
No new CLI flags are introduced by this change. No startup behavior changes. Every existing ralph-run invocation continues to work unchanged.
When _autoCommit detects that one or more paths were filtered by .gitignore (anomaly types paths_ignored_filtered or all_paths_ignored), the runner emits the following block directly to process.stderr on every iteration where the anomaly fires — independently of any reporter buffering or deduplication:
================================================================================
⚠ AUTO-COMMIT IGNORE FILTER FIRED (iteration 7, type: paths_ignored_filtered)
Paths filtered because .gitignore matches:
- openspec/changes/my-change/tasks.md
- openspec/changes/my-change/proposal.md
Consequence: these paths are NOT in the latest commit.
Remediation (pick one):
1. git add -f <path> # one-time unblock, if you want it tracked
2. edit .gitignore # narrow or remove the matching rule
3. pass --no-auto-commit on the ralph-run invocation
================================================================================
The three remediation options mean:
git add -f <path>— force-stage a specific file for the next commit. One-time unblock; the path stays gitignored and will be filtered again on the next auto-commit unless you also do option 2.- edit
.gitignore— narrow or remove the matching rule so the path is no longer excluded. The safest long-term fix when the rule is too broad. --no-auto-commit— disable auto-commit for this run entirely. Use when you want to manage commits yourself and don't want the runner touching git.
The runner enforces a per-iteration idle timeout: if the opencode run subprocess produces no new bytes on stdout or stderr for RALPH_ITERATION_IDLE_TIMEOUT_MS milliseconds, the watchdog fires. It sends SIGTERM, waits up to RALPH_ITERATION_KILL_GRACE_MS for a graceful exit, then sends SIGKILL.
The timed-out iteration is recorded in history with failureReason: 'iteration_timeout_idle' and three diagnostic fields: idleMs (how long the process was silent), lastStdoutBytes (last ≤200 bytes of stdout), and lastStderrBytes (last ≤200 bytes of stderr). These fields are absent on entries where the watchdog did not fire.
The iteration_timeout_idle reason also appears in the ## Recent Loop Signals block injected into each iteration's prompt, giving the agent visibility into prior timeout events.
Set RALPH_ITERATION_IDLE_TIMEOUT_MS=0 to disable the watchdog if your agent workflow runs legitimately long silent tools (e.g., large integration test suites). Example:
RALPH_ITERATION_IDLE_TIMEOUT_MS=900000 ralph-run --change my-feature # 15-minute idle threshold
RALPH_ITERATION_IDLE_TIMEOUT_MS=0 ralph-run --change my-feature # watchdog disabledQuick fixes:
# opencode not found?
npm install -g opencode-ai
# jq not found?
sudo apt install jq # or: brew install jq
# Not a git repository?
git init
# command not found: ralph-run?
export PATH="$PATH:$(npm root -g)/.bin"- OpenSpec - Structured specification workflow
- opencode - Agentic coding assistant
- Ralph-Friendly OpenSpec Best Practices - How to author loop-safe artifacts and tasks
- OpenSpec + Ralph Wiggum BOTW - Strengths, tradeoffs, and best-fit guidance
- Ralph Methodology Assessment - Repository-specific methodology review
GPL-3.0