claude-evolve 1.3.39 → 1.3.41

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.
@@ -1,389 +0,0 @@
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
-
13
- # Use CLAUDE_EVOLVE_CONFIG if set, otherwise default
14
- if [[ -n ${CLAUDE_EVOLVE_CONFIG:-} ]]; then
15
- load_config "$CLAUDE_EVOLVE_CONFIG"
16
- else
17
- load_config
18
- fi
19
-
20
- # Parse arguments
21
- timeout_seconds=""
22
-
23
- while [[ $# -gt 0 ]]; do
24
- case $1 in
25
- --timeout)
26
- timeout_seconds="$2"
27
- shift 2
28
- ;;
29
- *)
30
- echo "[ERROR] Unknown option: $1" >&2
31
- exit 1
32
- ;;
33
- esac
34
- done
35
-
36
- echo "[DISPATCHER] Starting parallel evolution with $MAX_WORKERS max workers"
37
- [[ -n $timeout_seconds ]] && echo "[DISPATCHER] Using timeout: ${timeout_seconds} seconds per worker"
38
-
39
- # Validate workspace
40
- if [[ ! -d "$FULL_EVOLUTION_DIR" ]]; then
41
- echo "[ERROR] Evolution directory not found: $FULL_EVOLUTION_DIR" >&2
42
- exit 1
43
- fi
44
-
45
- if [[ ! -f "$FULL_CSV_PATH" ]]; then
46
- echo "[ERROR] CSV file not found: $FULL_CSV_PATH" >&2
47
- exit 1
48
- fi
49
-
50
- # Prepare logging
51
- mkdir -p logs
52
-
53
- # Track active workers (using regular array for compatibility)
54
- worker_pids=()
55
- consecutive_failures=0
56
- MAX_FAILURES=10
57
- rate_limit_hit=false
58
-
59
- # Count pending candidates
60
- count_pending() {
61
- if ! read_csv_with_lock csv_content; then
62
- echo "0"
63
- return
64
- fi
65
-
66
- # Use Python for proper CSV parsing with quoted fields
67
- echo "$csv_content" | "$PYTHON_CMD" -c "
68
- import csv
69
- import sys
70
- reader = csv.reader(sys.stdin)
71
- next(reader) # Skip header
72
- count = 0
73
- for row in reader:
74
- # If row has fewer than 5 fields, treat as pending
75
- if len(row) < 5:
76
- count += 1
77
- elif len(row) >= 5 and (row[4] == 'pending' or row[4] == ''):
78
- count += 1
79
- print(count)
80
- "
81
- }
82
-
83
- # Start a worker
84
- start_worker() {
85
- local worker_cmd="$SCRIPT_DIR/claude-evolve-worker"
86
- [[ -n $timeout_seconds ]] && worker_cmd="$worker_cmd --timeout $timeout_seconds"
87
-
88
- echo "[DISPATCHER] Starting worker..."
89
- $worker_cmd &
90
- local pid=$!
91
-
92
- # Verify worker started successfully
93
- sleep 0.1
94
- if kill -0 "$pid" 2>/dev/null; then
95
- worker_pids+=($pid)
96
- echo "[DISPATCHER] Worker $pid started"
97
- else
98
- echo "[ERROR] Worker failed to start" >&2
99
- ((consecutive_failures++))
100
- fi
101
- }
102
-
103
- # Check worker exit status and handle accordingly
104
- handle_worker_exit() {
105
- local pid="$1"
106
- local exit_code="$2"
107
-
108
- case $exit_code in
109
- 0)
110
- echo "[DISPATCHER] Worker $pid completed successfully"
111
- consecutive_failures=0
112
- ;;
113
- 1)
114
- echo "[DISPATCHER] Worker $pid failed"
115
- ((consecutive_failures++))
116
- ;;
117
- 2)
118
- echo "[DISPATCHER] Worker $pid hit rate limit"
119
- rate_limit_hit=true
120
- ;;
121
- 130)
122
- echo "[DISPATCHER] Worker $pid interrupted"
123
- ;;
124
- *)
125
- echo "[DISPATCHER] Worker $pid exited with code $exit_code"
126
- ((consecutive_failures++))
127
- ;;
128
- esac
129
-
130
- # Remove from tracking
131
- local new_pids=()
132
- for p in "${worker_pids[@]}"; do
133
- [[ $p != "$pid" ]] && new_pids+=($p)
134
- done
135
- worker_pids=("${new_pids[@]}")
136
- }
137
-
138
- # Signal handler for graceful shutdown
139
- shutdown_workers() {
140
- echo "[DISPATCHER] Shutting down workers..."
141
- for pid in "${worker_pids[@]}"; do
142
- if kill -0 "$pid" 2>/dev/null; then
143
- echo "[DISPATCHER] Stopping worker $pid"
144
- kill -TERM "$pid" 2>/dev/null || true
145
- fi
146
- done
147
-
148
- # Wait for workers to exit
149
- local timeout=10
150
- while [[ ${#worker_pids[@]} -gt 0 && $timeout -gt 0 ]]; do
151
- sleep 1
152
- ((timeout--))
153
-
154
- local new_pids=()
155
- for pid in "${worker_pids[@]}"; do
156
- if kill -0 "$pid" 2>/dev/null; then
157
- new_pids+=($pid)
158
- fi
159
- done
160
- worker_pids=("${new_pids[@]}")
161
- done
162
-
163
- # Force kill remaining workers
164
- for pid in "${worker_pids[@]}"; do
165
- if kill -0 "$pid" 2>/dev/null; then
166
- echo "[DISPATCHER] Force killing worker $pid"
167
- kill -KILL "$pid" 2>/dev/null || true
168
- fi
169
- done
170
-
171
- echo "[DISPATCHER] Shutdown complete"
172
- exit 0
173
- }
174
-
175
- # Better signal handling with logging
176
- handle_signal() {
177
- local signal="$1"
178
- echo "[DISPATCHER] Received signal: $signal" >&2
179
- echo "[DISPATCHER] Active workers: ${#worker_pids[@]}" >&2
180
-
181
- # For expensive workers, give option to continue
182
- if [[ ${#worker_pids[@]} -gt 0 ]]; then
183
- echo "[DISPATCHER] Warning: ${#worker_pids[@]} expensive workers are still running!" >&2
184
- echo "[DISPATCHER] Press Ctrl+C again within 5 seconds to force shutdown, or wait..." >&2
185
-
186
- # Give 5 seconds to reconsider
187
- local count=5
188
- while [[ $count -gt 0 ]]; do
189
- sleep 1
190
- ((count--))
191
- # Check if we got another signal
192
- if [[ -f /tmp/evolve-force-shutdown-$$ ]]; then
193
- echo "[DISPATCHER] Force shutdown requested" >&2
194
- rm -f /tmp/evolve-force-shutdown-$$
195
- shutdown_workers
196
- exit 1
197
- fi
198
- done
199
-
200
- echo "[DISPATCHER] Continuing with active workers..." >&2
201
- return
202
- fi
203
-
204
- shutdown_workers
205
- }
206
-
207
- # Set up signal handlers
208
- trap 'handle_signal INT' INT
209
- trap 'handle_signal TERM' TERM
210
- trap 'echo "[DISPATCHER] Exiting with code $?" >&2' EXIT
211
-
212
- # Check for stuck "running" candidates from previous runs
213
- check_stuck_candidates() {
214
- if read_csv_with_lock csv_content; then
215
- local stuck_count=$(echo "$csv_content" | "$PYTHON_CMD" -c "
216
- import csv
217
- import sys
218
- reader = csv.reader(sys.stdin)
219
- next(reader) # Skip header
220
- count = 0
221
- for row in reader:
222
- if len(row) >= 5 and row[4] == 'running':
223
- count += 1
224
- print(count)
225
- ")
226
- if [[ $stuck_count -gt 0 ]]; then
227
- echo "[DISPATCHER] Found $stuck_count candidates stuck in 'running' status"
228
- echo "[DISPATCHER] Resetting them to 'pending' for retry..."
229
-
230
- # Reset stuck candidates
231
- if acquire_csv_lock; then
232
- "$PYTHON_CMD" -c "
233
- import csv
234
- import sys
235
-
236
- # Read CSV
237
- with open('$FULL_CSV_PATH', 'r') as f:
238
- reader = csv.reader(f)
239
- rows = list(reader)
240
-
241
- # Reset running to pending
242
- for i in range(1, len(rows)):
243
- if len(rows[i]) >= 5 and rows[i][4] == 'running':
244
- rows[i][4] = 'pending'
245
-
246
- # Write back
247
- with open('${FULL_CSV_PATH}.tmp', 'w', newline='') as f:
248
- writer = csv.writer(f)
249
- writer.writerows(rows)
250
- " && mv -f "${FULL_CSV_PATH}.tmp" "$FULL_CSV_PATH"
251
- release_csv_lock
252
- fi
253
- fi
254
- fi
255
- }
256
-
257
- # Main dispatcher loop
258
- echo "[DISPATCHER] Starting main dispatch loop"
259
-
260
- # Check for stuck candidates from previous runs
261
- check_stuck_candidates
262
-
263
- # Set error handling
264
- set +e # Don't exit on error in the main loop
265
-
266
- while true; do
267
- # In parallel mode, let individual algorithm failures happen
268
- # The generation is finite, so worst case it just completes with many failures
269
-
270
- # Check if rate limit was hit
271
- if [[ $rate_limit_hit == true ]]; then
272
- echo "[DISPATCHER] Rate limit detected. Waiting 60 seconds before retrying..."
273
- sleep 60
274
- rate_limit_hit=false
275
- fi
276
-
277
- # Count pending work
278
- pending_count=$(count_pending || echo "0")
279
- active_workers=${#worker_pids[@]}
280
-
281
- echo "[DISPATCHER] Status: $pending_count pending, $active_workers active workers"
282
-
283
- # Debug: Show CSV status if no pending
284
- if [[ $pending_count -eq 0 ]]; then
285
- total_rows=$(read_csv_with_lock csv_content && echo "$csv_content" | wc -l | xargs)
286
- complete_count=$(read_csv_with_lock csv_content && echo "$csv_content" | "$PYTHON_CMD" -c "
287
- import csv
288
- import sys
289
- reader = csv.reader(sys.stdin)
290
- next(reader) # Skip header
291
- count = 0
292
- for row in reader:
293
- if len(row) >= 5 and row[4] == 'complete':
294
- count += 1
295
- print(count)
296
- ")
297
- echo "[DISPATCHER] CSV has $((total_rows-1)) total candidates, $complete_count complete"
298
- fi
299
-
300
- # If no pending work and no active workers, check for auto-ideation
301
- if [[ $pending_count -eq 0 && $active_workers -eq 0 ]]; then
302
- echo "[DISPATCHER] No pending candidates found."
303
-
304
- # Check if auto ideation is enabled
305
- echo "[DEBUG] AUTO_IDEATE value: '$AUTO_IDEATE'"
306
- if [[ "$AUTO_IDEATE" == "true" || "$AUTO_IDEATE" == "1" ]]; then
307
- echo "[DISPATCHER] Auto ideation is enabled. Generating new ideas..."
308
-
309
- # Check if claude-evolve-ideate exists
310
- ideate_script="$SCRIPT_DIR/claude-evolve-ideate"
311
- if [[ ! -f "$ideate_script" ]]; then
312
- echo "[ERROR] claude-evolve-ideate script not found: $ideate_script" >&2
313
- echo "[DISPATCHER] Evolution complete - no way to generate more ideas."
314
- break
315
- fi
316
-
317
- # Generate new ideas using the multi-strategy approach
318
- echo "[DISPATCHER] Calling claude-evolve-ideate to generate new candidates..."
319
- if ! "$ideate_script"; then
320
- echo "[ERROR] Failed to generate new ideas" >&2
321
- echo "[DISPATCHER] Evolution complete - ideation failed."
322
- break
323
- fi
324
-
325
- echo "[DISPATCHER] New ideas generated successfully. Continuing evolution..."
326
- continue # Go back to start of loop to find the new candidates
327
- else
328
- echo "[DISPATCHER] Auto ideation is disabled. Evolution complete."
329
- echo "[DISPATCHER] Run 'claude-evolve ideate' to generate more candidates."
330
- echo "[DISPATCHER] Exiting main loop: no work remaining" >&2
331
- break
332
- fi
333
- fi
334
-
335
- # Start workers if we have pending work and capacity
336
- while [[ $pending_count -gt 0 && $active_workers -lt $MAX_WORKERS ]]; do
337
- start_worker
338
- active_workers=${#worker_pids[@]}
339
- ((pending_count--)) # Optimistically assume this will be picked up
340
- done
341
-
342
- # If no active workers and no pending work, we're done
343
- if [[ $active_workers -eq 0 && $pending_count -eq 0 ]]; then
344
- echo "[DISPATCHER] No active workers and no pending work. Evolution complete."
345
- break
346
- fi
347
-
348
- # Wait for any worker to finish
349
- if [[ $active_workers -gt 0 ]]; then
350
- # Poll for finished workers (macOS compatible)
351
- sleep 5 # Check every 5 seconds
352
-
353
- # Check which workers have finished
354
- for pid in "${worker_pids[@]}"; do
355
- if ! kill -0 "$pid" 2>/dev/null; then
356
- # Get exit status
357
- wait "$pid" 2>/dev/null
358
- exit_code=$?
359
- handle_worker_exit "$pid" "$exit_code"
360
- fi
361
- done
362
-
363
- # Safety check - if we have workers but the array is corrupted
364
- if [[ ${#worker_pids[@]} -eq 0 ]] && jobs -r | grep -q .; then
365
- echo "[DISPATCHER] Warning: Lost track of workers but jobs still running!" >&2
366
- echo "[DISPATCHER] Attempting to recover..." >&2
367
-
368
- # Try to recover PIDs from jobs
369
- while read -r job_info; do
370
- if [[ $job_info =~ \[([0-9]+)\][[:space:]]+([0-9]+) ]]; then
371
- local recovered_pid="${BASH_REMATCH[2]}"
372
- echo "[DISPATCHER] Recovered worker PID: $recovered_pid" >&2
373
- worker_pids+=($recovered_pid)
374
- fi
375
- done < <(jobs -l)
376
- fi
377
- else
378
- # No active workers but we might be waiting for ideation or have pending work
379
- sleep 1
380
- fi
381
- done
382
-
383
- echo "[DISPATCHER] Evolution run complete"
384
-
385
- # Final cleanup check
386
- if [[ ${#worker_pids[@]} -gt 0 ]]; then
387
- echo "[DISPATCHER] Warning: ${#worker_pids[@]} workers still active at exit"
388
- shutdown_workers
389
- fi