claude-evolve 1.4.12 → 1.5.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.
- package/bin/claude-evolve-autostatus +117 -110
- package/bin/claude-evolve-edit +82 -6
- package/bin/claude-evolve-ideate +604 -209
- package/bin/claude-evolve-ideate.debug +907 -0
- package/bin/claude-evolve-run +49 -7
- package/bin/claude-evolve-worker +249 -25
- package/lib/__pycache__/evolution_csv.cpython-311.pyc +0 -0
- package/lib/__pycache__/evolution_csv.cpython-313.pyc +0 -0
- package/lib/evolution_csv.py +36 -2
- package/lib/validate_parent_ids.py +232 -0
- package/package.json +1 -1
package/bin/claude-evolve-run
CHANGED
|
@@ -13,16 +13,23 @@ if [[ -n ${CLAUDE_EVOLVE_CONFIG:-} ]]; then
|
|
|
13
13
|
else
|
|
14
14
|
# Check if config.yaml exists in current directory
|
|
15
15
|
if [[ -f "config.yaml" ]]; then
|
|
16
|
-
export
|
|
17
|
-
|
|
16
|
+
# Don't export to avoid collision with parallel runs
|
|
17
|
+
CONFIG_FILE="$(pwd)/config.yaml"
|
|
18
|
+
load_config "$CONFIG_FILE"
|
|
18
19
|
else
|
|
19
20
|
load_config
|
|
20
21
|
fi
|
|
21
22
|
fi
|
|
22
23
|
|
|
23
|
-
#
|
|
24
|
-
if [[ -
|
|
25
|
-
|
|
24
|
+
# Store the config path for workers (don't export to avoid collision)
|
|
25
|
+
if [[ -n ${CLAUDE_EVOLVE_CONFIG:-} ]]; then
|
|
26
|
+
WORKER_CONFIG_PATH="$CLAUDE_EVOLVE_CONFIG"
|
|
27
|
+
elif [[ -n ${CONFIG_FILE:-} ]]; then
|
|
28
|
+
WORKER_CONFIG_PATH="$CONFIG_FILE"
|
|
29
|
+
elif [[ -f "config.yaml" ]]; then
|
|
30
|
+
WORKER_CONFIG_PATH="$(pwd)/config.yaml"
|
|
31
|
+
else
|
|
32
|
+
WORKER_CONFIG_PATH=""
|
|
26
33
|
fi
|
|
27
34
|
|
|
28
35
|
# Validate configuration
|
|
@@ -229,6 +236,7 @@ start_worker() {
|
|
|
229
236
|
|
|
230
237
|
local worker_args=()
|
|
231
238
|
[[ -n $timeout_seconds ]] && worker_args+=(--timeout "$timeout_seconds")
|
|
239
|
+
[[ -n $WORKER_CONFIG_PATH ]] && worker_args+=(--config "$WORKER_CONFIG_PATH")
|
|
232
240
|
|
|
233
241
|
echo "[DISPATCHER] Starting worker..."
|
|
234
242
|
"$worker_script" "${worker_args[@]}" &
|
|
@@ -252,6 +260,12 @@ cleanup_workers() {
|
|
|
252
260
|
if [[ $exit_code -eq 2 ]]; then
|
|
253
261
|
echo "[DISPATCHER] Worker $pid hit rate limit, will retry later"
|
|
254
262
|
# Rate limits don't count as consecutive failures
|
|
263
|
+
elif [[ $exit_code -eq 3 ]]; then
|
|
264
|
+
echo "[DISPATCHER] Worker $pid hit API usage limit - stopping all processing" >&2
|
|
265
|
+
echo "[DISPATCHER] Cannot continue evolution run due to API limits" >&2
|
|
266
|
+
echo "[DISPATCHER] Please wait for limits to reset before restarting" >&2
|
|
267
|
+
# Set a flag to stop the main loop
|
|
268
|
+
api_limit_reached=true
|
|
255
269
|
else
|
|
256
270
|
echo "[DISPATCHER] Worker $pid failed with exit code $exit_code"
|
|
257
271
|
# With retry mechanism, failures are normal - just keep processing
|
|
@@ -290,6 +304,16 @@ get_csv_stats() {
|
|
|
290
304
|
echo "[DISPATCHER] Starting unified evolution engine"
|
|
291
305
|
echo "[DISPATCHER] Configuration: max_workers=$MAX_WORKERS, timeout=${timeout_seconds:-none}"
|
|
292
306
|
|
|
307
|
+
# Clean up any stuck 'running' statuses at startup
|
|
308
|
+
if [[ -f "$FULL_CSV_PATH" ]]; then
|
|
309
|
+
echo "[DISPATCHER] Resetting any stuck 'running' candidates to 'pending'..."
|
|
310
|
+
if "$SCRIPT_DIR/claude-evolve-edit" running pending >/dev/null 2>&1; then
|
|
311
|
+
echo "[DISPATCHER] Successfully reset stuck candidates"
|
|
312
|
+
else
|
|
313
|
+
echo "[DISPATCHER] No stuck candidates found or edit command not available"
|
|
314
|
+
fi
|
|
315
|
+
fi
|
|
316
|
+
|
|
293
317
|
# Validate CSV and clean up stuck statuses and duplicates
|
|
294
318
|
if [[ -f "$FULL_CSV_PATH" ]]; then
|
|
295
319
|
echo "[DISPATCHER] Validating CSV and cleaning up..."
|
|
@@ -451,11 +475,20 @@ ensure_baseline_entry
|
|
|
451
475
|
# With retry mechanism, we don't need consecutive failure tracking
|
|
452
476
|
# Failures are handled gracefully through the retry system
|
|
453
477
|
|
|
478
|
+
# Flag to track API limit status
|
|
479
|
+
api_limit_reached=false
|
|
480
|
+
|
|
454
481
|
# Main dispatch loop
|
|
455
482
|
while true; do
|
|
456
483
|
# Clean up finished workers
|
|
457
484
|
cleanup_workers
|
|
458
485
|
|
|
486
|
+
# Check if API limit was reached
|
|
487
|
+
if [[ "$api_limit_reached" == "true" ]]; then
|
|
488
|
+
echo "[DISPATCHER] Stopping evolution run due to API usage limits" >&2
|
|
489
|
+
break
|
|
490
|
+
fi
|
|
491
|
+
|
|
459
492
|
# Get current status
|
|
460
493
|
csv_stats=$(get_csv_stats "$FULL_CSV_PATH")
|
|
461
494
|
read -r total_rows complete_count pending_count <<< "$csv_stats"
|
|
@@ -514,5 +547,14 @@ done
|
|
|
514
547
|
|
|
515
548
|
# Clean shutdown
|
|
516
549
|
shutdown_workers
|
|
517
|
-
|
|
518
|
-
|
|
550
|
+
|
|
551
|
+
# Final status message
|
|
552
|
+
if [[ "$api_limit_reached" == "true" ]]; then
|
|
553
|
+
echo "[DISPATCHER] Evolution run stopped due to API usage limits"
|
|
554
|
+
echo "[DISPATCHER] Wait for limits to reset, then run 'claude-evolve run' again"
|
|
555
|
+
echo "[DISPATCHER] Exiting with code 1 (API limits reached)"
|
|
556
|
+
exit 1
|
|
557
|
+
else
|
|
558
|
+
echo "[DISPATCHER] Evolution run complete"
|
|
559
|
+
echo "[DISPATCHER] Exiting with code 0"
|
|
560
|
+
fi
|
package/bin/claude-evolve-worker
CHANGED
|
@@ -6,21 +6,63 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &> /dev/null && pwd)"
|
|
|
6
6
|
source "$SCRIPT_DIR/../lib/config.sh"
|
|
7
7
|
source "$SCRIPT_DIR/../lib/csv-lock.sh"
|
|
8
8
|
|
|
9
|
-
#
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
# Track current candidate for cleanup
|
|
10
|
+
CURRENT_CANDIDATE_ID=""
|
|
11
|
+
TERMINATION_SIGNAL=""
|
|
12
|
+
|
|
13
|
+
# Cleanup function to handle termination
|
|
14
|
+
cleanup_on_exit() {
|
|
15
|
+
if [[ -n "$CURRENT_CANDIDATE_ID" ]]; then
|
|
16
|
+
# Only mark as failed if it was a timeout (SIGTERM from timeout command)
|
|
17
|
+
# For user interruption (Ctrl-C) or kill, leave it for retry
|
|
18
|
+
if [[ "$TERMINATION_SIGNAL" == "TERM" ]]; then
|
|
19
|
+
echo "[WORKER-$$] Timeout detected, marking $CURRENT_CANDIDATE_ID as failed" >&2
|
|
20
|
+
"$PYTHON_CMD" -c "
|
|
21
|
+
import sys
|
|
22
|
+
sys.path.insert(0, '$SCRIPT_DIR/..')
|
|
23
|
+
from lib.evolution_csv import EvolutionCSV
|
|
24
|
+
try:
|
|
25
|
+
with EvolutionCSV('$FULL_CSV_PATH') as csv:
|
|
26
|
+
csv.update_candidate_status('$CURRENT_CANDIDATE_ID', 'failed')
|
|
27
|
+
except:
|
|
28
|
+
pass # Best effort cleanup
|
|
29
|
+
" 2>/dev/null || true
|
|
30
|
+
else
|
|
31
|
+
echo "[WORKER-$$] Interrupted, leaving $CURRENT_CANDIDATE_ID for retry" >&2
|
|
32
|
+
# Optionally reset to pending instead of leaving as running
|
|
33
|
+
"$PYTHON_CMD" -c "
|
|
34
|
+
import sys
|
|
35
|
+
sys.path.insert(0, '$SCRIPT_DIR/..')
|
|
36
|
+
from lib.evolution_csv import EvolutionCSV
|
|
37
|
+
try:
|
|
38
|
+
with EvolutionCSV('$FULL_CSV_PATH') as csv:
|
|
39
|
+
csv.update_candidate_status('$CURRENT_CANDIDATE_ID', 'pending')
|
|
40
|
+
except:
|
|
41
|
+
pass # Best effort cleanup
|
|
42
|
+
" 2>/dev/null || true
|
|
43
|
+
fi
|
|
44
|
+
fi
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# Set up signal handlers
|
|
48
|
+
trap 'TERMINATION_SIGNAL="TERM"; cleanup_on_exit' TERM
|
|
49
|
+
trap 'TERMINATION_SIGNAL="INT"; cleanup_on_exit' INT
|
|
50
|
+
trap 'TERMINATION_SIGNAL="HUP"; cleanup_on_exit' HUP
|
|
51
|
+
trap 'cleanup_on_exit' EXIT
|
|
15
52
|
|
|
16
|
-
# Parse arguments
|
|
53
|
+
# Parse arguments first to get config path
|
|
17
54
|
timeout_seconds=""
|
|
55
|
+
config_path=""
|
|
18
56
|
while [[ $# -gt 0 ]]; do
|
|
19
57
|
case "$1" in
|
|
20
58
|
--timeout)
|
|
21
59
|
timeout_seconds="$2"
|
|
22
60
|
shift 2
|
|
23
61
|
;;
|
|
62
|
+
--config)
|
|
63
|
+
config_path="$2"
|
|
64
|
+
shift 2
|
|
65
|
+
;;
|
|
24
66
|
*)
|
|
25
67
|
echo "[ERROR] Unknown argument: $1" >&2
|
|
26
68
|
exit 1
|
|
@@ -28,6 +70,141 @@ while [[ $# -gt 0 ]]; do
|
|
|
28
70
|
esac
|
|
29
71
|
done
|
|
30
72
|
|
|
73
|
+
# Load config using the provided path, environment variable, or default
|
|
74
|
+
if [[ -n $config_path ]]; then
|
|
75
|
+
load_config "$config_path"
|
|
76
|
+
elif [[ -n ${CLAUDE_EVOLVE_CONFIG:-} ]]; then
|
|
77
|
+
load_config "$CLAUDE_EVOLVE_CONFIG"
|
|
78
|
+
else
|
|
79
|
+
load_config
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
# AI round-robin with fallback function for code evolution
|
|
83
|
+
call_ai_for_evolution() {
|
|
84
|
+
local prompt="$1"
|
|
85
|
+
local candidate_id="$2"
|
|
86
|
+
|
|
87
|
+
# Extract generation and ID numbers for round-robin calculation
|
|
88
|
+
local gen_num=0
|
|
89
|
+
local id_num=0
|
|
90
|
+
if [[ $candidate_id =~ ^gen([0-9]+)-([0-9]+)$ ]]; then
|
|
91
|
+
gen_num=$((10#${BASH_REMATCH[1]}))
|
|
92
|
+
id_num=$((10#${BASH_REMATCH[2]}))
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
# Calculate hash for round-robin (combine generation and ID)
|
|
96
|
+
local hash_value=$((gen_num * 1000 + id_num))
|
|
97
|
+
|
|
98
|
+
# Check which AI tools are available
|
|
99
|
+
local available_models=()
|
|
100
|
+
available_models+=("claude") # Claude Sonnet always available
|
|
101
|
+
if command -v gemini >/dev/null 2>&1; then
|
|
102
|
+
available_models+=("gemini")
|
|
103
|
+
fi
|
|
104
|
+
if command -v codex >/dev/null 2>&1; then
|
|
105
|
+
available_models+=("codex")
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
# Create ordered list based on round-robin for this candidate
|
|
109
|
+
local num_models=${#available_models[@]}
|
|
110
|
+
local start_index=$((hash_value % num_models))
|
|
111
|
+
local models=()
|
|
112
|
+
|
|
113
|
+
# Add models in round-robin order starting from the calculated index
|
|
114
|
+
for ((i=0; i<num_models; i++)); do
|
|
115
|
+
local idx=$(((start_index + i) % num_models))
|
|
116
|
+
models+=("${available_models[$idx]}")
|
|
117
|
+
done
|
|
118
|
+
|
|
119
|
+
echo "[WORKER-$$] Model order for $candidate_id (round-robin): ${models[*]}" >&2
|
|
120
|
+
|
|
121
|
+
# Try each model in the ordered sequence
|
|
122
|
+
for model in "${models[@]}"; do
|
|
123
|
+
echo "[WORKER-$$] Attempting code evolution with $model" >&2
|
|
124
|
+
local ai_output
|
|
125
|
+
local ai_exit_code
|
|
126
|
+
|
|
127
|
+
case "$model" in
|
|
128
|
+
"claude")
|
|
129
|
+
ai_output=$(echo "$prompt" | claude --dangerously-skip-permissions -p 2>&1)
|
|
130
|
+
ai_exit_code=$?
|
|
131
|
+
|
|
132
|
+
# Check for usage limits
|
|
133
|
+
if echo "$ai_output" | grep -q "Claude AI usage limit reached"; then
|
|
134
|
+
echo "[WORKER-$$] Claude AI usage limit reached - trying next model" >&2
|
|
135
|
+
continue
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
if [[ $ai_exit_code -eq 0 ]]; then
|
|
139
|
+
echo "[WORKER-$$] Claude Sonnet succeeded" >&2
|
|
140
|
+
return 0
|
|
141
|
+
fi
|
|
142
|
+
;;
|
|
143
|
+
|
|
144
|
+
"gemini")
|
|
145
|
+
ai_output=$(gemini -y -p "$prompt" 2>&1)
|
|
146
|
+
ai_exit_code=$?
|
|
147
|
+
|
|
148
|
+
# Check for authentication messages or valid response
|
|
149
|
+
if [[ $ai_exit_code -eq 0 ]]; then
|
|
150
|
+
if ! echo "$ai_output" | grep -q "Attempting to authenticate\|Authenticating\|Loading\|Initializing"; then
|
|
151
|
+
if [[ -n "$ai_output" ]] && [[ $(echo "$ai_output" | wc -l) -ge 2 ]]; then
|
|
152
|
+
echo "[WORKER-$$] Gemini succeeded" >&2
|
|
153
|
+
return 0
|
|
154
|
+
fi
|
|
155
|
+
fi
|
|
156
|
+
fi
|
|
157
|
+
;;
|
|
158
|
+
|
|
159
|
+
"codex")
|
|
160
|
+
ai_output=$(echo "$prompt" | codex exec --full-auto 2>&1)
|
|
161
|
+
ai_exit_code=$?
|
|
162
|
+
|
|
163
|
+
if [[ $ai_exit_code -eq 0 ]]; then
|
|
164
|
+
# Clean codex output if it's JSON
|
|
165
|
+
if echo "$ai_output" | grep -q '"content"'; then
|
|
166
|
+
ai_output=$(echo "$ai_output" | python3 -c "
|
|
167
|
+
import sys
|
|
168
|
+
import json
|
|
169
|
+
try:
|
|
170
|
+
data = json.load(sys.stdin)
|
|
171
|
+
if 'content' in data:
|
|
172
|
+
print(data['content'])
|
|
173
|
+
elif 'response' in data:
|
|
174
|
+
print(data['response'])
|
|
175
|
+
elif 'text' in data:
|
|
176
|
+
print(data['text'])
|
|
177
|
+
else:
|
|
178
|
+
print(json.dumps(data))
|
|
179
|
+
except:
|
|
180
|
+
print(sys.stdin.read())
|
|
181
|
+
" 2>/dev/null || echo "$ai_output")
|
|
182
|
+
fi
|
|
183
|
+
|
|
184
|
+
if [[ -n "$ai_output" ]] && ! echo "$ai_output" | grep -q "error\|failed\|exception"; then
|
|
185
|
+
echo "[WORKER-$$] Codex succeeded" >&2
|
|
186
|
+
return 0
|
|
187
|
+
fi
|
|
188
|
+
fi
|
|
189
|
+
;;
|
|
190
|
+
esac
|
|
191
|
+
|
|
192
|
+
echo "[WORKER-$$] $model failed (exit code $ai_exit_code), trying next model..." >&2
|
|
193
|
+
if [[ -n "$ai_output" ]]; then
|
|
194
|
+
echo "[WORKER-$$] $model error: $(echo "$ai_output" | head -5)" >&2
|
|
195
|
+
fi
|
|
196
|
+
done
|
|
197
|
+
|
|
198
|
+
# All models in round-robin failed, check for API limit exit
|
|
199
|
+
if echo "${ai_output:-}" | grep -q "Claude AI usage limit reached"; then
|
|
200
|
+
echo "[WORKER-$$] ERROR: All AI models unavailable - Claude hit usage limit" >&2
|
|
201
|
+
exit 3
|
|
202
|
+
fi
|
|
203
|
+
|
|
204
|
+
echo "[WORKER-$$] All AI models failed for code evolution" >&2
|
|
205
|
+
return 1
|
|
206
|
+
}
|
|
207
|
+
|
|
31
208
|
# Validate paths
|
|
32
209
|
if [[ ! -f "$FULL_CSV_PATH" ]]; then
|
|
33
210
|
echo "[WORKER-$$] CSV file not found: $FULL_CSV_PATH" >&2
|
|
@@ -58,11 +235,21 @@ process_candidate() {
|
|
|
58
235
|
fi
|
|
59
236
|
fi
|
|
60
237
|
|
|
61
|
-
#
|
|
238
|
+
# Check if this is a baseline candidate (no parent and specific ID pattern)
|
|
239
|
+
local is_baseline=false
|
|
240
|
+
if [[ -z "$parent_id" ]] && [[ "$candidate_id" =~ ^(baseline|baseline-000|000|0|gen00-000)$ ]]; then
|
|
241
|
+
is_baseline=true
|
|
242
|
+
echo "[WORKER-$$] Detected baseline candidate - will run algorithm.py directly"
|
|
243
|
+
fi
|
|
244
|
+
|
|
245
|
+
# Target file for evolution (not used for baseline)
|
|
62
246
|
local target_file="$FULL_OUTPUT_DIR/evolution_${candidate_id}.py"
|
|
63
247
|
|
|
64
248
|
# Check if processing should be skipped
|
|
65
|
-
if [[
|
|
249
|
+
if [[ "$is_baseline" == "true" ]]; then
|
|
250
|
+
# For baseline, skip all file operations
|
|
251
|
+
echo "[WORKER-$$] Baseline candidate - skipping file operations"
|
|
252
|
+
elif [[ -f "$target_file" ]]; then
|
|
66
253
|
echo "[WORKER-$$] � Skipping copy - File already exists - skipping all processing"
|
|
67
254
|
echo "[WORKER-$$] � Skipping Claude processing - File already exists - skipping all processing"
|
|
68
255
|
|
|
@@ -92,30 +279,50 @@ with EvolutionCSV('$FULL_CSV_PATH') as csv:
|
|
|
92
279
|
echo "[WORKER-$$] Copying $source_file to $target_file"
|
|
93
280
|
cp "$source_file" "$target_file"
|
|
94
281
|
|
|
95
|
-
# Apply evolution using
|
|
96
|
-
echo "[WORKER-$$] Applying evolution
|
|
97
|
-
|
|
282
|
+
# Apply evolution using AI
|
|
283
|
+
echo "[WORKER-$$] Applying evolution..."
|
|
284
|
+
|
|
285
|
+
# Use relative path for AI prompt
|
|
286
|
+
local target_basename=$(basename "$target_file")
|
|
287
|
+
local evolution_prompt="Modify the algorithm in $target_basename based on this description: $description
|
|
98
288
|
|
|
99
289
|
The modification should be substantial and follow the description exactly. Make sure the algorithm still follows all interface requirements and can run properly.
|
|
100
290
|
|
|
101
291
|
Important: Make meaningful changes that match the description. Don't just add comments or make trivial adjustments."
|
|
102
292
|
|
|
103
|
-
if
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
293
|
+
if [[ "$is_baseline" != "true" ]]; then
|
|
294
|
+
# Change to evolution directory so AI can access files
|
|
295
|
+
local original_pwd=$(pwd)
|
|
296
|
+
cd "$FULL_EVOLUTION_DIR"
|
|
297
|
+
|
|
298
|
+
# Try AI models with round-robin based on candidate ID
|
|
299
|
+
if ! call_ai_for_evolution "$evolution_prompt" "$candidate_id"; then
|
|
300
|
+
echo "[WORKER-$$] ERROR: All AI models failed to generate code" >&2
|
|
301
|
+
cd "$original_pwd"
|
|
302
|
+
rm -f "$target_file" # Clean up on failure
|
|
303
|
+
return 1
|
|
304
|
+
fi
|
|
305
|
+
|
|
306
|
+
# Restore working directory
|
|
307
|
+
cd "$original_pwd"
|
|
308
|
+
|
|
309
|
+
echo "[WORKER-$$] Evolution applied successfully"
|
|
107
310
|
fi
|
|
108
|
-
|
|
109
|
-
echo "[WORKER-$$] Evolution applied successfully"
|
|
110
311
|
fi
|
|
111
312
|
|
|
112
313
|
# Run evaluation
|
|
113
314
|
echo "[WORKER-$$] Evaluating algorithm..."
|
|
114
|
-
local eval_output_file="/
|
|
315
|
+
local eval_output_file="$FULL_EVOLUTION_DIR/temp-eval-$$-$candidate_id.out"
|
|
115
316
|
local eval_start=$(date +%s)
|
|
116
317
|
|
|
117
318
|
# Prepare evaluation command
|
|
118
|
-
|
|
319
|
+
# For baseline, pass "baseline" or empty string to evaluator to use algorithm.py
|
|
320
|
+
local eval_arg="$candidate_id"
|
|
321
|
+
if [[ "$is_baseline" == "true" ]]; then
|
|
322
|
+
# Evaluator should interpret this as "use algorithm.py directly"
|
|
323
|
+
eval_arg=""
|
|
324
|
+
fi
|
|
325
|
+
local eval_cmd=("$PYTHON_CMD" "$FULL_EVALUATOR_PATH" "$eval_arg")
|
|
119
326
|
[[ -n "$timeout_seconds" ]] && eval_cmd=(timeout "$timeout_seconds" "${eval_cmd[@]}")
|
|
120
327
|
|
|
121
328
|
# Run evaluation with tee to both display and capture output
|
|
@@ -228,17 +435,20 @@ with EvolutionCSV('$FULL_CSV_PATH') as csv:
|
|
|
228
435
|
else
|
|
229
436
|
echo "[WORKER-$$] ERROR: No score found in evaluation output" >&2
|
|
230
437
|
echo "[WORKER-$$] Output: $eval_output" >&2
|
|
231
|
-
rm -f "$eval_output_file"
|
|
438
|
+
# rm -f "$eval_output_file" # Keep for debugging
|
|
439
|
+
echo "[WORKER-$$] Evaluation output saved to: $eval_output_file" >&2
|
|
232
440
|
return 1
|
|
233
441
|
fi
|
|
234
442
|
|
|
235
|
-
# Clean up temp file
|
|
236
|
-
rm -f "$eval_output_file"
|
|
443
|
+
# Clean up temp file (comment out to keep for debugging)
|
|
444
|
+
# rm -f "$eval_output_file"
|
|
445
|
+
echo "[WORKER-$$] Evaluation output saved to: $eval_output_file" >&2
|
|
237
446
|
else
|
|
238
447
|
local exit_code=$?
|
|
239
448
|
# Read any output that was captured before failure
|
|
240
449
|
eval_output=$(<"$eval_output_file")
|
|
241
|
-
rm -f "$eval_output_file"
|
|
450
|
+
# rm -f "$eval_output_file" # Keep for debugging
|
|
451
|
+
echo "[WORKER-$$] Evaluation output saved to: $eval_output_file" >&2
|
|
242
452
|
|
|
243
453
|
echo "[WORKER-$$] ERROR: Evaluation failed with exit code $exit_code" >&2
|
|
244
454
|
echo "[WORKER-$$] Output: $eval_output" >&2
|
|
@@ -272,7 +482,7 @@ with EvolutionCSV('$FULL_CSV_PATH') as csv:
|
|
|
272
482
|
# Get full candidate info
|
|
273
483
|
candidate = csv.get_candidate_info(candidate_id)
|
|
274
484
|
if candidate:
|
|
275
|
-
print(f'{candidate[\"id\"]}|{candidate.get(\"
|
|
485
|
+
print(f'{candidate[\"id\"]}|{candidate.get(\"basedOnId\", \"\")}|{candidate[\"description\"]}')
|
|
276
486
|
")
|
|
277
487
|
|
|
278
488
|
if [[ -z "$candidate_info" ]]; then
|
|
@@ -283,12 +493,26 @@ with EvolutionCSV('$FULL_CSV_PATH') as csv:
|
|
|
283
493
|
# Parse candidate info
|
|
284
494
|
IFS='|' read -r candidate_id parent_id description <<< "$candidate_info"
|
|
285
495
|
|
|
496
|
+
# Set current candidate for cleanup
|
|
497
|
+
CURRENT_CANDIDATE_ID="$candidate_id"
|
|
498
|
+
|
|
286
499
|
# Process the candidate
|
|
287
500
|
if process_candidate "$candidate_id" "$parent_id" "$description"; then
|
|
288
501
|
echo "[WORKER-$$] Successfully processed $candidate_id"
|
|
289
502
|
else
|
|
290
503
|
echo "[WORKER-$$] Failed to process $candidate_id"
|
|
504
|
+
# Ensure status is set to failed (might already be done in process_candidate)
|
|
505
|
+
"$PYTHON_CMD" -c "
|
|
506
|
+
import sys
|
|
507
|
+
sys.path.insert(0, '$SCRIPT_DIR/..')
|
|
508
|
+
from lib.evolution_csv import EvolutionCSV
|
|
509
|
+
with EvolutionCSV('$FULL_CSV_PATH') as csv:
|
|
510
|
+
csv.update_candidate_status('$candidate_id', 'failed')
|
|
511
|
+
" 2>/dev/null || true
|
|
291
512
|
fi
|
|
513
|
+
|
|
514
|
+
# Clear current candidate
|
|
515
|
+
CURRENT_CANDIDATE_ID=""
|
|
292
516
|
done
|
|
293
517
|
|
|
294
518
|
echo "[WORKER-$$] No more pending candidates, worker exiting"
|
|
Binary file
|
|
Binary file
|
package/lib/evolution_csv.py
CHANGED
|
@@ -121,8 +121,9 @@ class EvolutionCSV:
|
|
|
121
121
|
# Check status field (5th column, index 4)
|
|
122
122
|
status = row[4].strip().lower() if row[4] else ''
|
|
123
123
|
|
|
124
|
-
#
|
|
125
|
-
|
|
124
|
+
# Only blank, missing, or "pending" mean pending
|
|
125
|
+
# "running" should NOT be considered pending to avoid duplicate processing
|
|
126
|
+
if not status or status == 'pending':
|
|
126
127
|
return True
|
|
127
128
|
|
|
128
129
|
# Check for retry statuses
|
|
@@ -321,6 +322,39 @@ class EvolutionCSV:
|
|
|
321
322
|
|
|
322
323
|
return None
|
|
323
324
|
|
|
325
|
+
def delete_candidate(self, candidate_id: str) -> bool:
|
|
326
|
+
"""Delete a candidate from the CSV file."""
|
|
327
|
+
rows = self._read_csv()
|
|
328
|
+
if not rows:
|
|
329
|
+
return False
|
|
330
|
+
|
|
331
|
+
# Check if we have a header row
|
|
332
|
+
has_header = rows and rows[0] and rows[0][0].lower() == 'id'
|
|
333
|
+
|
|
334
|
+
# Find and remove the candidate
|
|
335
|
+
deleted = False
|
|
336
|
+
new_rows = []
|
|
337
|
+
|
|
338
|
+
# Keep header if it exists
|
|
339
|
+
if has_header:
|
|
340
|
+
new_rows.append(rows[0])
|
|
341
|
+
start_idx = 1
|
|
342
|
+
else:
|
|
343
|
+
start_idx = 0
|
|
344
|
+
|
|
345
|
+
for i in range(start_idx, len(rows)):
|
|
346
|
+
row = rows[i]
|
|
347
|
+
if self.is_valid_candidate_row(row) and row[0].strip() == candidate_id:
|
|
348
|
+
deleted = True
|
|
349
|
+
# Skip this row (delete it)
|
|
350
|
+
continue
|
|
351
|
+
new_rows.append(row)
|
|
352
|
+
|
|
353
|
+
if deleted:
|
|
354
|
+
self._write_csv(new_rows)
|
|
355
|
+
|
|
356
|
+
return deleted
|
|
357
|
+
|
|
324
358
|
def has_pending_work(self) -> bool:
|
|
325
359
|
"""Check if there are any pending candidates. Used by dispatcher."""
|
|
326
360
|
return self.count_pending_candidates() > 0
|