@crcatala/ralph-unpossible
v1.0.0
Published
TypeScript rewrite of the Ralph autonomous loop runner
Readme
ralph-unpossible
Ralph is a campaign-only, ticket-native delivery runtime. Give it an explicitly
ordered list of trusted tk tickets and it delivers each one as a branch, a
recipe run, and a pull request — starting the next ticket only after the
previous pull request is proven merged on the fetched base branch.
Campaigns are the only product abstraction. Each delivery executes an editable project-owned recipe of agent, command, and deterministic GitHub steps. Ralph's controller owns the durable shell: pinned inputs, retained worktrees, guarded pushes, exact PR/check identity, controller-owned ticket closure, merge authority, and restart-safe recovery.
- Requires Node.js 22 or newer
- Uses
piby default; OpenCode and custom runners are supported - Reads the
tkissue tracker (ralph new,ralph validate,ralph plan)
Install
# Run without installing
npx @crcatala/ralph-unpossible --help
# Or install globally
npm install -g @crcatala/ralph-unpossibleFrom source:
git clone https://github.com/crcatala/ralph-unpossible
cd ralph-unpossible
npm install
npm run build
npm linkQuick start: run a campaign
# 1. Install the standard recipe and prompts into .ralph/
ralph init
# 2. Scaffold and edit a campaign
ralph new my-campaign --epic ru-gmir --ticket ru-6ocr --ticket ru-xxxx
$EDITOR .ralph/campaigns/my-campaign/campaign.yaml # Order is the dependency model
# 3. Configure the implementation model you actually use
ralph config init --project --provider <provider> --model <model> --thinking <level>
# 4. Check readiness, then validate, plan, and run
ralph doctor
ralph validate my-campaign # Network-free local check; creates no runtime state
ralph plan my-campaign # Compile the pinned plan; may fetch the base
ralph run my-campaign # Fetch, pin, and execute deliveries in orderralph init never overwrites files you have edited, so it is safe to re-run.
ralph new scaffolds a schema-valid version-2 campaign; its editable ticket IDs
need not resolve until you commit them on the base branch.
The campaign model
| Concept | Meaning |
|---|---|
| Campaign | One ordered list of tk ticket deliveries (.ralph/campaigns/<name>/campaign.yaml) |
| Delivery | One ticket → one branch → one recipe run → one pull request → proven merge |
| Recipe | Project-owned YAML steps (.ralph/recipes/*.yaml) plus editable Markdown prompts |
| Step | One bounded agent, command, or github.* operation with declared Git postconditions |
| Durable run | state.json is the resume authority; events.jsonl is the canonical operator history |
The next ticket cannot start until Ralph proves the prior pull request merged on the fetched base. Immediately before the first push and PR creation, the controller closes the delivery ticket on its branch and commits that closure, so ticket state lands atomically with its implementation. Ralph never infers or reorders deliveries during execution.
Campaign manifest
.ralph/campaigns/my-campaign/campaign.yaml:
version: 2
name: my-campaign
base_branch: main
source:
type: tk
epic: ru-gmir # Optional campaign context ticket
recipe: .ralph/recipes/standard-pr.yaml
pull_request:
branch_template: "ralph/{{campaign}}/{{ticket}}"
title_template: "{{ticket}}: {{ticket_title}}"
draft: false
deliveries: # Manifest order is the dependency model
- ticket: ru-6ocr
- ticket: ru-yb6rsource.type is tk. The manifest, recipe, prompts, epic, and every delivery
ticket must be committed on the fetched base before a run begins. Campaign
manifest version 1 and runtime state version 1 are rejected with an actionable
remedy — Ralph does not migrate them.
The standard recipe
ralph init installs .ralph/recipes/standard-pr.yaml and four prompts. The
installed recipe uses the same public contract that you edit:
implement (commit required)
→ open PR
→ review/fix (commit optional)
→ verify (commit optional)
→ draft PR description (commit forbidden)
→ update PR
→ required checks
→ mergeEvery agent and command step declares its accepted Git result:
git_head_advance: required | optional | forbidden
git_worktree_state: cleanA required step must leave a new, forward-descendant HEAD; forbidden must
leave HEAD unchanged. A dirty result is preserved as attention and never
silently reset.
Agent runtime configuration
Runtime resolution has no implicit model: missing config files are skipped, not
invented. ralph config init writes only the provider, model, and thinking you
pass. Values resolve field-by-field in this order:
- CLI flags:
--provider,--model,--thinking - Project config:
.ralph/config.json - Global config:
$RALPH_HOME/config.json(normally~/.ralph/config.json)
{
"version": 1,
"provider": "openrouter",
"model": "z-ai/glm-5.3-flash",
"thinking": "high"
}ralph config init --project --provider <provider> --model <model> --thinking <level>
ralph config show # Show effective values and their sources
ralph doctor # Validate runtime and project readiness
ralph doctor --jsonPi requires provider, model, and thinking. Before the first delivery Ralph asks the installed Pi to verify the exact model, thinking capability, and credentials without making an inference request. OpenCode requires provider and model. Project config version 1 and recipe version 1 remain valid.
Status, logs, and recovery
Default output is organized around campaign, ticket, and recipe step — not internal controller phases.
ralph status my-campaign # Merged count, active ticket/step, PR, next action
ralph status my-campaign --format json
ralph logs my-campaign # Follow the canonical event ledger (human)
ralph logs my-campaign --format jsonl # Stable JSONL for machines
ralph logs my-campaign --after <event-id> --format jsonl # Resume from a durable checkpoint
ralph logs my-campaign --verbose # Receipt paths and controller diagnostics
ralph resume my-campaign # Continue after waiting/attention/blocked
ralph abort my-campaign # Stop without deleting preserved work--format json emits exactly one stable object on stdout; logs --format jsonl
emits one canonical event per line. Diagnostics always go to stderr. Runtime
state and worktrees live outside the repository under RALPH_HOME (default
~/.ralph/).
Top-level run status vocabulary is small: waiting (healthy external wait),
attention (preserved work or judgment needed), blocked (a proven
identity/safety invariant prevents progress), aborted, and completed.
Project layout
.ralph/
config.json # Optional project runtime configuration
campaigns/<campaign>/
campaign.yaml # Ordered tk delivery manifest
recipes/
standard-pr.yaml # Editable delivery recipe
prompts/
implement.md # Editable agent step prompts
review-fix.md
verify.md
pr-description.mdDocumentation
- Quick command guide
- Campaign operation and configuration
- ADR-0002: Campaign-only, ticket-native delivery recipes — accepted architecture
- Campaign-only overhaul implementation plan
- ADR-0001: Prompt-native linear campaign workflows — superseded by ADR-0002
- Historical campaign architecture and trust design — superseded
- Historical campaign implementation plan — superseded
- Maintainer release guide
Development
npm install
npm test
npm run build
npm run test:package