claude-evolve 1.2.13 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -21,6 +21,14 @@ while [[ $# -gt 0 ]]; do
21
21
  timeout_seconds="$2"
22
22
  shift 2
23
23
  ;;
24
+ --parallel)
25
+ force_parallel="true"
26
+ shift
27
+ ;;
28
+ --sequential)
29
+ force_sequential="true"
30
+ shift
31
+ ;;
24
32
  --help)
25
33
  cat <<EOF
26
34
  claude-evolve run - Execute evolution candidates
@@ -30,6 +38,8 @@ USAGE:
30
38
 
31
39
  OPTIONS:
32
40
  --timeout <sec> Kill evaluator after specified seconds (default: no timeout)
41
+ --parallel Force parallel execution mode
42
+ --sequential Force sequential execution mode
33
43
  --help Show this help message
34
44
 
35
45
  DESCRIPTION:
@@ -49,8 +59,29 @@ EOF
49
59
  esac
50
60
  done
51
61
 
52
- echo "[INFO] Starting continuous evolution run..."
53
- echo "[INFO] Will continue running until no more pending candidates or 5 consecutive failures"
62
+ # Determine execution mode
63
+ use_parallel=false
64
+ if [[ "$force_parallel" == "true" ]]; then
65
+ use_parallel=true
66
+ echo "[INFO] Using parallel mode (forced via --parallel)"
67
+ elif [[ "$force_sequential" == "true" ]]; then
68
+ use_parallel=false
69
+ echo "[INFO] Using sequential mode (forced via --sequential)"
70
+ elif [[ "$PARALLEL_ENABLED" == "true" || "$PARALLEL_ENABLED" == "1" ]]; then
71
+ use_parallel=true
72
+ echo "[INFO] Using parallel mode (enabled in config)"
73
+ else
74
+ echo "[INFO] Using sequential mode (default)"
75
+ fi
76
+
77
+ if [[ "$use_parallel" == "true" ]]; then
78
+ echo "[INFO] Starting parallel evolution run with up to $MAX_WORKERS workers"
79
+ exec "$SCRIPT_DIR/claude-evolve-run-parallel" ${timeout_seconds:+--timeout "$timeout_seconds"}
80
+ else
81
+ echo "[INFO] Starting continuous evolution run..."
82
+ echo "[INFO] Will continue running until no more pending candidates or 5 consecutive failures"
83
+ fi
84
+
54
85
  [[ -n $timeout_seconds ]] && echo "[INFO] Using timeout: ${timeout_seconds} seconds per evaluation"
55
86
 
56
87
  # Prepare logging directory
@@ -0,0 +1,219 @@
1
+ #!/bin/bash
2
+ # Parallel evolution dispatcher - manages worker pool
3
+
4
+ set -e
5
+
6
+ # Load configuration
7
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ # shellcheck source=../lib/config.sh
9
+ source "$SCRIPT_DIR/../lib/config.sh"
10
+ # shellcheck source=../lib/csv-lock.sh
11
+ source "$SCRIPT_DIR/../lib/csv-lock.sh"
12
+ load_config
13
+
14
+ # Parse arguments
15
+ timeout_seconds=""
16
+
17
+ while [[ $# -gt 0 ]]; do
18
+ case $1 in
19
+ --timeout)
20
+ timeout_seconds="$2"
21
+ shift 2
22
+ ;;
23
+ *)
24
+ echo "[ERROR] Unknown option: $1" >&2
25
+ exit 1
26
+ ;;
27
+ esac
28
+ done
29
+
30
+ echo "[DISPATCHER] Starting parallel evolution with $MAX_WORKERS max workers"
31
+ [[ -n $timeout_seconds ]] && echo "[DISPATCHER] Using timeout: ${timeout_seconds} seconds per worker"
32
+
33
+ # Validate workspace
34
+ if [[ ! -d "$FULL_EVOLUTION_DIR" ]]; then
35
+ echo "[ERROR] Evolution directory not found: $FULL_EVOLUTION_DIR" >&2
36
+ exit 1
37
+ fi
38
+
39
+ if [[ ! -f "$FULL_CSV_PATH" ]]; then
40
+ echo "[ERROR] CSV file not found: $FULL_CSV_PATH" >&2
41
+ exit 1
42
+ fi
43
+
44
+ # Prepare logging
45
+ mkdir -p logs
46
+
47
+ # Track active workers
48
+ declare -A worker_pids=()
49
+ consecutive_failures=0
50
+ MAX_FAILURES=10
51
+ rate_limit_hit=false
52
+
53
+ # Count pending candidates
54
+ count_pending() {
55
+ if ! read_csv_with_lock csv_content; then
56
+ echo "0"
57
+ return
58
+ fi
59
+
60
+ echo "$csv_content" | awk -F',' 'NR>1 && $5 == "pending" { count++ } END { print count+0 }'
61
+ }
62
+
63
+ # Start a worker
64
+ start_worker() {
65
+ local worker_cmd="$SCRIPT_DIR/claude-evolve-worker"
66
+ [[ -n $timeout_seconds ]] && worker_cmd="$worker_cmd --timeout $timeout_seconds"
67
+
68
+ echo "[DISPATCHER] Starting worker..."
69
+ $worker_cmd &
70
+ local pid=$!
71
+ worker_pids[$pid]=1
72
+ echo "[DISPATCHER] Worker $pid started"
73
+ }
74
+
75
+ # Check worker exit status and handle accordingly
76
+ handle_worker_exit() {
77
+ local pid="$1"
78
+ local exit_code="$2"
79
+
80
+ case $exit_code in
81
+ 0)
82
+ echo "[DISPATCHER] Worker $pid completed successfully"
83
+ consecutive_failures=0
84
+ ;;
85
+ 1)
86
+ echo "[DISPATCHER] Worker $pid failed"
87
+ ((consecutive_failures++))
88
+ ;;
89
+ 2)
90
+ echo "[DISPATCHER] Worker $pid hit rate limit"
91
+ rate_limit_hit=true
92
+ ;;
93
+ 130)
94
+ echo "[DISPATCHER] Worker $pid interrupted"
95
+ ;;
96
+ *)
97
+ echo "[DISPATCHER] Worker $pid exited with code $exit_code"
98
+ ((consecutive_failures++))
99
+ ;;
100
+ esac
101
+
102
+ # Remove from tracking
103
+ unset worker_pids[$pid]
104
+ }
105
+
106
+ # Signal handler for graceful shutdown
107
+ shutdown_workers() {
108
+ echo "[DISPATCHER] Shutting down workers..."
109
+ for pid in "${!worker_pids[@]}"; do
110
+ if kill -0 "$pid" 2>/dev/null; then
111
+ echo "[DISPATCHER] Stopping worker $pid"
112
+ kill -TERM "$pid" 2>/dev/null || true
113
+ fi
114
+ done
115
+
116
+ # Wait for workers to exit
117
+ local timeout=10
118
+ while [[ ${#worker_pids[@]} -gt 0 && $timeout -gt 0 ]]; do
119
+ sleep 1
120
+ ((timeout--))
121
+
122
+ for pid in "${!worker_pids[@]}"; do
123
+ if ! kill -0 "$pid" 2>/dev/null; then
124
+ unset worker_pids[$pid]
125
+ fi
126
+ done
127
+ done
128
+
129
+ # Force kill remaining workers
130
+ for pid in "${!worker_pids[@]}"; do
131
+ if kill -0 "$pid" 2>/dev/null; then
132
+ echo "[DISPATCHER] Force killing worker $pid"
133
+ kill -KILL "$pid" 2>/dev/null || true
134
+ fi
135
+ done
136
+
137
+ echo "[DISPATCHER] Shutdown complete"
138
+ exit 0
139
+ }
140
+
141
+ trap shutdown_workers INT TERM
142
+
143
+ # Main dispatcher loop
144
+ echo "[DISPATCHER] Starting main dispatch loop"
145
+
146
+ while true; do
147
+ # Check if too many consecutive failures
148
+ if [[ $consecutive_failures -ge $MAX_FAILURES ]]; then
149
+ echo "[ERROR] Too many consecutive failures ($consecutive_failures). Stopping." >&2
150
+ break
151
+ fi
152
+
153
+ # Check if rate limit was hit
154
+ if [[ $rate_limit_hit == true ]]; then
155
+ echo "[DISPATCHER] Rate limit detected. Waiting 60 seconds before retrying..."
156
+ sleep 60
157
+ rate_limit_hit=false
158
+ fi
159
+
160
+ # Count pending work
161
+ pending_count=$(count_pending)
162
+ active_workers=${#worker_pids[@]}
163
+
164
+ echo "[DISPATCHER] Status: $pending_count pending, $active_workers active workers"
165
+
166
+ # If no pending work and no active workers, try to generate more ideas
167
+ if [[ $pending_count -eq 0 && $active_workers -eq 0 ]]; then
168
+ echo "[DISPATCHER] No work available. Generating new ideas..."
169
+
170
+ ideate_script="$SCRIPT_DIR/claude-evolve-ideate"
171
+ if [[ ! -f "$ideate_script" ]]; then
172
+ echo "[DISPATCHER] No ideate script found. Evolution complete."
173
+ break
174
+ fi
175
+
176
+ if ! "$ideate_script"; then
177
+ echo "[ERROR] Failed to generate new ideas" >&2
178
+ break
179
+ fi
180
+
181
+ # Recount after ideation
182
+ pending_count=$(count_pending)
183
+ echo "[DISPATCHER] Generated ideas. New pending count: $pending_count"
184
+ fi
185
+
186
+ # Start workers if we have pending work and capacity
187
+ while [[ $pending_count -gt 0 && $active_workers -lt $MAX_WORKERS ]]; do
188
+ start_worker
189
+ active_workers=${#worker_pids[@]}
190
+ ((pending_count--)) # Optimistically assume this will be picked up
191
+ done
192
+
193
+ # If no active workers and no pending work, we're done
194
+ if [[ $active_workers -eq 0 && $pending_count -eq 0 ]]; then
195
+ echo "[DISPATCHER] No active workers and no pending work. Evolution complete."
196
+ break
197
+ fi
198
+
199
+ # Wait for any worker to finish
200
+ if [[ $active_workers -gt 0 ]]; then
201
+ # Wait for any child process to exit
202
+ wait -n
203
+
204
+ # Check which workers have finished
205
+ for pid in "${!worker_pids[@]}"; do
206
+ if ! kill -0 "$pid" 2>/dev/null; then
207
+ # Get exit status
208
+ wait "$pid"
209
+ exit_code=$?
210
+ handle_worker_exit "$pid" "$exit_code"
211
+ fi
212
+ done
213
+ else
214
+ # No active workers but we might be waiting for ideation or have pending work
215
+ sleep 1
216
+ fi
217
+ done
218
+
219
+ echo "[DISPATCHER] Evolution run complete"
@@ -0,0 +1,259 @@
1
+ #!/bin/bash
2
+ # Worker process for parallel evolution execution
3
+ # Processes a single evolution candidate and exits
4
+
5
+ set -e
6
+
7
+ # Load configuration
8
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+ # shellcheck source=../lib/config.sh
10
+ source "$SCRIPT_DIR/../lib/config.sh"
11
+ # shellcheck source=../lib/csv-lock.sh
12
+ source "$SCRIPT_DIR/../lib/csv-lock.sh"
13
+ load_config
14
+
15
+ # Parse arguments
16
+ timeout_seconds=""
17
+ candidate_id=""
18
+
19
+ while [[ $# -gt 0 ]]; do
20
+ case $1 in
21
+ --timeout)
22
+ if [[ -z ${2:-} ]] || [[ ! $2 =~ ^[0-9]+$ ]] || [[ $2 -eq 0 ]]; then
23
+ echo "[ERROR] --timeout requires a positive integer (seconds)" >&2
24
+ exit 1
25
+ fi
26
+ timeout_seconds="$2"
27
+ shift 2
28
+ ;;
29
+ --id)
30
+ candidate_id="$2"
31
+ shift 2
32
+ ;;
33
+ *)
34
+ echo "[ERROR] Unknown option: $1" >&2
35
+ exit 1
36
+ ;;
37
+ esac
38
+ done
39
+
40
+ # If no ID provided, find next pending
41
+ if [[ -z $candidate_id ]]; then
42
+ candidate_id=$(find_next_pending_with_lock)
43
+ if [[ -z $candidate_id ]]; then
44
+ echo "[INFO] No pending candidates found"
45
+ exit 0
46
+ fi
47
+ else
48
+ # Mark specified candidate as running
49
+ update_csv_row_with_lock "$candidate_id" "status" "running"
50
+ fi
51
+
52
+ echo "[WORKER-$$] Processing candidate ID: $candidate_id"
53
+
54
+ # Validate workspace
55
+ if [[ ! -d "$FULL_EVOLUTION_DIR" ]]; then
56
+ echo "[ERROR] Evolution directory not found: $FULL_EVOLUTION_DIR" >&2
57
+ exit 1
58
+ fi
59
+
60
+ # Create log file for this run
61
+ mkdir -p logs
62
+ LOGFILE="logs/worker-${candidate_id}-$(date +%Y%m%d_%H%M%S).txt"
63
+
64
+ # Find candidate in CSV
65
+ row_data=""
66
+ if ! read_csv_with_lock csv_content; then
67
+ echo "[ERROR] Failed to read CSV" >&2
68
+ exit 1
69
+ fi
70
+
71
+ # Extract candidate data
72
+ found=false
73
+ while IFS=, read -r csv_id csv_based_on csv_desc csv_perf csv_stat; do
74
+ if [[ $csv_id == "$candidate_id" ]]; then
75
+ id="$csv_id"
76
+ based_on_id="$csv_based_on"
77
+ description="$csv_desc"
78
+ performance="$csv_perf"
79
+ status="$csv_stat"
80
+ found=true
81
+ break
82
+ fi
83
+ done <<< "$csv_content"
84
+
85
+ if [[ $found == false ]]; then
86
+ echo "[ERROR] Candidate ID not found: $candidate_id" >&2
87
+ exit 1
88
+ fi
89
+
90
+ # Clean up description
91
+ description=${description#\"}
92
+ description=${description%\"}
93
+
94
+ echo "[WORKER-$$] Description: $description"
95
+ echo "[WORKER-$$] Based on ID: $based_on_id"
96
+
97
+ # Determine parent algorithm
98
+ if [[ -z $based_on_id || $based_on_id == "0" || $based_on_id == '""' ]]; then
99
+ parent_file="$FULL_ALGORITHM_PATH"
100
+ echo "[WORKER-$$] Using base algorithm"
101
+ else
102
+ # Handle both old and new format IDs
103
+ if [[ $based_on_id =~ ^[0-9]+$ ]]; then
104
+ parent_file="$FULL_OUTPUT_DIR/evolution_id${based_on_id}.py"
105
+ else
106
+ parent_file="$FULL_OUTPUT_DIR/evolution_${based_on_id}.py"
107
+ fi
108
+
109
+ if [[ ! -f $parent_file ]]; then
110
+ echo "[ERROR] Parent algorithm not found: $parent_file" >&2
111
+ update_csv_row_with_lock "$candidate_id" "status" "failed"
112
+ exit 1
113
+ fi
114
+ fi
115
+
116
+ # Generate output file
117
+ if [[ $id =~ ^[0-9]+$ ]]; then
118
+ output_file="$FULL_OUTPUT_DIR/evolution_id${id}.py"
119
+ else
120
+ output_file="$FULL_OUTPUT_DIR/evolution_${id}.py"
121
+ fi
122
+
123
+ # Copy parent to output
124
+ cp "$parent_file" "$output_file"
125
+ echo "[WORKER-$$] Copied parent to: $output_file"
126
+
127
+ # Generate mutation (skip for baseline)
128
+ if [[ $id == "000" || $id == "0" || $id == "gen00-000" ]]; then
129
+ echo "[WORKER-$$] Baseline algorithm - skipping mutation"
130
+ else
131
+ # Check for claude CLI
132
+ claude_cmd="${CLAUDE_CMD:-claude}"
133
+ if ! command -v "$claude_cmd" >/dev/null 2>&1; then
134
+ echo "[ERROR] Claude CLI not found" >&2
135
+ update_csv_row_with_lock "$candidate_id" "status" "failed"
136
+ exit 1
137
+ fi
138
+
139
+ CLAUDE_MODEL="sonnet"
140
+ echo "[WORKER-$$] Using Claude $CLAUDE_MODEL for mutation"
141
+
142
+ # Create mutation prompt
143
+ prompt="Edit the file $output_file to implement this specific change: $description
144
+
145
+ Requirements:
146
+ - Edit the file directly (don't just provide comments or suggestions)
147
+ - Maintain the same function signatures and interfaces
148
+ - Make the specific change described above
149
+ - Ensure the code runs without syntax errors
150
+ - Add proper error handling if needed
151
+
152
+ The file currently contains the parent algorithm. Modify it according to the description above."
153
+
154
+ # Log prompt
155
+ {
156
+ echo "=== WORKER $$ - MUTATION PROMPT ==="
157
+ echo "ID: $id"
158
+ echo "Timestamp: $(date)"
159
+ echo "$prompt"
160
+ echo
161
+ } >> "$LOGFILE"
162
+
163
+ # Call Claude
164
+ echo "[WORKER-$$] Calling Claude to apply mutation..."
165
+ claude_output=$(echo "$prompt" | "$claude_cmd" --dangerously-skip-permissions --model $CLAUDE_MODEL -p 2>&1 | tee -a "$LOGFILE")
166
+ claude_exit_code=${PIPESTATUS[1]}
167
+
168
+ # Check for rate limit
169
+ if echo "$claude_output" | grep -q "Claude AI usage limit reached"; then
170
+ echo "[ERROR] Claude API rate limit reached" >&2
171
+ # Reset to pending so it can be retried later
172
+ update_csv_row_with_lock "$candidate_id" "status" "pending"
173
+ exit 2 # Special exit code for rate limit
174
+ fi
175
+
176
+ if [[ $claude_exit_code -ne 0 ]]; then
177
+ echo "[ERROR] Claude failed to mutate algorithm" >&2
178
+ update_csv_row_with_lock "$candidate_id" "status" "failed"
179
+ exit 1
180
+ fi
181
+ fi
182
+
183
+ # Run evaluator
184
+ echo "[WORKER-$$] Running evaluation..."
185
+ eval_output=""
186
+ eval_exit_code=0
187
+
188
+ if [[ -n $timeout_seconds ]]; then
189
+ echo "[WORKER-$$] Evaluation timeout: ${timeout_seconds}s"
190
+ if eval_output=$(timeout "$timeout_seconds" "$PYTHON_CMD" "$FULL_EVALUATOR_PATH" "$output_file" 2>&1); then
191
+ eval_exit_code=0
192
+ else
193
+ eval_exit_code=$?
194
+ if [[ $eval_exit_code -eq 124 ]]; then
195
+ echo "[ERROR] Evaluation timed out" >&2
196
+ update_csv_row_with_lock "$candidate_id" "status" "timeout"
197
+ exit 1
198
+ fi
199
+ fi
200
+ else
201
+ if eval_output=$("$PYTHON_CMD" "$FULL_EVALUATOR_PATH" "$output_file" 2>&1); then
202
+ eval_exit_code=0
203
+ else
204
+ eval_exit_code=$?
205
+ fi
206
+ fi
207
+
208
+ # Log evaluator output
209
+ {
210
+ echo "=== WORKER $$ - EVALUATOR OUTPUT ==="
211
+ echo "Exit code: $eval_exit_code"
212
+ echo "$eval_output"
213
+ echo
214
+ } >> "$LOGFILE"
215
+
216
+ # Process results
217
+ if [[ $eval_exit_code -eq 0 ]]; then
218
+ # Extract score
219
+ if score=$(echo "$eval_output" | grep -o '"score"[[:space:]]*:[[:space:]]*[0-9.]*' | cut -d: -f2 | tr -d ' '); then
220
+ if [[ -n $score ]]; then
221
+ if (( $(echo "$score == 0" | bc -l) )); then
222
+ update_csv_row_with_lock "$candidate_id" "status" "failed"
223
+ update_csv_row_with_lock "$candidate_id" "performance" "$score"
224
+ echo "[WORKER-$$] ✗ Evaluation failed with score 0"
225
+ exit 1
226
+ else
227
+ update_csv_row_with_lock "$candidate_id" "performance" "$score"
228
+ update_csv_row_with_lock "$candidate_id" "status" "complete"
229
+ echo "[WORKER-$$] ✓ Evaluation complete, score: $score"
230
+ exit 0
231
+ fi
232
+ fi
233
+ fi
234
+
235
+ # Try "performance" field
236
+ if score=$(echo "$eval_output" | grep -o '"performance"[[:space:]]*:[[:space:]]*[0-9.]*' | cut -d: -f2 | tr -d ' '); then
237
+ if [[ -n $score ]]; then
238
+ if (( $(echo "$score == 0" | bc -l) )); then
239
+ update_csv_row_with_lock "$candidate_id" "status" "failed"
240
+ update_csv_row_with_lock "$candidate_id" "performance" "$score"
241
+ echo "[WORKER-$$] ✗ Evaluation failed with score 0"
242
+ exit 1
243
+ else
244
+ update_csv_row_with_lock "$candidate_id" "performance" "$score"
245
+ update_csv_row_with_lock "$candidate_id" "status" "complete"
246
+ echo "[WORKER-$$] ✓ Evaluation complete, score: $score"
247
+ exit 0
248
+ fi
249
+ fi
250
+ fi
251
+
252
+ echo "[ERROR] No score found in evaluator output" >&2
253
+ update_csv_row_with_lock "$candidate_id" "status" "failed"
254
+ exit 1
255
+ else
256
+ echo "[ERROR] Evaluator failed with exit code $eval_exit_code" >&2
257
+ update_csv_row_with_lock "$candidate_id" "status" "failed"
258
+ exit 1
259
+ fi
package/lib/config.sh CHANGED
@@ -19,6 +19,11 @@ DEFAULT_STRUCTURAL_MUTATION=3
19
19
  DEFAULT_CROSSOVER_HYBRID=4
20
20
  DEFAULT_NUM_ELITES=3
21
21
 
22
+ # Default parallel execution values
23
+ DEFAULT_PARALLEL_ENABLED=false
24
+ DEFAULT_MAX_WORKERS=4
25
+ DEFAULT_LOCK_TIMEOUT=30
26
+
22
27
  # Load configuration from config file
23
28
  load_config() {
24
29
  # Set defaults first
@@ -38,6 +43,11 @@ load_config() {
38
43
  STRUCTURAL_MUTATION="$DEFAULT_STRUCTURAL_MUTATION"
39
44
  CROSSOVER_HYBRID="$DEFAULT_CROSSOVER_HYBRID"
40
45
  NUM_ELITES="$DEFAULT_NUM_ELITES"
46
+
47
+ # Set parallel execution defaults
48
+ PARALLEL_ENABLED="$DEFAULT_PARALLEL_ENABLED"
49
+ MAX_WORKERS="$DEFAULT_MAX_WORKERS"
50
+ LOCK_TIMEOUT="$DEFAULT_LOCK_TIMEOUT"
41
51
 
42
52
  # Single config file location: evolution/config.yaml
43
53
  local config_file="evolution/config.yaml"
@@ -47,6 +57,7 @@ load_config() {
47
57
  echo "[INFO] Loading configuration from: $config_file"
48
58
  # Simple YAML parsing for key: value pairs and nested structures
49
59
  local in_ideation_section=false
60
+ local in_parallel_section=false
50
61
  while IFS=': ' read -r key value; do
51
62
  # Skip comments and empty lines
52
63
  [[ $key =~ ^[[:space:]]*# ]] || [[ -z $key ]] && continue
@@ -58,13 +69,19 @@ load_config() {
58
69
  # Remove quotes from value
59
70
  value=$(echo "$value" | sed 's/^"//;s/"$//')
60
71
 
61
- # Handle nested ideation_strategies section
72
+ # Handle nested sections
62
73
  if [[ $key == "ideation_strategies" ]]; then
63
74
  in_ideation_section=true
75
+ in_parallel_section=false
76
+ continue
77
+ elif [[ $key == "parallel" ]]; then
78
+ in_parallel_section=true
79
+ in_ideation_section=false
64
80
  continue
65
- elif [[ $key =~ ^[a-z_]+ ]] && [[ $in_ideation_section == true ]]; then
66
- # Top-level key found, exit ideation section
81
+ elif [[ $key =~ ^[a-z_]+ ]] && [[ $in_ideation_section == true || $in_parallel_section == true ]]; then
82
+ # Top-level key found, exit nested sections
67
83
  in_ideation_section=false
84
+ in_parallel_section=false
68
85
  fi
69
86
 
70
87
  if [[ $in_ideation_section == true ]]; then
@@ -77,6 +94,13 @@ load_config() {
77
94
  crossover_hybrid) CROSSOVER_HYBRID="$value" ;;
78
95
  num_elites) NUM_ELITES="$value" ;;
79
96
  esac
97
+ elif [[ $in_parallel_section == true ]]; then
98
+ # Handle indented keys in parallel section
99
+ case $key in
100
+ enabled) PARALLEL_ENABLED="$value" ;;
101
+ max_workers) MAX_WORKERS="$value" ;;
102
+ lock_timeout) LOCK_TIMEOUT="$value" ;;
103
+ esac
80
104
  else
81
105
  # Handle top-level keys
82
106
  case $key in
@@ -149,6 +173,8 @@ show_config() {
149
173
  echo " CSV file: $FULL_CSV_PATH"
150
174
  echo " Output directory: $FULL_OUTPUT_DIR"
151
175
  echo " Parent selection: $PARENT_SELECTION"
152
- echo " Max ideas: $MAX_IDEAS"
153
176
  echo " Python command: $PYTHON_CMD"
177
+ echo " Parallel enabled: $PARALLEL_ENABLED"
178
+ echo " Max workers: $MAX_WORKERS"
179
+ echo " Lock timeout: $LOCK_TIMEOUT"
154
180
  }
@@ -0,0 +1,151 @@
1
+ #!/bin/bash
2
+ # CSV locking functions for parallel execution
3
+
4
+ # Lock file location
5
+ CSV_LOCKFILE="${EVOLUTION_DIR:-evolution}/.evolution.csv.lock"
6
+
7
+ # Acquire exclusive lock on CSV file
8
+ # Usage: acquire_csv_lock [timeout_seconds]
9
+ acquire_csv_lock() {
10
+ local timeout="${1:-30}"
11
+ local lockdir="$(dirname "$CSV_LOCKFILE")"
12
+
13
+ # Ensure lock directory exists
14
+ mkdir -p "$lockdir"
15
+
16
+ # Try to acquire lock with timeout
17
+ if command -v flock >/dev/null 2>&1; then
18
+ # Use flock if available (Linux)
19
+ exec 200>"$CSV_LOCKFILE"
20
+ if ! flock -w "$timeout" -x 200; then
21
+ echo "ERROR: Failed to acquire CSV lock within $timeout seconds" >&2
22
+ return 1
23
+ fi
24
+ else
25
+ # Fallback for systems without flock (macOS)
26
+ local start_time=$(date +%s)
27
+ while ! (set -C; echo $$ > "$CSV_LOCKFILE") 2>/dev/null; do
28
+ local current_time=$(date +%s)
29
+ if [ $((current_time - start_time)) -ge $timeout ]; then
30
+ echo "ERROR: Failed to acquire CSV lock within $timeout seconds" >&2
31
+ return 1
32
+ fi
33
+ sleep 0.1
34
+ done
35
+ fi
36
+ return 0
37
+ }
38
+
39
+ # Release CSV lock
40
+ release_csv_lock() {
41
+ if command -v flock >/dev/null 2>&1; then
42
+ # Release flock
43
+ exec 200>&-
44
+ else
45
+ # Remove lock file
46
+ rm -f "$CSV_LOCKFILE"
47
+ fi
48
+ }
49
+
50
+ # Read CSV with lock
51
+ # Usage: read_csv_with_lock <variable_name>
52
+ read_csv_with_lock() {
53
+ local var_name="$1"
54
+ local csv_file="${EVOLUTION_DIR:-evolution}/evolution.csv"
55
+
56
+ if ! acquire_csv_lock; then
57
+ return 1
58
+ fi
59
+
60
+ # Read CSV content
61
+ if [ -f "$csv_file" ]; then
62
+ eval "$var_name=\$(cat '$csv_file')"
63
+ else
64
+ eval "$var_name=''"
65
+ fi
66
+
67
+ release_csv_lock
68
+ return 0
69
+ }
70
+
71
+ # Write CSV with lock
72
+ # Usage: echo "content" | write_csv_with_lock
73
+ write_csv_with_lock() {
74
+ local csv_file="${EVOLUTION_DIR:-evolution}/evolution.csv"
75
+ local temp_file="${csv_file}.tmp.$$"
76
+
77
+ if ! acquire_csv_lock; then
78
+ return 1
79
+ fi
80
+
81
+ # Write to temporary file first
82
+ cat > "$temp_file"
83
+
84
+ # Atomic move
85
+ mv -f "$temp_file" "$csv_file"
86
+
87
+ release_csv_lock
88
+ return 0
89
+ }
90
+
91
+ # Update single CSV row with lock
92
+ # Usage: update_csv_row_with_lock <id> <field> <value>
93
+ update_csv_row_with_lock() {
94
+ local target_id="$1"
95
+ local field="$2"
96
+ local value="$3"
97
+ local csv_file="${EVOLUTION_DIR:-evolution}/evolution.csv"
98
+
99
+ if ! acquire_csv_lock; then
100
+ return 1
101
+ fi
102
+
103
+ # Determine field position
104
+ local field_pos
105
+ case "$field" in
106
+ "status") field_pos=5 ;;
107
+ "performance") field_pos=4 ;;
108
+ "description") field_pos=3 ;;
109
+ "basedOnId") field_pos=2 ;;
110
+ *)
111
+ echo "ERROR: Unknown field: $field" >&2
112
+ release_csv_lock
113
+ return 1
114
+ ;;
115
+ esac
116
+
117
+ # Update CSV using awk
118
+ awk -F',' -v OFS=',' -v id="$target_id" -v pos="$field_pos" -v val="$value" '
119
+ NR==1 || $1 != id { print }
120
+ $1 == id { $pos = val; print }
121
+ ' "$csv_file" > "${csv_file}.tmp" && mv -f "${csv_file}.tmp" "$csv_file"
122
+
123
+ release_csv_lock
124
+ return 0
125
+ }
126
+
127
+ # Find next pending candidate with lock
128
+ # Usage: next_pending=$(find_next_pending_with_lock)
129
+ find_next_pending_with_lock() {
130
+ local csv_file="${EVOLUTION_DIR:-evolution}/evolution.csv"
131
+
132
+ if ! acquire_csv_lock; then
133
+ return 1
134
+ fi
135
+
136
+ # Find oldest pending candidate and update to running
137
+ local candidate=$(awk -F',' '
138
+ NR>1 && $5 == "pending" { print $1; exit }
139
+ ' "$csv_file")
140
+
141
+ if [ -n "$candidate" ]; then
142
+ # Update status to running while we have the lock
143
+ awk -F',' -v OFS=',' -v id="$candidate" '
144
+ NR==1 || $1 != id { print }
145
+ $1 == id { $5 = "running"; print }
146
+ ' "$csv_file" > "${csv_file}.tmp" && mv -f "${csv_file}.tmp" "$csv_file"
147
+ fi
148
+
149
+ release_csv_lock
150
+ echo "$candidate"
151
+ }
package/package.json CHANGED
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "name": "claude-evolve",
3
- "version": "1.2.13",
3
+ "version": "1.3.0",
4
4
  "bin": {
5
5
  "claude-evolve": "./bin/claude-evolve",
6
6
  "claude-evolve-main": "./bin/claude-evolve-main",
7
7
  "claude-evolve-setup": "./bin/claude-evolve-setup",
8
8
  "claude-evolve-ideate": "./bin/claude-evolve-ideate",
9
9
  "claude-evolve-run": "./bin/claude-evolve-run",
10
+ "claude-evolve-run-parallel": "./bin/claude-evolve-run-parallel",
11
+ "claude-evolve-worker": "./bin/claude-evolve-worker",
10
12
  "claude-evolve-analyze": "./bin/claude-evolve-analyze",
11
13
  "claude-evolve-config": "./bin/claude-evolve-config"
12
14
  },
@@ -35,4 +35,15 @@ ideation_strategies:
35
35
  num_elites: 3
36
36
 
37
37
  # Python command to use for evaluation
38
- python_cmd: "python3"
38
+ python_cmd: "python3"
39
+
40
+ # Parallel execution configuration
41
+ parallel:
42
+ # Enable parallel execution of evolution candidates
43
+ enabled: false
44
+
45
+ # Maximum number of worker processes to run simultaneously
46
+ max_workers: 4
47
+
48
+ # Timeout in seconds when waiting for CSV locks
49
+ lock_timeout: 30