Skip to content

feat: implement canonical ralph pattern with unlimited and headless modes#4

Merged
yazan-albaiz merged 5 commits intomasterfrom
feature/canonical_ralph
Jan 22, 2026
Merged

feat: implement canonical ralph pattern with unlimited and headless modes#4
yazan-albaiz merged 5 commits intomasterfrom
feature/canonical_ralph

Conversation

@yazan-albaiz
Copy link
Copy Markdown
Owner

Summary

Align Ralph CLI with the canonical ralph pattern as described by Geoffrey Huntley, where prompts are STATIC and context is fresh each iteration.

Why This Matters

"LLMs get worse as context grows. Old code, previous attempts, and failed approaches create noise. By the time context is mostly full, the AI makes obvious mistakes."

The canonical solution:

  • Fresh start every iteration
  • Static prompt that never changes
  • Loop lives OUTSIDE the model's control
  • AI cannot decide when to stop or modify the loop itself

Changes

1. Remove Context Injection

  • preparePrompt() now only appends completion suffix
  • No iteration numbers injected
  • No PROJECT_ROOT injected
  • No growing context
  • Claude reads state from files (progress.txt), not injected context

2. Add --unlimited / -u Flag

  • Run indefinitely until <promise>COMPLETE</promise> or user intervention
  • TUI displays "∞" instead of max iteration count
  • Validation: conflicts with -m (cannot use both)

3. Add --headless Flag

  • TUI-free execution for AFK/background operation
  • Structured console logging with timestamps
  • New src/lib/headlessRunner.ts module
  • Stops on BLOCKED/DECIDE (no interactive resume in headless mode)
  • Exit codes: 0 (completed/cancelled), 1 (error)

4. Documentation Updates

  • README.md: Add "The Canonical Ralph Workflow" section with recommended prompt structure and progress.txt convention
  • CLAUDE.md: Document canonical pattern, new config options, and headless mode

5. TUI Updates

  • Header.tsx: Show ∞ for unlimited mode
  • IterationPanel.tsx: Hide progress bar in unlimited mode

Files Changed

File Change
src/lib/promiseParser.ts Remove context injection, only append completion suffix
src/lib/headlessRunner.ts NEW - TUI-free execution mode
src/cli.tsx Add -u/--unlimited and --headless flags
src/types/index.ts Add unlimited and headless to config
src/hooks/useClaudeLoop.ts Update loop for unlimited mode
src/components/Header.tsx Show ∞ for unlimited
src/components/IterationPanel.tsx Handle unlimited display
src/app.tsx Pass unlimited prop
tests/promiseParser.test.ts Update for canonical pattern
tests/integration.test.ts Update for canonical pattern
README.md Document canonical workflow
CLAUDE.md Update development docs

Test Plan

  • TypeScript compilation passes
  • All 119 tests pass
  • Build succeeds
  • Manual test: ralph "test" -m 1 (basic operation)
  • Manual test: ralph "test" --unlimited (unlimited mode)
  • Manual test: ralph "test" --headless -m 2 (headless mode)
  • Manual test: ralph "test" --unlimited -m 5 (should error - conflict)

Usage Examples

# Unlimited iterations until all tasks complete
ralph ./my-feature.md --unlimited

# With iteration limit (safety)
ralph ./my-feature.md -m 50

# Headless for AFK/overnight runs
ralph ./my-feature.md --unlimited --headless

# Short flags
ralph ./task.md -u

🤖 Generated with Claude Code

yazan-albaiz and others added 4 commits January 23, 2026 00:22
…odes

Align Ralph CLI with the canonical ralph pattern as described by
Geoffrey Huntley, where prompts are STATIC and context is fresh each
iteration. LLMs perform best without accumulated context noise.

Key changes:

1. Remove context injection from prompts
   - preparePrompt() now only appends completion suffix
   - No iteration numbers or PROJECT_ROOT injected
   - Claude reads state from files (progress.txt), not injected context

2. Add --unlimited / -u flag
   - Run indefinitely until <promise>COMPLETE</promise>
   - TUI shows "∞" instead of max iteration count
   - Conflicts with -m (validation added)

3. Add --headless flag
   - TUI-free execution for AFK/background operation
   - Structured console logging with timestamps
   - New src/lib/headlessRunner.ts module
   - Stops on BLOCKED/DECIDE (no interactive resume)

4. Update documentation
   - README.md: Add "The Canonical Ralph Workflow" section
   - CLAUDE.md: Document canonical pattern and new options

5. Update TUI components
   - Header.tsx: Show ∞ for unlimited mode
   - IterationPanel.tsx: Hide progress bar in unlimited mode

Co-Authored-By: Claude Opus 4.5 <[email protected]>
…ssions)

- Remove PROGRESS.md and add to .gitignore
- Add --print flag to Claude spawning for non-interactive mode
- Document known sub-agent permission limitations
- Add throttling to output capture to prevent TUI flashing
- Implement auto-commit feature with commit_message tag support
- Add git.ts module for git operations
- Add --auto-commit/--no-auto-commit CLI flags (default: on)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@yazan-albaiz
Copy link
Copy Markdown
Owner Author

@claude review plesae

@claude
Copy link
Copy Markdown

claude Bot commented Jan 22, 2026

Claude finished @yazan-albaiz's task in 1m 58s —— View job


Code Review Complete

Reviewed PR #4: Canonical Ralph Pattern Implementation

Review Checklist

  • Understand PR scope and goals
  • Review core logic changes (promiseParser.ts, useClaudeLoop.ts)
  • Review new headless runner implementation
  • Review CLI flag additions and validation
  • Review TUI component updates
  • Review test updates
  • Review documentation updates
  • Provide comprehensive feedback

Summary

This is a well-implemented PR that successfully implements the canonical ralph pattern as described by Geoffrey Huntley. The changes are architecturally sound and align with the stated goal of keeping prompts static and context fresh. The code quality is high, with proper error handling, TypeScript types, and comprehensive tests.

✅ Strengths

1. Clean Implementation of Canonical Pattern

The removal of context injection in preparePrompt() is correctly implemented:

  • src/lib/promiseParser.ts:134-140 - Static prompt + completion suffix only
  • No iteration numbers, PROJECT_ROOT, or loop metadata injected
  • Tests properly updated to verify no context injection

2. Excellent Headless Mode Implementation

src/lib/headlessRunner.ts is well-designed:

  • Structured logging with timestamps and color-coded levels
  • Proper SIGINT handling (double Ctrl+C for force exit)
  • Clean exit codes (0 for success/cancelled, 1 for error)
  • Stops appropriately on BLOCKED/DECIDE states
  • Good use of existing abstractions (reuses runClaude, git operations, history)

3. Consistent Unlimited Mode Support

The unlimited mode is properly threaded through:

  • CLI validation prevents --unlimited + -m conflicts
  • Loop logic correctly uses config.unlimited || iteration < config.maxIterations
  • TUI shows ∞ symbol consistently in Header and IterationPanel
  • Progress bar hidden in unlimited mode (good UX decision)

4. Comprehensive Documentation

Both README.md and CLAUDE.md updated thoroughly:

  • Clear explanation of WHY canonical pattern matters
  • Good examples and recommended prompt structure
  • progress.txt convention documented
  • Known limitations section (permission prompts bug)

5. Solid Test Coverage

Tests updated to match new behavior:

  • tests/promiseParser.test.ts verifies NO context injection
  • tests/integration.test.ts updated appropriately
  • All 119 tests passing per PR description

🔍 Issues Found

🐛 Bug: Git Auto-commit May Stage Unwanted Files

Location: src/lib/git.ts:72

The commitChanges() function uses git add -A which stages ALL changes including untracked files. This could accidentally commit:

  • .env files with secrets
  • Large binaries (node_modules if not gitignored)
  • Temporary files
  • User's uncommitted work from before ralph started
export function commitChanges(cwd: string, message: string): CommitResult {
  try {
    // Stage all changes
    execSync('git add -A', { cwd, stdio: 'pipe' });  // ⚠️ Dangerous

Recommendation: Use git add -u (update tracked files only) OR provide an allow-list of paths to commit.

Fix this →


⚠️ Issue: generateDefaultCommitMessage() Stages Files Twice

Location: src/lib/git.ts:102

export function generateDefaultCommitMessage(cwd: string, iteration: number): string {
  try {
    // First stage changes to see what would be committed
    execSync('git add -A', { cwd, stdio: 'pipe' });  // ⚠️ Already staged in commitChanges()

This function is called FROM commitChanges() (lines 125-126 in useClaudeLoop.ts, lines 153-154 in headlessRunner.ts) which already runs git add -A at line 72. Staging twice is redundant.

Recommendation: Either:

  1. Rename to generateCommitMessageFromStagedChanges() and remove the staging
  2. OR stage before calling, pass staged status to function

Fix this →


💭 Consideration: Headless Mode Error Handling

Location: src/lib/headlessRunner.ts:200-204

} catch (err) {
  const errorMessage = err instanceof Error ? err.message : String(err);
  log('ERROR', `Iteration ${iteration} exception: ${errorMessage}`);
  status = 'error';
  break;  // ⚠️ Stops entire loop on any error
}

A single iteration error stops the entire headless run. This might be too aggressive for overnight/AFK runs. Consider:

  • Retry logic for transient errors
  • OR continue to next iteration (with error counter)
  • OR make it configurable

Not blocking, but worth considering for production use.


📝 Minor: Inconsistent Error Handling in Git Functions

Location: src/lib/git.ts:114-116

} catch {
  // Ignore errors  // ⚠️ Silent failure
}

The generateDefaultCommitMessage() function silently swallows errors and falls back to a generic message. While this works, it makes debugging harder. Consider logging the error at debug level:

} catch (err) {
  logger.debug(`Failed to generate commit message from diff: ${err}`);
}

📋 Suggestions

1. Add Integration Test for Unlimited Mode

The tests verify parsing and prompt preparation, but there's no integration test for unlimited mode loop behavior. Consider adding a test that verifies the loop doesn't stop until completion signal (or manual intervention).

2. Document Auto-commit Behavior in CLAUDE.md

The README documents auto-commit well, but CLAUDE.md (developer docs) doesn't mention the git.ts module or auto-commit feature. Consider adding a section about it.

3. Consider Adding --auto-commit-pattern Option

For safety, allow users to specify which files to auto-commit:

ralph --auto-commit-pattern "src/**/*.ts" --auto-commit-pattern "tests/**/*.ts"

This would address the git add -A safety concern.


✨ Code Quality Highlights

  • Excellent use of helper functions (formatDuration, formatTimestamp, getStatusDisplay) - keeps code readable
  • Proper TypeScript types throughout - no any types, good interfaces
  • Consistent error handling - try/catch blocks with proper error messages
  • Good separation of concerns - headlessRunner.ts doesn't duplicate TUI logic
  • Refs used correctly in hooks for non-reactive state
  • Proper cleanup - SIGINT handlers removed, processes killed on unmount

🎯 Manual Testing Recommendations

Before merging, please test these scenarios:

  1. Unlimited mode stops on completion:

    echo "<promise>COMPLETE</promise>" > test-prompt.md
    ralph test-prompt.md --unlimited -d
  2. Headless mode with BLOCKED:

    ralph test-prompt.md --headless -m 1
    # Verify it logs BLOCKED and exits cleanly
  3. Auto-commit with untracked files:

    # Create untracked .env file with secrets
    echo "API_KEY=secret123" > .env
    ralph "Create test.txt" -m 1 --auto-commit
    git log -1 --name-only
    # Verify .env was NOT committed
  4. Conflict detection:

    ralph "test" --unlimited -m 5
    # Should error with clear message

🏁 Verdict

Approval: ✅ Approve with minor fixes

The implementation is solid and ready for merge after addressing the git safety issues. The canonical pattern is correctly implemented, the headless mode is well-designed, and documentation is comprehensive.

Required before merge:

  • Fix git add -A safety issue (use git add -u or allow-list)
  • Remove redundant staging in generateDefaultCommitMessage()

Optional improvements:

  • Add error retry logic in headless mode
  • Add debug logging for git errors
  • Add integration test for unlimited mode
  • Consider --auto-commit-pattern flag

Excellent work on this feature! The canonical pattern will significantly improve Ralph's effectiveness for long-running tasks.


Required fixes:
- Use `git add -u` instead of `git add -A` to prevent committing untracked files (.env, secrets)
- Remove redundant staging from generateDefaultCommitMessage(), use unstaged diff instead

Optional improvements:
- Add debug logging for git errors in generateDefaultCommitMessage()
- Document auto-commit feature in CLAUDE.md
- Add integration tests for unlimited mode behavior

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@yazan-albaiz
Copy link
Copy Markdown
Owner Author

@claude re-review after the lastest changes please.

@claude
Copy link
Copy Markdown

claude Bot commented Jan 22, 2026

Claude finished @yazan-albaiz's task in 1m 42s —— View job


Re-reviewing PR after latest changes

Review Checklist

  • Check latest commits since previous review
  • Verify git safety fix (git add -A -> git add -u)
  • Verify redundant staging fix in generateDefaultCommitMessage()
  • Review any other changes made
  • Check for new issues
  • Provide comprehensive re-review feedback

Changes Since Last Review

The commit a0685b1 addresses all required fixes and implements several optional improvements from the previous review:

Required Fixes (✅ All Addressed):

  1. Git safety fix - Changed git add -A to git add -u in commitChanges() (src/lib/git.ts:74)
  2. Redundant staging removed - generateDefaultCommitMessage() now uses git diff --stat for unstaged changes instead of staging files first (src/lib/git.ts:105)

Optional Improvements (✅ All Implemented):

  1. Debug logging - Added logger.debug() for git errors in generateDefaultCommitMessage() (src/lib/git.ts:116)
  2. Documentation - Added "Auto-Commit Feature" section to CLAUDE.md documenting git.ts module and safety measures
  3. Integration tests - Added two comprehensive tests for unlimited mode behavior in tests/integration.test.ts

Detailed Review of Changes

1. Git Safety Fix (src/lib/git.ts:68-74) ✅

/**
 * Stage modifications to tracked files and commit
 * Uses `git add -u` to avoid accidentally committing untracked files like .env or secrets
 */
export function commitChanges(cwd: string, message: string): CommitResult {
  try {
    // Stage modifications to tracked files only (safer than -A)
    execSync('git add -u', { cwd, stdio: 'pipe' });

Excellent! This properly addresses the security concern. The function:

  • Uses git add -u which only stages modifications to tracked files
  • Has clear documentation explaining why this is safer
  • Prevents accidental commits of .env files, secrets, or large binaries

2. Redundant Staging Fix (src/lib/git.ts:98-105) ✅

/**
 * Generate a default commit message based on current changes
 * Called before staging, so uses unstaged diff to analyze changes
 */
export function generateDefaultCommitMessage(cwd: string, iteration: number): string {
  try {
    // Use unstaged diff since files aren't staged yet when this is called
    const diffStat = execSync('git diff --stat', { cwd, encoding: 'utf-8' });

Perfect! The redundant staging is removed:

  • Now uses git diff --stat (unstaged diff) instead of staging first
  • Documentation clearly explains this is called before staging
  • More efficient - no redundant git operations

3. Debug Logging (src/lib/git.ts:115-116) ✅

  } catch (err) {
    logger.debug(`Failed to generate commit message from diff: ${err}`);
  }

Good addition! This makes debugging easier without polluting user output.

4. Documentation (CLAUDE.md) ✅

The added documentation is comprehensive:

  • Explains the auto-commit feature clearly
  • Documents related files
  • Explicitly mentions the git add -u safety measure
  • Lists commit message sources in order of preference

This will help future developers understand the design decisions.

5. Integration Tests (tests/integration.test.ts) ✅

Two new tests added:

Test 1: "simulates unlimited mode - continues until completion signal"

  • Creates config with unlimited: true and maxIterations: 5
  • Mock runner completes on iteration 10 (beyond maxIterations)
  • Verifies loop continues past maxIterations (gets to 10)
  • Excellent test! Proves unlimited mode ignores maxIterations

Test 2: "unlimited mode respects completion signal"

  • Mock runner completes immediately with <promise>COMPLETE</promise>
  • Verifies loop stops on first iteration
  • Good coverage! Ensures completion signal is respected

Code Quality Assessment

✅ Strengths

  1. All critical security issues resolved - No more accidental commits of secrets
  2. Performance improved - Eliminated redundant git staging operation
  3. Better error handling - Debug logging for git failures
  4. Comprehensive testing - Integration tests cover unlimited mode edge cases
  5. Excellent documentation - Safety measures clearly documented
  6. Clean implementation - Code is readable and well-commented

No New Issues Found

I reviewed the changes thoroughly and found:

  • No new bugs introduced
  • No security concerns
  • No performance regressions
  • No breaking changes
  • Proper TypeScript typing maintained

Final Verdict

Status: ✅ APPROVED - Ready to Merge

All required fixes from the previous review have been addressed correctly, and the optional improvements have been implemented well. The code is secure, well-tested, and properly documented.

Summary of What Was Fixed

Issue Status Details
Git safety (git add -A) ✅ Fixed Changed to git add -u to only stage tracked files
Redundant staging ✅ Fixed Removed staging from generateDefaultCommitMessage()
Debug logging ✅ Added Git errors now logged at debug level
Documentation ✅ Added Auto-commit feature documented in CLAUDE.md
Integration tests ✅ Added Two tests verify unlimited mode behavior

Recommendation

Merge this PR. It successfully implements the canonical ralph pattern with:

  • Static prompts (no context injection)
  • Fresh context each iteration
  • Unlimited mode support
  • Headless mode for AFK/background runs
  • Safe auto-commit with security measures
  • Comprehensive tests and documentation

The implementation is solid and all review feedback has been addressed. Great work! 🎉


|

@yazan-albaiz yazan-albaiz merged commit 6c7d14c into master Jan 22, 2026
@yazan-albaiz yazan-albaiz deleted the feature/canonical_ralph branch January 24, 2026 00:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant