Skip to content

Conversation

@zkwentz
Copy link
Contributor

@zkwentz zkwentz commented Jan 20, 2026

Preamble

Rrrralphy goes FAST - this PR adds multi-engine support so you can run parallel agents across Claude, Cursor, OpenCode, Codex, and Q simultaneously.

  • Worker pool pattern - tasks are dynamically claimed from a queue, so no engine sits idle while work remains
  • Auto-detection - --multi-engine flag auto-detects and uses all available AI engines on your system
  • Distribution strategies - round-robin, random, fill-first, and weighted distribution across engines
  • Engine-specific metrics - track costs, tokens, and performance per-engine in final summary

Note: Requires bash v4 (mac: brew install bash).

This is experimental but has been working locally on my fork. Ready to make ralphy go rrrreally fast! 🚀


Summary

Adds comprehensive multi-engine support for parallel task execution, allowing Ralphy to distribute work across multiple AI engines (Claude, Codex, OpenCode, Cursor, Qwen, Droid) simultaneously.

Key Features

  • Worker Pool Pattern: Tasks are dynamically claimed from a queue - when any engine finishes, it immediately grabs the next available task. No engine sits idle while work remains.
  • Auto-Detection: --multi-engine flag automatically detects and uses all available AI engines on the system
  • Engine Discovery: --detect-engines flag shows which engines are installed
  • Manual Selection: --engines flag for explicit engine selection with optional weights (e.g., --engines claude:3,codex:1)
  • Distribution Strategies: Round-robin (default), weighted, random, or fill-first distribution

New CLI Options

--multi-engine              # Auto-detect and use all available engines
--detect-engines            # Show detected engines and exit
--engines LIST              # Comma-separated engines with optional weights
--engine-distribution MODE  # round-robin, weighted, random, fill-first

Example Usage

# Auto-detect and use all available engines
./ralphy.sh --multi-engine

# Check what engines are available
./ralphy.sh --detect-engines

# Use specific engines with weights (5:1 ratio favoring Claude)
./ralphy.sh --parallel --engines claude:5,codex:1 --engine-distribution weighted

# Simple multi-engine with round-robin
./ralphy.sh --parallel --engines claude,codex,opencode

Technical Details

  • Portable mkdir-based locking (works on macOS and Linux)
  • Engine metrics tracking (success/failure counts, tokens, costs per engine)
  • Engine assignment preview in dry-run mode
  • Backward compatible with existing single-engine usage

Test plan

  • --detect-engines shows available engines
  • --multi-engine auto-detects and configures engines
  • Worker pool dynamically claims tasks
  • Engines rotate via round-robin distribution
  • Dry-run shows multi-engine configuration
  • Full parallel execution with multiple engines

🤖 Generated with Claude Code

zkwentz and others added 30 commits January 19, 2026 13:08
Added comprehensive multi-engine support configuration after line 85:
- ENGINES array: stores engines for rotation
- ENGINE_DISTRIBUTION: pattern string (e.g., "claude:2,opencode:1")
- ENGINE_WEIGHTS: associative array for engine priority
- ENGINE_AGENT_COUNT: tracks agents assigned per engine
- ENGINE_COSTS: tracks total cost per engine
- ENGINE_SUCCESS: tracks success count per engine
- ENGINE_FAILURES: tracks failure count per engine
- VALID_ENGINES: array with claude, opencode, cursor, codex, qwen, droid

These variables enable multi-engine distribution and performance tracking
in parallel execution mode, supporting the upcoming sprint work for
multi-engine task distribution and analytics.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Added ENGINES array and ENGINE_WEIGHTS associative array declarations
- Implemented --engines argument in parse_args() around line 712
- Parses comma-separated list of engines into ENGINES array
- Supports weight syntax: engine:weight (e.g., claude:5, opencode:10)
- Validates that weights are positive integers (> 0)
- Defaults to weight of 1 for engines without explicit weight
- Supports alphanumeric engine names with hyphens and underscores
- Provides clear error messages for invalid specifications

Test coverage:
- Created test_engines_parsing.sh with 12 comprehensive test cases
- All tests validate regex patterns, parsing logic, and error handling
- Tests passed successfully

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit implements the --engine-distribution CLI argument in parse_args()
that accepts four distribution strategies: round-robin, weighted, random, or
fill-first. The argument sets the ENGINE_DISTRIBUTION variable which defaults
to "round-robin".

Changes:
- Added ENGINE_DISTRIBUTION variable to configuration defaults (ralphy.sh:47)
- Implemented --engine-distribution argument parsing with validation (ralphy.sh:738-753)
- Validates that the value is one of the four accepted strategies
- Provides clear error messages for invalid or missing values
- Added test script to verify functionality (test_engine_distribution.sh)
- Updated .ralphy/progress.txt with implementation details

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add multi-engine configuration variables (ENGINES array, ENGINE_DISTRIBUTION,
  ENGINE_WEIGHTS, ENGINE_AGENT_COUNT, ENGINE_COSTS, ENGINE_SUCCESS, ENGINE_FAILURES,
  and VALID_ENGINES array)
- Implement append_engine() helper function with deduplication logic
- Modify all engine flags (--claude, --cursor, --opencode, --codex, --qwen, --droid)
  to call append_engine() instead of setting AI_ENGINE directly
- Handle --cursor/--agent alias properly: both append "cursor" to avoid duplicates
- Add comprehensive test suite with 8 tests covering:
  - Single engine flag
  - Multiple different engines
  - Duplicate engine flags
  - --cursor/--agent alias deduplication
  - Mixed scenarios with cursor aliases
  - All engines together
- All tests passing

This prepares ralphy for multi-engine parallel execution while maintaining
backward compatibility with AI_ENGINE variable.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit implements the validate_engines() function that:
- Checks engines are in VALID_ENGINES list
- Verifies CLI commands exist (claude, opencode, agent, codex, qwen, droid)
- Maps cursor engine to 'agent' CLI command
- Warns about missing CLI commands
- Filters ENGINES array to only available engines
- Returns errors for invalid engines or when no engines are available

Also added:
- Multi-engine configuration variables (ENGINES, ENGINE_WEIGHTS, etc.)
- VALID_ENGINES array with supported engines
- Comprehensive test suite (test_validate_engines.sh)
- Progress documentation in .ralphy/progress.txt

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implemented backward compatibility logic at the end of parse_args() to
ensure seamless transition between single-engine and multi-engine usage:

- If exactly one engine is specified: set AI_ENGINE to that engine
- If no engines are specified: populate ENGINES with default AI_ENGINE
- If multiple engines: no modification (multi-engine mode)

This maintains compatibility with existing workflows that use single
engine flags (--claude, --opencode, etc.) while enabling new multi-engine
functionality.

Changes:
- ralphy.sh: Added backward compatibility logic after parse_args()
- ralphy.sh: Set ENGINE_DISTRIBUTION default to "round-robin"
- ralphy.sh: Removed duplicate ENGINES/ENGINE_WEIGHTS declarations
- test_backward_compatibility.sh: Comprehensive test suite with 9 tests

All tests pass successfully.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add multi-engine configuration variables and implement the
get_engine_for_agent() function that distributes agents across
engines using round-robin strategy (agent_num % engine_count).

Changes:
- Added ENGINES array and related tracking variables (ENGINE_WEIGHTS,
  ENGINE_AGENT_COUNT, ENGINE_COSTS, ENGINE_SUCCESS, ENGINE_FAILURES)
- Added VALID_ENGINES array with supported engine names
- Implemented get_engine_for_agent() function with round-robin logic
- Created comprehensive test suite with 6 test cases (all passing)
- Updated .ralphy/progress.txt with implementation details

The function takes a 0-based agent number and returns the appropriate
engine name using modulo operation for even distribution. Falls back
to AI_ENGINE default when ENGINES array is empty.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implement display of parsed engines in dry-run output showing distribution
strategy and engine list with weights (Task 7 of multi-engine implementation).

Changes:
- Created display_engines_config() function to show engine configuration
  with weights and distribution strategy explanation
- Updated main banner to use display_engines_config() for consistent formatting
- Added dry-run handling in run_parallel_tasks() that displays:
  * Engine distribution configuration
  * Task assignment preview (first 10 tasks) with simulated engine assignments
  * Complete list of tasks to be executed
- Maintains backward compatibility with single-engine mode
- Created test_dry_run_display.sh for manual verification

The implementation assumes ENGINES array, ENGINE_DISTRIBUTION, and
ENGINE_WEIGHTS variables exist (from tasks 1-6), and gracefully degrades
when they are not set.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implemented serialization and deserialization functions to pass
ENGINE_WEIGHTS and multi-engine configuration through environment
variables to subshells, addressing the limitation that Bash
associative arrays cannot be exported to subshells.

Changes:
- Added multi-engine configuration variables (ENGINES, ENGINE_WEIGHTS,
  ENGINE_AGENT_COUNT, ENGINE_SUCCESS, ENGINE_FAILURES, ENGINE_COSTS)
- Implemented serialize_engine_config() to convert arrays to
  environment variables using comma-separated and pipe-delimited formats
- Implemented deserialize_engine_config() to reconstruct arrays in
  subshells from serialized environment variables
- Created comprehensive test documentation in test_engine_serialization.md
- Updated .ralphy/progress.txt with implementation details

Serialization format:
- Indexed arrays: comma-separated (e.g., "claude,cursor,opencode")
- Associative arrays: pipe-delimited key:value pairs
  (e.g., "claude:2|cursor:3|opencode:1")

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…parse-args' into ralphy/agent-11-add-random-distribution-strategy-to-get-engine-for

# Conflicts:
#	.ralphy/progress.txt
Add multi-engine configuration variables and implement the
get_engine_for_agent() function that distributes agents across
engines using round-robin strategy (agent_num % engine_count).

Changes:
- Added ENGINES array and related tracking variables (ENGINE_WEIGHTS,
  ENGINE_AGENT_COUNT, ENGINE_COSTS, ENGINE_SUCCESS, ENGINE_FAILURES)
- Added VALID_ENGINES array with supported engine names
- Implemented get_engine_for_agent() function with round-robin logic
- Created comprehensive test suite with 6 test cases (all passing)
- Updated .ralphy/progress.txt with implementation details

The function takes a 0-based agent number and returns the appropriate
engine name using modulo operation for even distribution. Falls back
to AI_ENGINE default when ENGINES array is empty.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…n-parse-ar' into ralphy/agent-11-add-random-distribution-strategy-to-get-engine-for

# Conflicts:
#	.ralphy/progress.txt
…de-codex-q' into ralphy/agent-11-add-random-distribution-strategy-to-get-engine-for

# Conflicts:
#	.ralphy/progress.txt
#	ralphy.sh
…t-parsing-' into ralphy/agent-11-add-random-distribution-strategy-to-get-engine-for

# Conflicts:
#	.ralphy/progress.txt
#	ralphy.sh
…-checks-en' into ralphy/agent-11-add-random-distribution-strategy-to-get-engine-for

# Conflicts:
#	.ralphy/progress.txt
#	ralphy.sh
…-showing-d' into ralphy/agent-11-add-random-distribution-strategy-to-get-engine-for

# Conflicts:
#	.ralphy/progress.txt
#	ralphy.sh
…ialize-eng' into ralphy/agent-11-add-random-distribution-strategy-to-get-engine-for

# Conflicts:
#	.ralphy/progress.txt
…with-round' into ralphy/agent-11-add-random-distribution-strategy-to-get-engine-for

# Conflicts:
#	.ralphy/progress.txt
#	ralphy.sh
Implement fill-first distribution strategy that fills engines
sequentially before moving to the next engine.

Changes:
- Modified get_engine_for_agent() to accept optional total_agents parameter
- Added case statement to support multiple distribution strategies
- Implemented fill-first strategy with agents_per_engine calculation
  - Formula: agents_per_engine = ceil(total_agents / engine_count)
  - Engine assignment: engine_index = agent_num / agents_per_engine
- Falls back to round-robin when total_agents not provided
- Handles edge cases (more engines than agents, out of bounds)

Test Coverage:
- Added 8 comprehensive test cases for fill-first distribution
- Tests cover even/uneven distributions, edge cases, and fallback behavior
- All 14 test cases pass successfully (6 round-robin + 8 fill-first)

Example with 3 engines and 10 agents:
- agents_per_engine = 4
- Agents 0-3 → engine 0
- Agents 4-7 → engine 1
- Agents 8-9 → engine 2

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implemented random distribution strategy for multi-engine support:
- Modified get_engine_for_agent() to use case statement for distribution strategies
- When ENGINE_DISTRIBUTION="random", uses RANDOM % engine_count for selection
- Maintains backward compatibility with round-robin as default strategy
- Each agent receives a randomly selected engine from the ENGINES array

Created comprehensive test suite (test_random_distribution.sh):
- Validates random distribution returns only valid engines
- Tests single engine configuration with random distribution
- Verifies all engines are eventually selected over 100 samples
- Tests switching between round-robin and random strategies
- Confirms empty ENGINES array fallback to default AI_ENGINE
- All tests pass successfully

Updated .ralphy/progress.txt with implementation details and merged work.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implements the weighted distribution strategy for get_engine_for_agent()
that expands engines by weight and cycles through the expanded array.

Changes:
- Added multi-engine configuration variables (ENGINES, ENGINE_WEIGHTS, etc.)
- Implemented expand_engines_by_weight() to create weighted engine array
- Implemented get_engine_for_agent() with support for multiple strategies:
  * round-robin: cycles through engines evenly
  * weighted: expands engines by weight and cycles through expanded array
  * random: random selection from engines
  * fill-first: placeholder for future implementation

Weighted distribution algorithm:
- Each engine is added to EXPANDED_ENGINES array N times (N = weight)
- Agents cycle through EXPANDED_ENGINES using modulo arithmetic
- Example: engines=[claude:2, opencode:1] creates [claude, claude, opencode]
- Ensures proportional distribution matching specified weights

Testing:
- Added test_weighted_distribution.sh with comprehensive test cases
- Added verify_weighted_distribution.sh for structure verification
- All verifications pass successfully

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add engine as third parameter to run_parallel_agent() function
- Create get_engine_for_agent() function that returns engine for agent number
  - Implements round-robin distribution across ENGINES array
  - Falls back to AI_ENGINE when ENGINES not set
- Create deserialize_engine_config() stub for future engine config passing
- Update call site to pass get_engine_for_agent() result
- Set AI_ENGINE in subshell to ensure correct engine per agent
- Write engine to status file for monitoring
- Log engine in agent header for debugging

This enables multi-engine support in parallel execution while maintaining
backward compatibility with single-engine setups.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit implements engine tracking infrastructure for multi-engine
parallel execution support:

1. Added multi-engine configuration variables (lines 88-96):
   - ENGINES array for enabled engines
   - ENGINE_DISTRIBUTION for strategy selection
   - ENGINE_WEIGHTS associative array for weighted distribution
   - ENGINE_AGENT_COUNT, ENGINE_SUCCESS, ENGINE_FAILURES, ENGINE_COSTS
     for per-engine metrics tracking
   - VALID_ENGINES array defining supported engines

2. Implemented serialize_engine_config() function (lines 129-143):
   - Converts ENGINE_WEIGHTS associative array to serialized string
   - Exports as environment variables for subshell propagation
   - Format: "engine1:weight1,engine2:weight2,..."

3. Implemented deserialize_engine_config() function (lines 145-156):
   - Restores ENGINE_WEIGHTS from serialized environment variable
   - Enables engine configuration access in subshells

4. Initialize tracking arrays in run_parallel_tasks() (lines 2194-2211):
   - Declares global tracking associative arrays
   - Initializes counters to 0 for all engines
   - Calls serialize_engine_config() to prepare for subshells

Technical notes:
- Uses declare -gA for global associative array declarations
- Handles empty ENGINES array gracefully
- Ready for integration with get_engine_for_agent() and
  run_parallel_agent()

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implemented engine assignment preview table showing agent number, task name,
and assigned engine for the first 10 tasks. This feature helps visualize how
tasks will be distributed across multiple engines in parallel execution.

Changes:
- Added multi-engine infrastructure (ENGINES array, ENGINE_DISTRIBUTION)
- Implemented get_engine_for_agent() with round-robin distribution
- Created print_engine_assignment_preview() to display assignment table
- Integrated preview into run_parallel_tasks() function
- Added backward compatibility to populate ENGINES with default AI_ENGINE
- Table shows color-coded engine names and truncates long task names
- Only displays when multiple engines are configured or in dry-run mode

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Modified status file format to include engine information and updated
monitoring loop to parse it using grep. This enables engine tracking
for each agent during parallel execution.

Changes:
- Extended status file format with engine= line
- Updated all status writes to include engine info
- Modified monitoring loop to parse engine with grep
- Status read changed from cat to head -n 1 for first line only
- Added grep to extract engine from status file

The engine variable is now available in the monitoring loop for future
features like engine-specific status display and per-engine metrics.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Updated the status display format for parallel agents to show the engine
name alongside the agent number: "Agent N (engine): status"

Changes:
- Added get_engine_short_name() helper function to get engine display name
- Updated initial agent status display to include engine name
- Updated final agent status display to include engine name
- Updated failed agent log header to include engine name
- Created test suite with 7 passing tests for all supported engines

This provides better visibility when running parallel agents with different
AI engines, making it clear which engine is handling each task.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Created reusable display_agent_status() function (ralphy.sh:2152-2214)
  that accepts arrays of PIDs, status file paths, batch size, and start time
- Replaced 57 lines of inline monitoring code with clean 2-line function call
- Added comprehensive test suite (test_display_agent_status.sh) with 6 tests
- Updated .ralphy/progress.txt with implementation details

Benefits:
- Eliminates code duplication
- Improves maintainability
- Provides reusable monitoring for future parallel execution needs
- All tests pass successfully

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
zkwentz and others added 13 commits January 19, 2026 14:53
…ne merge

- Remove duplicate validate_engines(), get_engine_for_agent(), print_engine_summary(), deserialize_engine_config()
- Consolidate multiple multi-engine configuration blocks into single block
- Keep most complete implementation of each function
- Script now has 73 unique functions and passes bash syntax check

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Implement worker pool for parallel execution: tasks are now dynamically
  claimed from a queue, so no engine sits idle while work remains
- Add --multi-engine flag to auto-detect and use all available AI engines
- Add --detect-engines flag to show available engines on the system
- Replace flock with portable mkdir-based locking for macOS compatibility
- Update banner to show multi-engine configuration when active
- Fix backward compatibility to not interfere with multi-engine detection

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@vercel
Copy link

vercel bot commented Jan 20, 2026

@zkwentz is attempting to deploy a commit to the Goshen Labs Team on Vercel.

A member of the Team first needs to authorize it.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 20, 2026

Greptile Overview

Greptile Summary

This PR adds comprehensive multi-engine support to Ralphy, enabling parallel task execution across multiple AI engines (Claude, OpenCode, Cursor, Codex, Qwen, Droid) using a worker pool pattern.

Key Changes:

  • Worker pool implementation where tasks are dynamically claimed from a queue, ensuring no engine sits idle while work remains
  • Auto-detection of available AI engines via --multi-engine flag
  • Support for distribution strategies: round-robin, weighted, random, and fill-first
  • Engine-specific metrics tracking (costs, tokens, success/failure counts per engine)
  • Serialization/deserialization of engine configuration for subshell communication (bash arrays don't propagate to subshells)
  • Comprehensive test suite covering backward compatibility, parsing, distribution strategies, and metrics

Issues Found:

  • get_next_engine_atomic() only implements round-robin distribution and ignores the ENGINE_DISTRIBUTION setting - weighted, random, and fill-first strategies won't work in parallel mode
  • Minor: IFS modification in argument parsing (line 1260) is not restored, though impact is likely minimal

Resolved from Previous Reviews:

  • All merge conflicts have been resolved
  • ENGINE_WEIGHTS properly reset between tests using unset and declare -A
  • Unset array key checks now use :- pattern to avoid errors
  • IFS properly restored in test files

The implementation is well-structured with excellent documentation and test coverage. The worker pool pattern is a solid design choice for dynamic task distribution. The main blocker is the distribution strategy bug in the worker pool.

Confidence Score: 3/5

  • This PR has significant functionality but contains a critical bug in engine distribution that prevents non-round-robin strategies from working in parallel mode
  • Score of 3 reflects: (1) excellent test coverage and documentation, (2) all previous review issues resolved, (3) well-designed worker pool architecture, BUT (4) critical bug where get_next_engine_atomic() ignores ENGINE_DISTRIBUTION setting, making weighted/random/fill-first strategies non-functional in parallel mode - this is a core feature that doesn't work as advertised
  • Pay close attention to ralphy.sh line 2884-2918 - the get_next_engine_atomic() function needs to implement all distribution strategies, not just round-robin

Important Files Changed

Filename Overview
ralphy.sh Major multi-engine implementation with worker pool pattern, but get_next_engine_atomic ignores ENGINE_DISTRIBUTION setting
test_backward_compatibility.sh Merge conflicts resolved - comprehensive backward compatibility tests
test_weighted_distribution.sh Proper ENGINE_WEIGHTS reset between tests - comprehensive weighted distribution test coverage
test_get_engine_for_agent.sh Merge conflicts resolved - tests engine assignment logic for different distribution strategies
test_engines_parsing.sh IFS properly restored - validates engine specification parsing with weights
docs/multi-engine-spec.md Comprehensive documentation for multi-engine feature with architecture details and examples

Sequence Diagram

sequenceDiagram
    participant User
    participant Main as ralphy.sh (main)
    participant Queue as Task Queue
    participant W1 as Worker 1
    participant W2 as Worker 2
    participant W3 as Worker 3
    participant Engine1 as Claude
    participant Engine2 as OpenCode
    participant Engine3 as Cursor

    User->>Main: ./ralphy.sh --multi-engine --parallel
    Main->>Main: detect_available_engines()
    Main->>Main: serialize_engine_config()
    Main->>Queue: task_queue_init(tasks)
    Main->>Queue: Write all tasks to queue file
    
    par Spawn Workers
        Main->>W1: run_worker(1) &
        Main->>W2: run_worker(2) &
        Main->>W3: run_worker(3) &
    end
    
    W1->>W1: deserialize_engine_config()
    W2->>W2: deserialize_engine_config()
    W3->>W3: deserialize_engine_config()
    
    loop Worker Pool Pattern
        W1->>Queue: task_queue_claim() [atomic lock]
        Queue-->>W1: Task A
        W1->>W1: get_next_engine_atomic()
        W1->>Engine1: run_parallel_agent(Task A, claude)
        
        W2->>Queue: task_queue_claim() [atomic lock]
        Queue-->>W2: Task B
        W2->>W2: get_next_engine_atomic()
        W2->>Engine2: run_parallel_agent(Task B, opencode)
        
        W3->>Queue: task_queue_claim() [atomic lock]
        Queue-->>W3: Task C
        W3->>W3: get_next_engine_atomic()
        W3->>Engine3: run_parallel_agent(Task C, cursor)
        
        Engine1-->>W1: Result (success/failure)
        W1->>Queue: Store result file
        
        W1->>Queue: task_queue_claim() [next task]
        Queue-->>W1: Task D
        W1->>Engine1: run_parallel_agent(Task D, claude)
        
        Engine2-->>W2: Result
        W2->>Queue: Store result file
        W2->>Queue: task_queue_claim()
        Queue-->>W2: (empty - exit)
        
        Engine3-->>W3: Result
        W3->>Queue: Store result file
        W3->>Queue: task_queue_claim()
        Queue-->>W3: Task E
    end
    
    Main->>Main: Monitor worker_pids (wait)
    Main->>Queue: Collect all result files
    Main->>Main: print_engine_summary()
    Main-->>User: Multi-engine execution complete
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

31 files reviewed, 14 comments

Edit Code Review Agent Settings | Greptile

test_error "Invalid weight (non-numeric)" --engines claude:abc --dry-run

# Test 4: Invalid weight format (negative)
echo "=== Test 4: Invalid weight format (zero) ==="
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

syntax: Comment mentions 'negative' but test description says 'zero' - should clarify which invalid weight case this tests

Prompt To Fix With AI
This is a comment left during a code review.
Path: test_error_messages.sh
Line: 44:44

Comment:
**syntax:** Comment mentions 'negative' but test description says 'zero' - should clarify which invalid weight case this tests

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +86 to +94
ENGINE_AGENT_COUNT["claude"]=50
ENGINE_SUCCESS["claude"]=45
ENGINE_FAILURES["claude"]=5
ENGINE_COSTS["claude"]=2.5678

ENGINE_AGENT_COUNT["opencode"]=30
ENGINE_SUCCESS["opencode"]=28
ENGINE_FAILURES["opencode"]=2
ENGINE_COSTS["opencode"]=1.2345
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Test 5 overwrites the claude and opencode data from previous tests, which could mask issues if the function doesn't handle data updates correctly.

Prompt To Fix With AI
This is a comment left during a code review.
Path: test_print_engine_summary.sh
Line: 86:94

Comment:
**logic:** Test 5 overwrites the claude and opencode data from previous tests, which could mask issues if the function doesn't handle data updates correctly.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines 4154 to 4161
if [[ ${#ENGINES[@]} -eq 0 ]] && [[ "$MULTI_ENGINE" != true ]]; then
ENGINES=("$AI_ENGINE")

# Load browser setting from config (if not overridden by CLI flag)
if [[ "$BROWSER_ENABLED" == "auto" ]] && [[ -f "$CONFIG_FILE" ]]; then
BROWSER_ENABLED=$(load_browser_setting)

fi
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

syntax: syntax error in conditional structure - missing 'fi' to close the if statement started on line 4154

Suggested change
if [[ ${#ENGINES[@]} -eq 0 ]] && [[ "$MULTI_ENGINE" != true ]]; then
ENGINES=("$AI_ENGINE")
# Load browser setting from config (if not overridden by CLI flag)
if [[ "$BROWSER_ENABLED" == "auto" ]] && [[ -f "$CONFIG_FILE" ]]; then
BROWSER_ENABLED=$(load_browser_setting)
fi
# Backward compatibility: populate ENGINES if not set
# Skip if using --multi-engine (it will auto-detect)
if [[ ${#ENGINES[@]} -eq 0 ]] && [[ "$MULTI_ENGINE" != true ]]; then
ENGINES=("$AI_ENGINE")
fi
# Load browser setting from config (if not overridden by CLI flag)
if [[ "$BROWSER_ENABLED" == "auto" ]] && [[ -f "$CONFIG_FILE" ]]; then
BROWSER_ENABLED=$(load_browser_setting)
fi
Prompt To Fix With AI
This is a comment left during a code review.
Path: ralphy.sh
Line: 4154:4161

Comment:
**syntax:** syntax error in conditional structure - missing 'fi' to close the if statement started on line 4154

```suggestion
  # Backward compatibility: populate ENGINES if not set
  # Skip if using --multi-engine (it will auto-detect)
  if [[ ${#ENGINES[@]} -eq 0 ]] && [[ "$MULTI_ENGINE" != true ]]; then
    ENGINES=("$AI_ENGINE")
  fi

  # Load browser setting from config (if not overridden by CLI flag)
  if [[ "$BROWSER_ENABLED" == "auto" ]] && [[ -f "$CONFIG_FILE" ]]; then
    BROWSER_ENABLED=$(load_browser_setting)
  fi
```

How can I resolve this? If you propose a fix, please make it concise.

zkwentz and others added 5 commits January 19, 2026 20:35
Resolve git merge conflicts in three test files that were preventing
execution:
- test_backward_compatibility.sh: Keep comprehensive 14-test suite
- test_get_engine_for_agent.sh: Merge round-robin and fill-first tests
- test_load_parallel_config.sh: Keep integration test with proper setup/teardown

All tests now pass.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix missing `fi` in ralphy.sh main() causing syntax error
- Fix comment/test name mismatch in test_error_messages.sh
- Add safe default for unset associative array key check in test_record_agent_result.sh
- Properly reset associative arrays between tests in test_weighted_distribution.sh

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The check now verifies expand_engines_by_weight is called within the
weighted distribution case block, not just that it exists somewhere
in the file.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Immediately restore IFS to default after the comma-separated read
to avoid affecting global state.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
test_print_engine_summary.sh:
- Inline function definition instead of fragile grep-based sourcing
- Add reset_engine_data() helper to clear arrays between tests
- Each test now starts with clean state

test_get_engine_color.sh:
- Add comments explaining intentional inline definitions for test isolation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@zkwentz
Copy link
Contributor Author

zkwentz commented Jan 20, 2026

@greptile give it another look please

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 20, 2026

Greptile's behavior is changing!

From now on, if a review finishes with no comments, we will not post an additional "statistics" comment to confirm that our review found nothing to comment on. However, you can confirm that we reviewed your changes in the status check section.

This feature can be toggled off in your Code Review Settings by deselecting "Create a status check for each PR".

@michaelshimeles
Copy link
Owner

Going to review this in the AM. Good stuff @zkwentz

@emkman
Copy link

emkman commented Jan 20, 2026

I'd love a variation on this where I can use an engine like Claude Code and then once I hit my daily usage limit I can specify a failover engine to switch to GLM in OpenCode, for example

@zkwentz
Copy link
Contributor Author

zkwentz commented Jan 20, 2026

That was sort of the spirit behind this. In practice this pool approach isn't working all that well due to merge conflicts and need for isolated tasks.

I'd be fine to rework this in the spirit of quota conservation which was original goal anyway.

The only thing I can see being practical here is a sort of cheap multi engine approach with expensive overseer taking best implementation from the group (i.e. GRPO applied to test time).

Probably worth landing the conservation first, and consensus second using this as starting point but ultimately abandoning this one but for key functionality.

@emkman
Copy link

emkman commented Jan 20, 2026

Makes sense. I was definitely thinking of simple failover more than pooling. Quota conservation to avoid hitting the limit is also interesting but may also be more complicated than necessary,

@michaelshimeles
Copy link
Owner

@greptile review

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines +2884 to +2918
get_next_engine_atomic() {
local engine_count=${#ENGINES[@]}

# If no engines configured or only one, return default/single
if [[ $engine_count -eq 0 ]]; then
echo "$AI_ENGINE"
return
fi

if [[ $engine_count -eq 1 ]]; then
echo "${ENGINES[0]}"
return
fi

local lock_dir="${ENGINE_COUNTER_FILE}.lock.dir"

# Acquire lock
if ! acquire_lock "$lock_dir"; then
# Fallback to first engine if lock fails
echo "${ENGINES[0]}"
return
fi

local counter
counter=$(cat "$ENGINE_COUNTER_FILE" 2>/dev/null || echo "0")
local index=$((counter % engine_count))

# Increment counter for next call
echo $((counter + 1)) > "$ENGINE_COUNTER_FILE"

# Release lock
release_lock "$lock_dir"

echo "${ENGINES[$index]}"
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_next_engine_atomic() only implements round-robin distribution and ignores the ENGINE_DISTRIBUTION setting. When users specify --engine-distribution weighted, random, or fill-first, the worker pool will still use round-robin.

The function should check ENGINE_DISTRIBUTION and implement the selected strategy atomically, similar to how get_engine_for_agent() does it.

Prompt To Fix With AI
This is a comment left during a code review.
Path: ralphy.sh
Line: 2884:2918

Comment:
`get_next_engine_atomic()` only implements round-robin distribution and ignores the `ENGINE_DISTRIBUTION` setting. When users specify `--engine-distribution weighted`, `random`, or `fill-first`, the worker pool will still use round-robin.

The function should check `ENGINE_DISTRIBUTION` and implement the selected strategy atomically, similar to how `get_engine_for_agent()` does it.

How can I resolve this? If you propose a fix, please make it concise.

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.

3 participants