claude-evolve 1.9.10 → 1.11.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-ideate +7 -1802
- package/bin/claude-evolve-main +0 -12
- package/bin/claude-evolve-run +6 -786
- package/bin/claude-evolve-worker +7 -774
- package/lib/__pycache__/ai_cli.cpython-311.pyc +0 -0
- package/lib/__pycache__/evolution_csv.cpython-311.pyc +0 -0
- package/lib/__pycache__/evolve_ideate.cpython-314.pyc +0 -0
- package/lib/__pycache__/evolve_run.cpython-311.pyc +0 -0
- package/lib/__pycache__/evolve_run.cpython-314.pyc +0 -0
- package/lib/__pycache__/evolve_worker.cpython-314.pyc +0 -0
- package/lib/__pycache__/llm_bandit.cpython-314.pyc +0 -0
- package/lib/__pycache__/log.cpython-311.pyc +0 -0
- package/lib/__pycache__/log.cpython-314.pyc +0 -0
- package/lib/__pycache__/meta_learning.cpython-314.pyc +0 -0
- package/lib/__pycache__/sandbox_wrapper.cpython-314.pyc +0 -0
- package/lib/evolve_ideate.py +83 -4
- package/lib/evolve_run.py +19 -0
- package/lib/evolve_worker.py +113 -28
- package/lib/llm_bandit.py +361 -0
- package/lib/meta_learning.py +414 -0
- package/lib/sandbox.sb +59 -0
- package/lib/sandbox_wrapper.py +306 -0
- package/package.json +1 -4
- package/templates/config.yaml +20 -5
- package/bin/claude-evolve-ideate-py +0 -15
- package/bin/claude-evolve-run-py +0 -15
- package/bin/claude-evolve-worker-py +0 -15
- package/lib/memory_limit_wrapper.py +0 -310
package/bin/claude-evolve-ideate
CHANGED
|
@@ -1,1806 +1,11 @@
|
|
|
1
|
-
#!/bin/
|
|
2
|
-
|
|
3
|
-
set -e
|
|
4
|
-
|
|
5
|
-
# Load configuration
|
|
6
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
7
|
-
# shellcheck source=../lib/config.sh
|
|
8
|
-
source "$SCRIPT_DIR/../lib/config.sh"
|
|
9
|
-
# shellcheck source=../lib/csv-lock.sh
|
|
10
|
-
source "$SCRIPT_DIR/../lib/csv-lock.sh"
|
|
11
|
-
# shellcheck source=../lib/ai-cli.sh
|
|
12
|
-
source "$SCRIPT_DIR/../lib/ai-cli.sh"
|
|
13
|
-
|
|
14
|
-
# Use CLAUDE_EVOLVE_CONFIG if set, otherwise default
|
|
15
|
-
if [[ -n ${CLAUDE_EVOLVE_CONFIG:-} ]]; then
|
|
16
|
-
load_config "$CLAUDE_EVOLVE_CONFIG"
|
|
17
|
-
else
|
|
18
|
-
# Check if config.yaml exists in current directory
|
|
19
|
-
if [[ -f "config.yaml" ]]; then
|
|
20
|
-
# Don't export to avoid collision with parallel runs
|
|
21
|
-
CONFIG_FILE="$(pwd)/config.yaml"
|
|
22
|
-
load_config "$CONFIG_FILE"
|
|
23
|
-
else
|
|
24
|
-
load_config
|
|
25
|
-
fi
|
|
26
|
-
fi
|
|
27
|
-
|
|
28
|
-
# Setup logging to file
|
|
29
|
-
if [[ -n "${FULL_EVOLUTION_DIR:-}" ]]; then
|
|
30
|
-
LOG_DIR="$FULL_EVOLUTION_DIR/logs"
|
|
31
|
-
mkdir -p "$LOG_DIR"
|
|
32
|
-
LOG_FILE="$LOG_DIR/ideate-$$-$(date +%Y%m%d-%H%M%S).log"
|
|
33
|
-
|
|
34
|
-
# Log to both terminal and file with timestamps
|
|
35
|
-
exec > >(while IFS= read -r line; do echo "$(date '+%Y-%m-%d %H:%M:%S'): $line"; done | tee -a "$LOG_FILE") 2>&1
|
|
36
|
-
echo "[IDEATE-$$] Logging to: $LOG_FILE"
|
|
37
|
-
fi
|
|
38
|
-
|
|
39
|
-
# AIDEV-NOTE: Directory restoration helper to prevent working directory corruption
|
|
40
|
-
# Critical for preventing the bug where Ctrl+C during ideation leaves the shell in wrong directory
|
|
41
|
-
# Helper to safely change directory with automatic restoration
|
|
42
|
-
safe_pushd() {
|
|
43
|
-
SAFE_PUSHD_ORIGINAL_PWD=$(pwd)
|
|
44
|
-
cd "$1" || return 1
|
|
45
|
-
# Set trap to restore directory on any exit, error, or interrupt
|
|
46
|
-
trap 'cd "$SAFE_PUSHD_ORIGINAL_PWD" 2>/dev/null || true' EXIT INT TERM ERR
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
safe_popd() {
|
|
50
|
-
if [[ -n "${SAFE_PUSHD_ORIGINAL_PWD:-}" ]]; then
|
|
51
|
-
cd "$SAFE_PUSHD_ORIGINAL_PWD" || true
|
|
52
|
-
# Clear the trap since we're explicitly returning
|
|
53
|
-
trap - EXIT INT TERM ERR
|
|
54
|
-
fi
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
# Helper function to call AI (legacy wrapper)
|
|
58
|
-
call_ai_with_limit_check() {
|
|
59
|
-
local prompt="$1"
|
|
60
|
-
|
|
61
|
-
# Use centralized AI library for ideation (random model selection)
|
|
62
|
-
local ai_output
|
|
63
|
-
ai_output=$(call_ai_random "$prompt" "ideate")
|
|
64
|
-
local ai_exit_code=$?
|
|
65
|
-
|
|
66
|
-
if [[ $ai_exit_code -eq 0 ]]; then
|
|
67
|
-
echo "[INFO] AI succeeded" >&2
|
|
68
|
-
return 0
|
|
69
|
-
else
|
|
70
|
-
return $ai_exit_code
|
|
71
|
-
fi
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
# Backward compatibility alias
|
|
76
|
-
call_claude_with_limit_check() {
|
|
77
|
-
call_ai_with_limit_check "$@"
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
# Robust AI calling with random model selection (no fallback)
|
|
81
|
-
# Returns 0 on success and echoes the successful model name to stdout
|
|
82
|
-
call_ai_for_ideation() {
|
|
83
|
-
local prompt="$1"
|
|
84
|
-
local generation="${2:-01}"
|
|
85
|
-
local expected_count="${3:-1}" # Number of ideas expected to be added
|
|
86
|
-
local temp_csv_file="${4:-temp-csv-$$.csv}" # Optional temp CSV filename
|
|
87
|
-
|
|
88
|
-
# Verify temp CSV exists
|
|
89
|
-
if [[ ! -f "$temp_csv_file" ]]; then
|
|
90
|
-
echo "[ERROR] Temp CSV file not found at start: $temp_csv_file" >&2
|
|
91
|
-
return 1
|
|
92
|
-
fi
|
|
93
|
-
|
|
94
|
-
# Get the current row count before any modifications (from the pre-populated file with stubs)
|
|
95
|
-
local original_csv_count
|
|
96
|
-
original_csv_count=$(grep -v '^[[:space:]]*$' "$temp_csv_file" | tail -n +2 | wc -l | tr -d '[:space:]')
|
|
97
|
-
|
|
98
|
-
echo "[DEBUG] Pre-populated temp CSV has $original_csv_count rows (includes stub rows with placeholders)" >&2
|
|
99
|
-
|
|
100
|
-
# Get models for ideation
|
|
101
|
-
local model_list
|
|
102
|
-
model_list=$(get_models_for_command "ideate")
|
|
103
|
-
local models=()
|
|
104
|
-
read -ra models <<< "$model_list"
|
|
105
|
-
|
|
106
|
-
if [[ ${#models[@]} -eq 0 ]]; then
|
|
107
|
-
echo "[ERROR] No models configured for ideation" >&2
|
|
108
|
-
return 1
|
|
109
|
-
fi
|
|
110
|
-
|
|
111
|
-
# Pick one random model (no fallback)
|
|
112
|
-
local num_models=${#models[@]}
|
|
113
|
-
local random_index=$((RANDOM % num_models))
|
|
114
|
-
local model="${models[$random_index]}"
|
|
115
|
-
|
|
116
|
-
echo "[AI] Selected $model for ideate (random from $num_models models)" >&2
|
|
117
|
-
|
|
118
|
-
# Call the model directly
|
|
119
|
-
local ai_output
|
|
120
|
-
ai_output=$(call_ai_model_configured "$model" "$prompt")
|
|
121
|
-
local ai_exit_code=$?
|
|
122
|
-
|
|
123
|
-
# Check if the file was modified correctly
|
|
124
|
-
if [[ ! -f "$temp_csv_file" ]]; then
|
|
125
|
-
echo "[ERROR] Temp CSV file not found after $model: $temp_csv_file" >&2
|
|
126
|
-
return 1
|
|
127
|
-
fi
|
|
128
|
-
|
|
129
|
-
local new_csv_count
|
|
130
|
-
new_csv_count=$(grep -v '^[[:space:]]*$' "$temp_csv_file" | tail -n +2 | wc -l | tr -d '[:space:]')
|
|
131
|
-
local added_count=$((new_csv_count - original_csv_count))
|
|
132
|
-
|
|
133
|
-
echo "[DEBUG] After $model: original=$original_csv_count, new=$new_csv_count, added=$added_count" >&2
|
|
134
|
-
|
|
135
|
-
# Check if row count is correct (should be same since we're editing stubs, not adding)
|
|
136
|
-
if [[ $new_csv_count -ne $original_csv_count ]]; then
|
|
137
|
-
if [[ $added_count -lt 0 ]]; then
|
|
138
|
-
echo "[ERROR] $model deleted rows ($added_count)" >&2
|
|
139
|
-
else
|
|
140
|
-
echo "[ERROR] $model added extra rows ($added_count) instead of editing stubs" >&2
|
|
141
|
-
fi
|
|
142
|
-
return 1
|
|
143
|
-
fi
|
|
144
|
-
|
|
145
|
-
# Count remaining placeholders - there should be none if AI did its job
|
|
146
|
-
local placeholder_count
|
|
147
|
-
placeholder_count=$(grep -c "PLACEHOLDER" "$temp_csv_file" 2>/dev/null) || true
|
|
148
|
-
placeholder_count=$(echo "$placeholder_count" | tr -d '[:space:]')
|
|
149
|
-
placeholder_count=${placeholder_count:-0}
|
|
150
|
-
|
|
151
|
-
if [[ $placeholder_count -ne 0 ]]; then
|
|
152
|
-
echo "[ERROR] $model left $placeholder_count placeholders unfilled" >&2
|
|
153
|
-
return 1
|
|
154
|
-
fi
|
|
155
|
-
|
|
156
|
-
echo "[INFO] CSV modified by $model: filled $expected_count placeholder rows ✓" >&2
|
|
157
|
-
|
|
158
|
-
# Post-process to ensure all description fields are quoted
|
|
159
|
-
local fixed_csv_file="${temp_csv_file}.fixed"
|
|
160
|
-
if "$PYTHON_CMD" "$SCRIPT_DIR/../lib/csv_fixer.py" "$temp_csv_file" "$fixed_csv_file"; then
|
|
161
|
-
mv "$fixed_csv_file" "$temp_csv_file"
|
|
162
|
-
echo "[INFO] CSV format validated and fixed if needed" >&2
|
|
163
|
-
else
|
|
164
|
-
echo "[WARN] CSV format validation failed, using original" >&2
|
|
165
|
-
fi
|
|
166
|
-
|
|
167
|
-
# Echo the successful model name for caller to capture
|
|
168
|
-
echo "$model"
|
|
169
|
-
return 0
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
# Parse arguments
|
|
173
|
-
use_strategies=true
|
|
174
|
-
|
|
175
|
-
while [[ $# -gt 0 ]]; do
|
|
176
|
-
case $1 in
|
|
177
|
-
--help)
|
|
178
|
-
cat <<EOF
|
|
179
|
-
claude-evolve ideate - Generate new algorithm ideas using evolutionary strategies
|
|
180
|
-
|
|
181
|
-
USAGE:
|
|
182
|
-
claude-evolve ideate [--legacy N]
|
|
183
|
-
|
|
184
|
-
OPTIONS:
|
|
185
|
-
--legacy N Use legacy mode with N ideas (ignores strategy config)
|
|
186
|
-
--help Show this help message
|
|
187
|
-
|
|
188
|
-
DESCRIPTION:
|
|
189
|
-
Generates algorithm ideas using multi-strategy evolutionary approach:
|
|
190
|
-
- Novel exploration: Pure creativity, global search
|
|
191
|
-
- Hill climbing: Parameter tuning of top performers
|
|
192
|
-
- Structural mutation: Algorithmic changes to top performers
|
|
193
|
-
- Crossover hybrid: Combine successful approaches
|
|
194
|
-
|
|
195
|
-
Strategy distribution is configured in evolution/config.yaml
|
|
196
|
-
EOF
|
|
197
|
-
exit 0
|
|
198
|
-
;;
|
|
199
|
-
--legacy)
|
|
200
|
-
use_strategies=false
|
|
201
|
-
shift
|
|
202
|
-
if [[ $1 =~ ^[0-9]+$ ]]; then
|
|
203
|
-
TOTAL_IDEAS=$1
|
|
204
|
-
shift
|
|
205
|
-
else
|
|
206
|
-
echo "[ERROR] --legacy requires a number" >&2
|
|
207
|
-
exit 1
|
|
208
|
-
fi
|
|
209
|
-
;;
|
|
210
|
-
*)
|
|
211
|
-
echo "[ERROR] Unknown option: $1" >&2
|
|
212
|
-
exit 1
|
|
213
|
-
;;
|
|
214
|
-
esac
|
|
215
|
-
done
|
|
216
|
-
|
|
217
|
-
# Check workspace using config
|
|
218
|
-
if [[ ! -d "$FULL_EVOLUTION_DIR" ]]; then
|
|
219
|
-
echo "[ERROR] Evolution workspace not found: $FULL_EVOLUTION_DIR. Run 'claude-evolve setup' first." >&2
|
|
220
|
-
exit 1
|
|
221
|
-
fi
|
|
222
|
-
|
|
223
|
-
# Ensure CSV exists
|
|
224
|
-
if [[ ! -f "$FULL_CSV_PATH" ]]; then
|
|
225
|
-
echo "id,basedOnId,description,performance,status,idea-LLM,run-LLM" >"$FULL_CSV_PATH"
|
|
226
|
-
fi
|
|
227
|
-
|
|
228
|
-
# Validate strategy configuration
|
|
229
|
-
if [[ $use_strategies == true ]]; then
|
|
230
|
-
total_check=$((${NOVEL_EXPLORATION:-0} + ${HILL_CLIMBING:-0} + ${STRUCTURAL_MUTATION:-0} + ${CROSSOVER_HYBRID:-0}))
|
|
231
|
-
if [[ $total_check -ne $TOTAL_IDEAS ]]; then
|
|
232
|
-
echo "[ERROR] Strategy counts don't sum to total_ideas ($total_check != $TOTAL_IDEAS)" >&2
|
|
233
|
-
echo "Check your evolution/config.yaml configuration" >&2
|
|
234
|
-
exit 1
|
|
235
|
-
fi
|
|
236
|
-
fi
|
|
237
|
-
|
|
238
|
-
# Get next generation number that doesn't have existing Python files
|
|
239
|
-
# AIDEV-NOTE: Returns zero-padded generation number (e.g., "02" not "2")
|
|
240
|
-
get_next_generation() {
|
|
241
|
-
# Start with generation 1 if no CSV exists
|
|
242
|
-
local start_gen=1
|
|
243
|
-
|
|
244
|
-
if [[ -f "$FULL_CSV_PATH" ]]; then
|
|
245
|
-
# Use Python for proper CSV parsing to find max generation
|
|
246
|
-
local max_gen
|
|
247
|
-
max_gen=$("$PYTHON_CMD" -c "
|
|
248
|
-
import csv
|
|
249
|
-
max_gen = 0
|
|
250
|
-
with open('$FULL_CSV_PATH', 'r') as f:
|
|
251
|
-
reader = csv.reader(f)
|
|
252
|
-
next(reader, None) # Skip header
|
|
253
|
-
for row in reader:
|
|
254
|
-
if row and len(row) > 0:
|
|
255
|
-
id_field = row[0].strip()
|
|
256
|
-
if id_field.startswith('gen') and '-' in id_field:
|
|
257
|
-
try:
|
|
258
|
-
gen_part = id_field.split('-')[0] # e.g., 'gen01'
|
|
259
|
-
gen_num = int(gen_part[3:]) # Extract number after 'gen'
|
|
260
|
-
max_gen = max(max_gen, gen_num)
|
|
261
|
-
except (ValueError, IndexError):
|
|
262
|
-
pass
|
|
263
|
-
print(max_gen)
|
|
264
|
-
")
|
|
265
|
-
# Start checking from the next generation after max
|
|
266
|
-
start_gen=$((max_gen + 1))
|
|
267
|
-
fi
|
|
268
|
-
|
|
269
|
-
# Keep incrementing until we find a generation with no Python files
|
|
270
|
-
local candidate_gen=$start_gen
|
|
271
|
-
while true; do
|
|
272
|
-
# Zero-pad to 2 digits for consistency (gen01, gen02, etc.)
|
|
273
|
-
local gen_formatted
|
|
274
|
-
gen_formatted=$(printf "%02d" "$((10#$candidate_gen))")
|
|
275
|
-
|
|
276
|
-
# Check if any Python files exist for this generation (check both padded and unpadded)
|
|
277
|
-
local py_files_exist=false
|
|
278
|
-
if ls "$FULL_OUTPUT_DIR"/evolution_gen${gen_formatted}-*.py >/dev/null 2>&1; then
|
|
279
|
-
py_files_exist=true
|
|
280
|
-
elif ls "$FULL_OUTPUT_DIR"/evolution_gen${candidate_gen}-*.py >/dev/null 2>&1; then
|
|
281
|
-
# Also check unpadded format for legacy compatibility
|
|
282
|
-
py_files_exist=true
|
|
283
|
-
fi
|
|
284
|
-
|
|
285
|
-
if [[ "$py_files_exist" == "false" ]]; then
|
|
286
|
-
# This generation is safe to use - return padded format
|
|
287
|
-
echo "$gen_formatted"
|
|
288
|
-
return
|
|
289
|
-
else
|
|
290
|
-
echo "[WARN] Generation $gen_formatted already has Python files, skipping to next generation" >&2
|
|
291
|
-
candidate_gen=$((candidate_gen + 1))
|
|
292
|
-
|
|
293
|
-
# Safety check to prevent infinite loop
|
|
294
|
-
if [[ $candidate_gen -gt 9999 ]]; then
|
|
295
|
-
echo "[ERROR] Could not find a safe generation number (checked up to 9999)" >&2
|
|
296
|
-
exit 1
|
|
297
|
-
fi
|
|
298
|
-
fi
|
|
299
|
-
done
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
# This function is no longer used with direct CSV modification approach
|
|
303
|
-
# Keeping for backward compatibility but it's not called anywhere
|
|
304
|
-
get_next_id_number() {
|
|
305
|
-
"$PYTHON_CMD" -c "
|
|
306
|
-
import csv
|
|
307
|
-
import re
|
|
308
|
-
max_id = 0
|
|
309
|
-
pattern = re.compile(r'^gen$CURRENT_GENERATION-(\d+)$')
|
|
310
|
-
with open('$FULL_CSV_PATH', 'r') as f:
|
|
311
|
-
reader = csv.reader(f)
|
|
312
|
-
next(reader, None) # Skip header
|
|
313
|
-
for row in reader:
|
|
314
|
-
if row and len(row) > 0:
|
|
315
|
-
match = pattern.match(row[0].strip())
|
|
316
|
-
if match:
|
|
317
|
-
max_id = max(max_id, int(match.group(1)))
|
|
318
|
-
print(max_id + 1)
|
|
319
|
-
"
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
# AIDEV-NOTE: This function had a critical race condition bug that caused wrong rows to be updated
|
|
323
|
-
# The bug occurred when parallel processes modified the main CSV between temp CSV creation and append.
|
|
324
|
-
# FIX: Now requires original_main_csv_lines parameter (6th arg) to track the exact line count at copy time.
|
|
325
|
-
# This ensures we always append the correct new rows from temp CSV, regardless of concurrent modifications.
|
|
326
|
-
# Without this fix, the system would update wrong IDs (e.g., claim to add gen81 but update gen80 instead).
|
|
327
|
-
#
|
|
328
|
-
# Validate that AI directly modified the CSV file
|
|
329
|
-
validate_direct_csv_modification() {
|
|
330
|
-
local temp_csv="$1"
|
|
331
|
-
local expected_count="$2"
|
|
332
|
-
local idea_type="$3"
|
|
333
|
-
local ai_model="${4:-}" # AI model that generated the ideas
|
|
334
|
-
local expected_ids="${5:-}" # Optional: comma or space separated list of expected IDs
|
|
335
|
-
local original_main_csv_lines="${6:-}" # CRITICAL: Line count of main CSV when temp CSV was created
|
|
336
|
-
|
|
337
|
-
# Check if the file was actually modified
|
|
338
|
-
if [[ ! -f "$temp_csv" ]]; then
|
|
339
|
-
echo "[ERROR] CSV file was not found after AI modification" >&2
|
|
340
|
-
return 1
|
|
341
|
-
fi
|
|
342
|
-
|
|
343
|
-
# Count data rows in the modified temp CSV
|
|
344
|
-
local new_count
|
|
345
|
-
new_count=$(grep -v '^[[:space:]]*$' "$temp_csv" | tail -n +2 | wc -l | tr -d '[:space:]')
|
|
346
|
-
|
|
347
|
-
# If original line count wasn't provided, fall back to current main CSV count (old behavior)
|
|
348
|
-
# This preserves backward compatibility but may have race conditions
|
|
349
|
-
if [[ -z "$original_main_csv_lines" ]]; then
|
|
350
|
-
echo "[WARN] No original line count provided - using current main CSV count (may cause race conditions)" >&2
|
|
351
|
-
original_main_csv_lines=$(wc -l < "$FULL_CSV_PATH" | tr -d '[:space:]')
|
|
352
|
-
fi
|
|
353
|
-
|
|
354
|
-
# Calculate how many data rows the temp CSV started with (before stubs were added)
|
|
355
|
-
# This should match the original main CSV line count (including header)
|
|
356
|
-
local original_data_rows=$((original_main_csv_lines - 1)) # Subtract header
|
|
357
|
-
|
|
358
|
-
# Calculate how many rows were actually added to temp CSV
|
|
359
|
-
local added_count=$((new_count - original_data_rows))
|
|
360
|
-
|
|
361
|
-
# Check if AI overwrote the file instead of appending
|
|
362
|
-
if [[ $new_count -lt $original_data_rows ]]; then
|
|
363
|
-
echo "[ERROR] AI overwrote the CSV file instead of appending ($new_count < $original_data_rows)" >&2
|
|
364
|
-
head -10 "$temp_csv" >&2
|
|
365
|
-
return 1
|
|
366
|
-
fi
|
|
367
|
-
|
|
368
|
-
# Check if no changes were made
|
|
369
|
-
if [[ $new_count -eq $original_data_rows ]]; then
|
|
370
|
-
echo "[ERROR] CSV file wasn't modified - same number of data rows ($new_count = $original_data_rows)" >&2
|
|
371
|
-
head -10 "$temp_csv" >&2
|
|
372
|
-
return 1
|
|
373
|
-
fi
|
|
374
|
-
if [[ $added_count -ne $expected_count ]]; then
|
|
375
|
-
echo "[ERROR] Expected to add $expected_count ideas but only added $added_count" >&2
|
|
376
|
-
echo "[ERROR] Ideation failed - rejecting partial results to prevent endless loops" >&2
|
|
377
|
-
rm -f "$temp_csv"
|
|
378
|
-
return 1
|
|
379
|
-
fi
|
|
380
|
-
|
|
381
|
-
# If expected IDs were provided, validate that the AI used exactly those IDs
|
|
382
|
-
if [[ -n "$expected_ids" ]]; then
|
|
383
|
-
# Get the IDs that were actually added (last N rows of temp CSV)
|
|
384
|
-
local actual_ids
|
|
385
|
-
actual_ids=$(tail -n $added_count "$temp_csv" | cut -d',' -f1 | tr -d '"' | tr '\n' ' ' | xargs)
|
|
386
|
-
|
|
387
|
-
# Normalize expected_ids (convert commas to spaces, trim)
|
|
388
|
-
local expected_ids_normalized
|
|
389
|
-
expected_ids_normalized=$(echo "$expected_ids" | tr ',' ' ' | xargs)
|
|
390
|
-
|
|
391
|
-
# Compare
|
|
392
|
-
if [[ "$actual_ids" != "$expected_ids_normalized" ]]; then
|
|
393
|
-
echo "[ERROR] AI used wrong IDs!" >&2
|
|
394
|
-
echo "[ERROR] Expected: $expected_ids_normalized" >&2
|
|
395
|
-
echo "[ERROR] Actually used: $actual_ids" >&2
|
|
396
|
-
echo "[ERROR] Rejecting this attempt - AI must use the exact IDs specified" >&2
|
|
397
|
-
rm -f "$temp_csv"
|
|
398
|
-
return 1
|
|
399
|
-
fi
|
|
400
|
-
|
|
401
|
-
echo "[INFO] ✓ AI correctly used the specified IDs: $actual_ids" >&2
|
|
402
|
-
fi
|
|
403
|
-
|
|
404
|
-
# Use proper locking to safely update the CSV
|
|
405
|
-
echo "[INFO] Acquiring CSV lock to apply changes..."
|
|
406
|
-
|
|
407
|
-
# Set the lockfile path
|
|
408
|
-
CSV_LOCKFILE="$FULL_EVOLUTION_DIR/.evolution.csv.lock"
|
|
409
|
-
|
|
410
|
-
if ! acquire_csv_lock; then
|
|
411
|
-
echo "[ERROR] Failed to acquire CSV lock for update" >&2
|
|
412
|
-
rm -f "$temp_csv"
|
|
413
|
-
return 1
|
|
414
|
-
fi
|
|
415
|
-
|
|
416
|
-
# CRITICAL FIX: Use the original line count (when temp CSV was created) to determine which lines to append
|
|
417
|
-
# This prevents race conditions where other processes modify the main CSV between temp CSV creation and append
|
|
418
|
-
# Append only the NEW lines from temp CSV (those added after the original content)
|
|
419
|
-
echo "[DEBUG] Appending last $added_count rows from temp CSV (from line $((original_main_csv_lines + 1)) onwards)" >&2
|
|
420
|
-
tail -n +$((original_main_csv_lines + 1)) "$temp_csv" >> "$FULL_CSV_PATH"
|
|
421
|
-
|
|
422
|
-
# Get the IDs that were actually added by reading them from temp CSV (not main CSV)
|
|
423
|
-
# This avoids race conditions where other processes add rows to main CSV
|
|
424
|
-
local new_ids
|
|
425
|
-
new_ids=$(tail -n $added_count "$temp_csv" | grep -v "^id," | cut -d',' -f1 | tr -d '"')
|
|
426
|
-
echo "[DEBUG] IDs being added: $new_ids" >&2
|
|
427
|
-
|
|
428
|
-
# Clean up temp file
|
|
429
|
-
rm -f "$temp_csv"
|
|
430
|
-
|
|
431
|
-
# Update idea-LLM field for newly added rows if model is known
|
|
432
|
-
if [[ -n "$ai_model" ]]; then
|
|
433
|
-
echo "[INFO] Recording that $ai_model generated the ideas" >&2
|
|
434
|
-
|
|
435
|
-
# Update each new row with the model that generated it
|
|
436
|
-
for id in $new_ids; do
|
|
437
|
-
if [[ -n "$id" && "$id" != "id" ]]; then
|
|
438
|
-
echo "[DEBUG] Updating field for $id" >&2
|
|
439
|
-
"$PYTHON_CMD" "$SCRIPT_DIR/../lib/evolution_csv.py" "$FULL_CSV_PATH" field "$id" "idea-LLM" "$ai_model" || echo "[WARN] Failed to update $id" >&2
|
|
440
|
-
fi
|
|
441
|
-
done
|
|
442
|
-
fi
|
|
443
|
-
|
|
444
|
-
# Release the lock
|
|
445
|
-
release_csv_lock
|
|
446
|
-
|
|
447
|
-
echo "[INFO] Successfully added $added_count $idea_type ideas to CSV"
|
|
448
|
-
return 0
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
# DEPRECATED: Old validation function for CSV output approach
|
|
452
|
-
validate_and_apply_csv_modification_old() {
|
|
453
|
-
local modified_csv="$1"
|
|
454
|
-
local temp_csv="$2"
|
|
455
|
-
local expected_count="$3"
|
|
456
|
-
local idea_type="$4"
|
|
457
|
-
|
|
458
|
-
# Check if the response looks like an error message (but not if it's just CSV data containing these words)
|
|
459
|
-
if echo "$modified_csv" | head -1 | grep -q "id,basedOnId,description,performance,status"; then
|
|
460
|
-
: # This looks like a CSV file, not an error message - continue
|
|
461
|
-
elif echo "$modified_csv" | grep -qi "error\|failed\|limit\|exceeded\|sorry\|cannot\|unable"; then
|
|
462
|
-
echo "[ERROR] AI failed to modify CSV and returned an error message:" >&2
|
|
463
|
-
echo "$modified_csv" | head -200 >&2
|
|
464
|
-
return 1
|
|
465
|
-
fi
|
|
466
|
-
|
|
467
|
-
# Check if response is too short to be a valid CSV
|
|
468
|
-
if [[ ${#modified_csv} -lt 50 ]]; then
|
|
469
|
-
echo "[ERROR] AI response is too short to be a valid CSV (${#modified_csv} chars):" >&2
|
|
470
|
-
echo "$modified_csv" >&2
|
|
471
|
-
return 1
|
|
472
|
-
fi
|
|
473
|
-
|
|
474
|
-
# Extract CSV from AI output (in case there's extra text before it)
|
|
475
|
-
local csv_start_line
|
|
476
|
-
csv_start_line=$(echo "$modified_csv" | grep -n "id,basedOnId,description,performance,status" | head -1 | cut -d: -f1)
|
|
477
|
-
|
|
478
|
-
if [[ -n "$csv_start_line" ]]; then
|
|
479
|
-
# Extract CSV starting from the header line
|
|
480
|
-
modified_csv=$(echo "$modified_csv" | tail -n +$csv_start_line)
|
|
481
|
-
elif ! echo "$modified_csv" | head -1 | grep -q "id,basedOnId,description,performance,status"; then
|
|
482
|
-
echo "[ERROR] AI failed to return a valid CSV file. Expected CSV with header, but got:" >&2
|
|
483
|
-
echo "$modified_csv" | head -c 500 >&2
|
|
484
|
-
echo "" >&2
|
|
485
|
-
return 1
|
|
486
|
-
fi
|
|
487
|
-
|
|
488
|
-
# Write the modified CSV to temp file
|
|
489
|
-
echo "$modified_csv" > "$temp_csv"
|
|
490
|
-
|
|
491
|
-
# Validate the modified CSV has more entries than original
|
|
492
|
-
local original_count
|
|
493
|
-
original_count=$(wc -l < "$FULL_CSV_PATH" | tr -d '[:space:]')
|
|
494
|
-
local new_count
|
|
495
|
-
new_count=$(wc -l < "$temp_csv" | tr -d '[:space:]')
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
if [[ $new_count -le $original_count ]]; then
|
|
499
|
-
echo "[ERROR] Modified CSV doesn't have more entries ($new_count <= $original_count)" >&2
|
|
500
|
-
head -10 "$temp_csv" >&2
|
|
501
|
-
return 1
|
|
502
|
-
fi
|
|
503
|
-
|
|
504
|
-
local added_count=$((new_count - original_count))
|
|
505
|
-
if [[ $added_count -ne $expected_count ]]; then
|
|
506
|
-
echo "[WARN] Expected to add $expected_count ideas but added $added_count" >&2
|
|
507
|
-
fi
|
|
508
|
-
|
|
509
|
-
# Use proper locking to safely update the CSV
|
|
510
|
-
echo "[INFO] Acquiring CSV lock to apply changes..."
|
|
511
|
-
|
|
512
|
-
# Set the lockfile path
|
|
513
|
-
CSV_LOCKFILE="$FULL_EVOLUTION_DIR/.evolution.csv.lock"
|
|
514
|
-
|
|
515
|
-
if ! acquire_csv_lock; then
|
|
516
|
-
echo "[ERROR] Failed to acquire CSV lock for update" >&2
|
|
517
|
-
rm -f "$temp_csv"
|
|
518
|
-
return 1
|
|
519
|
-
fi
|
|
520
|
-
|
|
521
|
-
# Get just the new entries (skip header and existing entries)
|
|
522
|
-
local original_line_count=$(wc -l < "$FULL_CSV_PATH" | tr -d '[:space:]')
|
|
523
|
-
|
|
524
|
-
# Append only the new lines from temp CSV to the main CSV
|
|
525
|
-
tail -n +$((original_line_count + 1)) "$temp_csv" >> "$FULL_CSV_PATH"
|
|
526
|
-
|
|
527
|
-
# Clean up temp file
|
|
528
|
-
rm -f "$temp_csv"
|
|
529
|
-
|
|
530
|
-
# Update idea-LLM field for newly added rows if model is known
|
|
531
|
-
if [[ -n "$ai_model" ]]; then
|
|
532
|
-
echo "[INFO] Recording that $ai_model generated the ideas" >&2
|
|
533
|
-
# Get the IDs of the newly added rows (skip header line and strip quotes)
|
|
534
|
-
local new_ids
|
|
535
|
-
new_ids=$(tail -n $added_count "$FULL_CSV_PATH" | grep -v "^id," | cut -d',' -f1 | tr -d '"')
|
|
536
|
-
|
|
537
|
-
# Update each new row with the model that generated it
|
|
538
|
-
for id in $new_ids; do
|
|
539
|
-
if [[ -n "$id" && "$id" != "id" ]]; then
|
|
540
|
-
"$PYTHON_CMD" "$SCRIPT_DIR/../lib/evolution_csv.py" "$FULL_CSV_PATH" field "$id" "idea-LLM" "$ai_model" || echo "[WARN] Failed to update $id" >&2
|
|
541
|
-
fi
|
|
542
|
-
done
|
|
543
|
-
fi
|
|
544
|
-
|
|
545
|
-
# Release the lock
|
|
546
|
-
release_csv_lock
|
|
547
|
-
|
|
548
|
-
echo "[INFO] Successfully added $added_count $idea_type ideas to CSV"
|
|
549
|
-
return 0
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
# DEPRECATED: Old two-step process function - kept for reference
|
|
553
|
-
process_ai_ideas_direct_old() {
|
|
554
|
-
local count="$1"
|
|
555
|
-
local idea_type="$2" # novel, hill-climbing, structural, crossover
|
|
556
|
-
local top_performers="${3:-}" # Optional, for non-novel ideas
|
|
557
|
-
local ai_output="$4" # The AI's response with ideas
|
|
558
|
-
|
|
559
|
-
# Create temporary CSV copy in evolution directory (so AI can access it)
|
|
560
|
-
local temp_csv="$FULL_EVOLUTION_DIR/temp-csv-$$.csv"
|
|
561
|
-
cp "$FULL_CSV_PATH" "$temp_csv"
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
# Build prompt for AI to directly modify the CSV
|
|
565
|
-
local csv_prompt="I need you to add exactly $count new $idea_type ideas to this CSV file.
|
|
566
|
-
|
|
567
|
-
Here are the $count $idea_type ideas to add:
|
|
568
|
-
$ai_output
|
|
569
|
-
|
|
570
|
-
Current CSV contents:
|
|
571
|
-
$(cat "$temp_csv")
|
|
572
|
-
|
|
573
|
-
Instructions:
|
|
574
|
-
1. Add exactly $count new rows to the CSV
|
|
575
|
-
2. Use the next available generation numbers (gen$CURRENT_GENERATION-XXX format)
|
|
576
|
-
3. For each idea, create a row with: id,parent_id,description,,pending
|
|
577
|
-
4. CRITICAL CSV FORMATTING RULES:
|
|
578
|
-
- ALWAYS wrap the description field in double quotes
|
|
579
|
-
- If the description contains quotes, escape them by doubling them (\" becomes \"\")
|
|
580
|
-
- Example: gen01-001,gen00-000,\"Implement adaptive RSI thresholds\",,pending
|
|
581
|
-
- BAD: gen01-001,gen00-000,Implement adaptive RSI thresholds,,pending
|
|
582
|
-
- NEVER omit quotes - unquoted descriptions cause CSV corruption
|
|
583
|
-
5. For novel ideas: leave parent_id empty
|
|
584
|
-
6. For other idea types: use appropriate parent IDs from these top performers:
|
|
585
|
-
$top_performers
|
|
586
|
-
|
|
587
|
-
IMPORTANT: Output the complete modified CSV file. Do not add any explanation or other text - just output the CSV."
|
|
588
|
-
|
|
589
|
-
echo "[INFO] Having AI directly modify CSV with $count $idea_type ideas..."
|
|
590
|
-
|
|
591
|
-
# Get AI to modify the CSV with fallbacks
|
|
592
|
-
local modified_csv
|
|
593
|
-
local stderr_file="$FULL_EVOLUTION_DIR/stderr-$$.txt"
|
|
594
|
-
if ! modified_csv=$(call_ai_for_ideation "$csv_prompt" "$CURRENT_GENERATION" 2>"$stderr_file"); then
|
|
595
|
-
echo "[ERROR] AI model failed to modify CSV" >&2
|
|
596
|
-
cat "$stderr_file" >&2
|
|
597
|
-
rm -f "$temp_csv" "$stderr_file"
|
|
598
|
-
return 1
|
|
599
|
-
fi
|
|
600
|
-
rm -f "$stderr_file"
|
|
601
|
-
|
|
602
|
-
# Check if the response looks like an error message
|
|
603
|
-
if echo "$modified_csv" | grep -qi "error\|failed\|limit\|exceeded\|sorry\|cannot\|unable"; then
|
|
604
|
-
echo "[ERROR] AI failed to modify CSV and returned an error message:" >&2
|
|
605
|
-
echo "$modified_csv" | head -200 >&2
|
|
606
|
-
rm -f "$temp_csv"
|
|
607
|
-
return 1
|
|
608
|
-
fi
|
|
609
|
-
|
|
610
|
-
# Check if response is too short to be a valid CSV
|
|
611
|
-
if [[ ${#modified_csv} -lt 50 ]]; then
|
|
612
|
-
echo "[ERROR] AI response is too short to be a valid CSV (${#modified_csv} chars):" >&2
|
|
613
|
-
echo "$modified_csv" >&2
|
|
614
|
-
rm -f "$temp_csv"
|
|
615
|
-
return 1
|
|
616
|
-
fi
|
|
617
|
-
|
|
618
|
-
# Extract CSV from AI output (in case there's extra text before it)
|
|
619
|
-
local csv_start_line
|
|
620
|
-
csv_start_line=$(echo "$modified_csv" | grep -n "id,basedOnId,description,performance,status" | head -1 | cut -d: -f1)
|
|
621
|
-
|
|
622
|
-
if [[ -n "$csv_start_line" ]]; then
|
|
623
|
-
# Extract CSV starting from the header line
|
|
624
|
-
modified_csv=$(echo "$modified_csv" | tail -n +$csv_start_line)
|
|
625
|
-
elif ! echo "$modified_csv" | head -1 | grep -q "id,basedOnId,description,performance,status"; then
|
|
626
|
-
echo "[ERROR] AI failed to return a valid CSV file. Expected CSV with header, but got:" >&2
|
|
627
|
-
echo "$modified_csv" | head -c 500 >&2
|
|
628
|
-
echo "" >&2
|
|
629
|
-
rm -f "$temp_csv"
|
|
630
|
-
return 1
|
|
631
|
-
fi
|
|
632
|
-
|
|
633
|
-
# Write the modified CSV to temp file
|
|
634
|
-
echo "$modified_csv" > "$temp_csv"
|
|
635
|
-
|
|
636
|
-
# Debug: Show the AI's CSV modification attempt
|
|
637
|
-
echo "$modified_csv" | head -c 300 >&2
|
|
638
|
-
echo "" >&2
|
|
639
|
-
echo "$modified_csv" | tail -c 300 >&2
|
|
640
|
-
echo "" >&2
|
|
641
|
-
|
|
642
|
-
# Validate the modified CSV has more entries than original
|
|
643
|
-
local original_count
|
|
644
|
-
original_count=$(wc -l < "$FULL_CSV_PATH" | tr -d '[:space:]')
|
|
645
|
-
local new_count
|
|
646
|
-
new_count=$(wc -l < "$temp_csv" | tr -d '[:space:]')
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
if [[ $new_count -le $original_count ]]; then
|
|
650
|
-
echo "[ERROR] Modified CSV doesn't have more entries ($new_count <= $original_count)" >&2
|
|
651
|
-
cat "$temp_csv" | head -10 >&2
|
|
652
|
-
cat "$FULL_CSV_PATH" | head -10 >&2
|
|
653
|
-
rm -f "$temp_csv"
|
|
654
|
-
return 1
|
|
655
|
-
fi
|
|
656
|
-
|
|
657
|
-
local added_count=$((new_count - original_count))
|
|
658
|
-
if [[ $added_count -ne $count ]]; then
|
|
659
|
-
echo "[WARN] Expected to add $count ideas but added $added_count" >&2
|
|
660
|
-
fi
|
|
661
|
-
|
|
662
|
-
# Use proper locking to safely update the CSV
|
|
663
|
-
echo "[INFO] Acquiring CSV lock to apply changes..."
|
|
664
|
-
|
|
665
|
-
# Set the lockfile path
|
|
666
|
-
CSV_LOCKFILE="$FULL_EVOLUTION_DIR/.evolution.csv.lock"
|
|
667
|
-
|
|
668
|
-
if ! acquire_csv_lock; then
|
|
669
|
-
echo "[ERROR] Failed to acquire CSV lock for update" >&2
|
|
670
|
-
rm -f "$temp_csv"
|
|
671
|
-
return 1
|
|
672
|
-
fi
|
|
673
|
-
|
|
674
|
-
# Get just the new entries (skip header and existing entries)
|
|
675
|
-
local original_line_count=$(wc -l < "$FULL_CSV_PATH" | tr -d '[:space:]')
|
|
676
|
-
|
|
677
|
-
# Append only the new lines from temp CSV to the main CSV
|
|
678
|
-
tail -n +$((original_line_count + 1)) "$temp_csv" >> "$FULL_CSV_PATH"
|
|
679
|
-
|
|
680
|
-
# Clean up temp file
|
|
681
|
-
rm -f "$temp_csv"
|
|
682
|
-
|
|
683
|
-
# Release the lock
|
|
684
|
-
release_csv_lock
|
|
685
|
-
|
|
686
|
-
echo "[INFO] Successfully added $added_count $idea_type ideas to CSV"
|
|
687
|
-
|
|
688
|
-
return 0
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
# Get list of existing Python files for a generation
|
|
692
|
-
get_existing_py_files_for_generation() {
|
|
693
|
-
local generation="$1"
|
|
694
|
-
local py_files=""
|
|
695
|
-
|
|
696
|
-
# List all Python files for this generation
|
|
697
|
-
for py_file in "$FULL_OUTPUT_DIR"/evolution_gen${generation}-*.py; do
|
|
698
|
-
if [[ -f "$py_file" ]]; then
|
|
699
|
-
local basename=$(basename "$py_file" .py)
|
|
700
|
-
local id="${basename#evolution_}"
|
|
701
|
-
if [[ -n "$py_files" ]]; then
|
|
702
|
-
py_files="$py_files, $id"
|
|
703
|
-
else
|
|
704
|
-
py_files="$id"
|
|
705
|
-
fi
|
|
706
|
-
fi
|
|
707
|
-
done
|
|
708
|
-
|
|
709
|
-
echo "$py_files"
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
# Add existing Python files warning to prompt
|
|
713
|
-
add_existing_files_warning() {
|
|
714
|
-
local prompt="$1"
|
|
715
|
-
local generation="$2"
|
|
716
|
-
local existing_py_files=$(get_existing_py_files_for_generation "$generation")
|
|
717
|
-
|
|
718
|
-
if [[ -n "$existing_py_files" ]]; then
|
|
719
|
-
prompt+="
|
|
720
|
-
|
|
721
|
-
WARNING: The following IDs already have Python files and MUST NOT be reused: $existing_py_files
|
|
722
|
-
Skip these IDs when assigning new IDs (e.g., if gen$generation-001 and gen$generation-002 exist as Python files, start with gen$generation-003)"
|
|
723
|
-
fi
|
|
724
|
-
|
|
725
|
-
echo "$prompt"
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
# Get next available ID for current generation
|
|
729
|
-
get_next_id() {
|
|
730
|
-
local generation="$1"
|
|
731
|
-
if [[ ! -f "$FULL_CSV_PATH" ]]; then
|
|
732
|
-
echo "gen${generation}-001"
|
|
733
|
-
return
|
|
734
|
-
fi
|
|
735
|
-
|
|
736
|
-
# Use Python for proper CSV parsing
|
|
737
|
-
local max_id
|
|
738
|
-
max_id=$("$PYTHON_CMD" -c "
|
|
739
|
-
import csv
|
|
740
|
-
import re
|
|
741
|
-
max_id = 0
|
|
742
|
-
pattern = re.compile(r'^gen${generation}-(\d+)$')
|
|
743
|
-
with open('$FULL_CSV_PATH', 'r') as f:
|
|
744
|
-
reader = csv.reader(f)
|
|
745
|
-
next(reader, None) # Skip header
|
|
746
|
-
for row in reader:
|
|
747
|
-
if row and len(row) > 0:
|
|
748
|
-
id_field = row[0].strip()
|
|
749
|
-
match = pattern.match(id_field)
|
|
750
|
-
if match:
|
|
751
|
-
id_num = int(match.group(1))
|
|
752
|
-
max_id = max(max_id, id_num)
|
|
753
|
-
print(max_id)
|
|
754
|
-
")
|
|
755
|
-
|
|
756
|
-
# Format next ID with generation and 3-digit number
|
|
757
|
-
printf "gen%s-%03d" "$generation" $((max_id + 1))
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
# Get the next N available IDs for current generation as a comma-separated list
|
|
761
|
-
get_next_ids() {
|
|
762
|
-
local generation="$1"
|
|
763
|
-
local count="$2"
|
|
764
|
-
|
|
765
|
-
# Get the starting ID number
|
|
766
|
-
local start_id
|
|
767
|
-
if [[ ! -f "$FULL_CSV_PATH" ]]; then
|
|
768
|
-
start_id=1
|
|
769
|
-
else
|
|
770
|
-
# Use Python for proper CSV parsing
|
|
771
|
-
start_id=$("$PYTHON_CMD" -c "
|
|
772
|
-
import csv
|
|
773
|
-
import re
|
|
774
|
-
max_id = 0
|
|
775
|
-
pattern = re.compile(r'^gen${generation}-(\d+)$')
|
|
776
|
-
with open('$FULL_CSV_PATH', 'r') as f:
|
|
777
|
-
reader = csv.reader(f)
|
|
778
|
-
next(reader, None) # Skip header
|
|
779
|
-
for row in reader:
|
|
780
|
-
if row and len(row) > 0:
|
|
781
|
-
id_field = row[0].strip()
|
|
782
|
-
match = pattern.match(id_field)
|
|
783
|
-
if match:
|
|
784
|
-
id_num = int(match.group(1))
|
|
785
|
-
max_id = max(max_id, id_num)
|
|
786
|
-
print(max_id + 1)
|
|
787
|
-
")
|
|
788
|
-
fi
|
|
789
|
-
|
|
790
|
-
# Generate the list of IDs
|
|
791
|
-
local ids=()
|
|
792
|
-
for ((i=0; i<count; i++)); do
|
|
793
|
-
local id_num=$((start_id + i))
|
|
794
|
-
ids+=("$(printf "gen%s-%03d" "$generation" "$id_num")")
|
|
795
|
-
done
|
|
796
|
-
|
|
797
|
-
# Join with commas
|
|
798
|
-
local IFS=','
|
|
799
|
-
echo "${ids[*]}"
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
# Get top performers for parent selection (absolute + top novel candidates)
|
|
804
|
-
get_top_performers() {
|
|
805
|
-
local num_requested="$1"
|
|
806
|
-
if [[ ! -f "$FULL_CSV_PATH" ]]; then
|
|
807
|
-
echo ""
|
|
808
|
-
return
|
|
809
|
-
fi
|
|
810
|
-
|
|
811
|
-
# Use Python to properly parse CSV and find top performers + top novel candidates
|
|
812
|
-
"$PYTHON_CMD" -c "
|
|
813
|
-
import csv
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Ideation for claude-evolve - generates new algorithm candidates."""
|
|
814
3
|
import sys
|
|
4
|
+
import os
|
|
815
5
|
|
|
816
|
-
|
|
817
|
-
reader = csv.reader(f)
|
|
818
|
-
next(reader) # Skip header
|
|
819
|
-
|
|
820
|
-
completed = []
|
|
821
|
-
novel = []
|
|
822
|
-
|
|
823
|
-
# Collect all completed candidates
|
|
824
|
-
for row in reader:
|
|
825
|
-
if len(row) >= 5 and row[3] and row[4] == 'complete':
|
|
826
|
-
try:
|
|
827
|
-
candidate_id = row[0]
|
|
828
|
-
parent_id = row[1] if len(row) > 1 else ''
|
|
829
|
-
description = row[2] if len(row) > 2 else ''
|
|
830
|
-
score = float(row[3])
|
|
831
|
-
|
|
832
|
-
completed.append((candidate_id, description, score))
|
|
833
|
-
|
|
834
|
-
# Track novel candidates separately
|
|
835
|
-
if not parent_id:
|
|
836
|
-
novel.append((candidate_id, description, score))
|
|
837
|
-
|
|
838
|
-
except ValueError:
|
|
839
|
-
pass
|
|
840
|
-
|
|
841
|
-
# Sort absolute leaders by score (descending)
|
|
842
|
-
completed.sort(key=lambda x: x[2], reverse=True)
|
|
843
|
-
|
|
844
|
-
# Sort novel candidates by score (descending)
|
|
845
|
-
novel.sort(key=lambda x: x[2], reverse=True)
|
|
846
|
-
|
|
847
|
-
# Collect top performers
|
|
848
|
-
selected_ids = set()
|
|
849
|
-
results = []
|
|
850
|
-
|
|
851
|
-
# Add top absolute performers
|
|
852
|
-
for i, (candidate_id, description, score) in enumerate(completed[:$num_requested]):
|
|
853
|
-
results.append(f'{candidate_id},{description},{score}')
|
|
854
|
-
selected_ids.add(candidate_id)
|
|
855
|
-
|
|
856
|
-
# Add top novel candidates (if not already selected)
|
|
857
|
-
novel_count = 0
|
|
858
|
-
for candidate_id, description, score in novel:
|
|
859
|
-
if candidate_id not in selected_ids and novel_count < $NUM_REVOLUTION:
|
|
860
|
-
results.append(f'{candidate_id},{description},{score}')
|
|
861
|
-
selected_ids.add(candidate_id)
|
|
862
|
-
novel_count += 1
|
|
863
|
-
|
|
864
|
-
# Output all selected candidates
|
|
865
|
-
for result in results:
|
|
866
|
-
print(result)
|
|
867
|
-
"
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
# Generate ideas using AI with multi-strategy approach
|
|
873
|
-
ideate_ai_strategies() {
|
|
874
|
-
if [[ ! -f "$FULL_BRIEF_PATH" ]]; then
|
|
875
|
-
echo "[ERROR] $BRIEF_FILE not found. Run 'claude-evolve setup' first." >&2
|
|
876
|
-
exit 1
|
|
877
|
-
fi
|
|
878
|
-
|
|
879
|
-
# Baseline should already be evaluated by run command
|
|
880
|
-
|
|
881
|
-
# Get top performers (now includes top novel candidates)
|
|
882
|
-
local top_performers
|
|
883
|
-
top_performers=$(get_top_performers "$NUM_ELITES")
|
|
884
|
-
|
|
885
|
-
if [[ -z $top_performers ]]; then
|
|
886
|
-
echo "[INFO] No completed algorithms found, will use baseline algorithm for hill climbing"
|
|
887
|
-
# For hill climbing and mutations, use the baseline algorithm
|
|
888
|
-
# Use a special ID that validation script will recognize
|
|
889
|
-
top_performers="000,Baseline Algorithm (algorithm.py),0.0"
|
|
890
|
-
fi
|
|
891
|
-
|
|
892
|
-
echo "[INFO] Generating $TOTAL_IDEAS ideas using multi-strategy approach:"
|
|
893
|
-
echo " Novel exploration: $NOVEL_EXPLORATION"
|
|
894
|
-
echo " Hill climbing: $HILL_CLIMBING"
|
|
895
|
-
echo " Structural mutation: $STRUCTURAL_MUTATION"
|
|
896
|
-
echo " Crossover hybrid: $CROSSOVER_HYBRID"
|
|
897
|
-
|
|
898
|
-
# Generate each type of idea by having Claude directly edit the CSV
|
|
899
|
-
# Track successes - continue even if some strategies fail
|
|
900
|
-
local strategies_attempted=0
|
|
901
|
-
local strategies_succeeded=0
|
|
902
|
-
|
|
903
|
-
# Helper to check if we've reached the target for this generation
|
|
904
|
-
check_generation_complete() {
|
|
905
|
-
local current_count
|
|
906
|
-
current_count=$(grep -c "^gen${CURRENT_GENERATION}-" "$FULL_CSV_PATH" 2>/dev/null) || true
|
|
907
|
-
current_count=${current_count:-0}
|
|
908
|
-
if [[ $current_count -ge $TOTAL_IDEAS ]]; then
|
|
909
|
-
echo "[INFO] Generation $CURRENT_GENERATION reached target ($current_count >= $TOTAL_IDEAS), stopping early" >&2
|
|
910
|
-
return 0
|
|
911
|
-
fi
|
|
912
|
-
return 1
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
if [[ ${NOVEL_EXPLORATION:-0} -gt 0 ]]; then
|
|
916
|
-
((strategies_attempted++))
|
|
917
|
-
if generate_novel_ideas_direct "$NOVEL_EXPLORATION"; then
|
|
918
|
-
((strategies_succeeded++))
|
|
919
|
-
check_generation_complete && return 0
|
|
920
|
-
else
|
|
921
|
-
echo "[WARN] Novel exploration strategy failed, continuing with other strategies" >&2
|
|
922
|
-
fi
|
|
923
|
-
fi
|
|
924
|
-
|
|
925
|
-
if [[ ${HILL_CLIMBING:-0} -gt 0 ]]; then
|
|
926
|
-
((strategies_attempted++))
|
|
927
|
-
if generate_hill_climbing_direct "$HILL_CLIMBING" "$top_performers"; then
|
|
928
|
-
((strategies_succeeded++))
|
|
929
|
-
check_generation_complete && return 0
|
|
930
|
-
else
|
|
931
|
-
echo "[WARN] Hill climbing strategy failed, continuing with other strategies" >&2
|
|
932
|
-
fi
|
|
933
|
-
fi
|
|
934
|
-
|
|
935
|
-
if [[ ${STRUCTURAL_MUTATION:-0} -gt 0 ]]; then
|
|
936
|
-
((strategies_attempted++))
|
|
937
|
-
if generate_structural_mutation_direct "$STRUCTURAL_MUTATION" "$top_performers"; then
|
|
938
|
-
((strategies_succeeded++))
|
|
939
|
-
check_generation_complete && return 0
|
|
940
|
-
else
|
|
941
|
-
echo "[WARN] Structural mutation strategy failed, continuing with other strategies" >&2
|
|
942
|
-
fi
|
|
943
|
-
fi
|
|
944
|
-
|
|
945
|
-
if [[ ${CROSSOVER_HYBRID:-0} -gt 0 ]]; then
|
|
946
|
-
((strategies_attempted++))
|
|
947
|
-
if generate_crossover_direct "$CROSSOVER_HYBRID" "$top_performers"; then
|
|
948
|
-
((strategies_succeeded++))
|
|
949
|
-
check_generation_complete && return 0
|
|
950
|
-
else
|
|
951
|
-
echo "[WARN] Crossover strategy failed, continuing with other strategies" >&2
|
|
952
|
-
fi
|
|
953
|
-
fi
|
|
954
|
-
|
|
955
|
-
echo "[INFO] Strategy results: $strategies_succeeded/$strategies_attempted succeeded" >&2
|
|
956
|
-
|
|
957
|
-
# Accept partial results - if ANY strategy succeeded, that's enough
|
|
958
|
-
# The workers will process what we have, and next ideation run can add more
|
|
959
|
-
# AIDEV-NOTE: Previously required ALL strategies to succeed, which caused
|
|
960
|
-
# endless ideation loops because CSV writes weren't rolled back on "rejection"
|
|
961
|
-
if [[ ${strategies_succeeded:-0} -gt 0 ]]; then
|
|
962
|
-
if [[ $strategies_succeeded -lt $strategies_attempted ]]; then
|
|
963
|
-
echo "[INFO] Partial success: $strategies_succeeded/$strategies_attempted strategies completed" >&2
|
|
964
|
-
echo "[INFO] Workers will process existing ideas, next ideation can add more" >&2
|
|
965
|
-
fi
|
|
966
|
-
return 0
|
|
967
|
-
else
|
|
968
|
-
echo "[ERROR] All ideation strategies failed (0/$strategies_attempted)" >&2
|
|
969
|
-
return 1
|
|
970
|
-
fi
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
# Generate novel exploration ideas using direct CSV modification
|
|
974
|
-
generate_novel_ideas_direct() {
|
|
975
|
-
local count="$1"
|
|
976
|
-
|
|
977
|
-
# Get the next available ID BEFORE creating temp CSV
|
|
978
|
-
# This ensures each strategy gets unique IDs even in parallel runs
|
|
979
|
-
local next_id
|
|
980
|
-
next_id=$(get_next_id "$CURRENT_GENERATION")
|
|
981
|
-
echo "[INFO] Next available ID for novel ideas: $next_id" >&2
|
|
982
|
-
|
|
983
|
-
# Generate the list of IDs this strategy should use
|
|
984
|
-
local next_id_num
|
|
985
|
-
next_id_num=$(echo "$next_id" | grep -o '[0-9]*$')
|
|
986
|
-
# Strip leading zeros to avoid octal interpretation in arithmetic
|
|
987
|
-
next_id_num=$((10#$next_id_num))
|
|
988
|
-
local required_ids=()
|
|
989
|
-
for ((i=0; i<count; i++)); do
|
|
990
|
-
required_ids+=("$(printf "gen%s-%03d" "$CURRENT_GENERATION" $((next_id_num + i)))")
|
|
991
|
-
done
|
|
992
|
-
local required_ids_str="${required_ids[*]}"
|
|
993
|
-
|
|
994
|
-
# Create temporary CSV copy in evolution directory (so AI can access it)
|
|
995
|
-
local temp_csv="$FULL_EVOLUTION_DIR/temp-csv-$$.csv"
|
|
996
|
-
cp "$FULL_CSV_PATH" "$temp_csv"
|
|
997
|
-
|
|
998
|
-
# CRITICAL: Capture the original line count immediately after copying
|
|
999
|
-
# This is needed to correctly append rows later, preventing race conditions
|
|
1000
|
-
local original_csv_lines
|
|
1001
|
-
original_csv_lines=$(wc -l < "$temp_csv" | tr -d '[:space:]')
|
|
1002
|
-
echo "[DEBUG] Original CSV has $original_csv_lines lines (including header)" >&2
|
|
1003
|
-
|
|
1004
|
-
# Pre-populate the CSV with stub rows containing the correct IDs
|
|
1005
|
-
# This ensures the AI can't possibly use wrong IDs - it just fills in descriptions
|
|
1006
|
-
echo "[INFO] Pre-populating CSV with stub rows: $required_ids_str"
|
|
1007
|
-
for id in "${required_ids[@]}"; do
|
|
1008
|
-
echo "$id,,\"[PLACEHOLDER: Replace this with your algorithmic idea]\",,pending" >> "$temp_csv"
|
|
1009
|
-
done
|
|
1010
|
-
|
|
1011
|
-
echo "[INFO] Generating $count novel exploration ideas with IDs: $required_ids_str"
|
|
1012
|
-
|
|
1013
|
-
# Count total lines in temp CSV (including header)
|
|
1014
|
-
local total_lines
|
|
1015
|
-
total_lines=$(wc -l < "$temp_csv" | tr -d '[:space:]')
|
|
1016
|
-
local read_offset=$((total_lines - 25))
|
|
1017
|
-
if [[ $read_offset -lt 1 ]]; then
|
|
1018
|
-
read_offset=1
|
|
1019
|
-
fi
|
|
1020
|
-
|
|
1021
|
-
# Use relative paths and change to evolution directory so AI can access files
|
|
1022
|
-
local temp_csv_basename=$(basename "$temp_csv")
|
|
1023
|
-
|
|
1024
|
-
# Get existing Python files for this generation to avoid ID collisions
|
|
1025
|
-
local existing_py_files=$(get_existing_py_files_for_generation "$CURRENT_GENERATION")
|
|
1026
|
-
|
|
1027
|
-
local prompt="$(get_git_protection_warning)
|
|
1028
|
-
|
|
1029
|
-
I need you to use your file editing capabilities to fill in PLACEHOLDER descriptions in the CSV file: $temp_csv_basename
|
|
1030
|
-
|
|
1031
|
-
THE FILE HAS $total_lines TOTAL LINES. Read from line $read_offset to see the placeholder rows at the end.
|
|
1032
|
-
|
|
1033
|
-
Current evolution context:
|
|
1034
|
-
- Generation: $CURRENT_GENERATION
|
|
1035
|
-
- Algorithm: algorithm.py (base algorithm)
|
|
1036
|
-
- Brief: $(head -5 "$FULL_BRIEF_PATH" 2>/dev/null | head -c 500 || echo "No brief file found")
|
|
1037
|
-
|
|
1038
|
-
IMPORTANT: DO NOT read algorithm.py or any evolution_*.py files. Focus on creative ideation based on the brief and CSV context only. Reading code files wastes tokens and time.
|
|
1039
|
-
|
|
1040
|
-
CROSS-POLLINATION OPPORTUNITY:
|
|
1041
|
-
Consider looking at the last 20-30 ideas from other evolution files in sibling directories (../*/evolution.csv).
|
|
1042
|
-
These may contain different algorithmic approaches that could inspire novel combinations or variations.
|
|
1043
|
-
This is OPTIONAL - only do it if you think it would help generate more diverse ideas.
|
|
1044
|
-
|
|
1045
|
-
CRITICAL TASK:
|
|
1046
|
-
The CSV file already contains $count stub rows with these IDs: $required_ids_str
|
|
1047
|
-
Each stub row has a PLACEHOLDER description like: \"[PLACEHOLDER: Replace this with your algorithmic idea]\"
|
|
1048
|
-
Your job is to REPLACE each PLACEHOLDER with a real algorithmic idea description.
|
|
1049
|
-
|
|
1050
|
-
⚠️ CRITICAL FILE READING INSTRUCTIONS ⚠️
|
|
1051
|
-
THE CSV FILE IS VERY LARGE (OVER 100,000 TOKENS). YOU WILL RUN OUT OF CONTEXT IF YOU READ IT ALL!
|
|
1052
|
-
- DO NOT read the entire file or you will exceed context limits and CRASH
|
|
1053
|
-
- Use: Read(file_path='$temp_csv_basename', offset=$read_offset, limit=25)
|
|
1054
|
-
- This will read ONLY the last 25 lines where the placeholders are
|
|
1055
|
-
- DO NOT READ FROM OFFSET 0 - that will load the entire huge file and fail!
|
|
1056
|
-
|
|
1057
|
-
CRITICAL INSTRUCTIONS:
|
|
1058
|
-
1. Read ONLY the last 20-30 lines of the CSV to see the placeholder rows
|
|
1059
|
-
2. DO NOT ADD OR DELETE ANY ROWS - only EDIT the placeholder descriptions
|
|
1060
|
-
3. DO NOT CHANGE THE IDs - they are already correct ($required_ids_str)
|
|
1061
|
-
4. Use the Edit tool to replace EACH PLACEHOLDER text with a real algorithmic idea
|
|
1062
|
-
5. When editing, preserve the CSV structure: keep the ID and parent_id fields unchanged"
|
|
1063
|
-
|
|
1064
|
-
if [[ -n "$existing_py_files" ]]; then
|
|
1065
|
-
prompt+="
|
|
1066
|
-
6. IMPORTANT: The following IDs already have Python files: $existing_py_files
|
|
1067
|
-
(This is informational only - use the IDs specified above)"
|
|
1068
|
-
fi
|
|
1069
|
-
|
|
1070
|
-
prompt+="
|
|
1071
|
-
7. CRITICAL CSV FORMATTING RULES:
|
|
1072
|
-
- ALWAYS wrap the description field in double quotes
|
|
1073
|
-
- If the description contains quotes, escape them by doubling them (\" becomes \"\")
|
|
1074
|
-
- Example: gen01-001,,\"Implement adaptive RSI thresholds based on volatility\",,pending
|
|
1075
|
-
- BAD: gen01-001,,Implement adaptive RSI thresholds based on volatility,,pending
|
|
1076
|
-
- NEVER omit quotes around descriptions - this causes CSV parsing errors
|
|
1077
|
-
9. Each description should be one clear sentence describing a novel algorithmic approach
|
|
1078
|
-
10. Focus on creative, ambitious ideas that haven't been tried yet
|
|
1079
|
-
11. Consider machine learning, new indicators, regime detection, risk management, etc.
|
|
1080
|
-
|
|
1081
|
-
IMPORTANT: You must APPEND new rows to the existing CSV file. DO NOT replace the file contents. All existing rows must remain unchanged.
|
|
1082
|
-
CRITICAL: You must use your file editing tools (Edit/MultiEdit) to modify the CSV file. DO NOT return CSV text - use your tools to edit the file directly."
|
|
1083
|
-
|
|
1084
|
-
# Debug prompt size and context (removed - no longer needed)
|
|
1085
|
-
|
|
1086
|
-
# Change to evolution directory so AI can access files (with safe restoration on interrupt)
|
|
1087
|
-
safe_pushd "$FULL_EVOLUTION_DIR" || {
|
|
1088
|
-
echo "[ERROR] Failed to change to evolution directory" >&2
|
|
1089
|
-
rm -f "$temp_csv"
|
|
1090
|
-
return 1
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
# Debug: Show files in evolution directory
|
|
1094
|
-
|
|
1095
|
-
# Get AI to directly edit the CSV file
|
|
1096
|
-
local ai_response
|
|
1097
|
-
if ! ai_response=$(call_ai_for_ideation "$prompt" "$CURRENT_GENERATION" "$count" "$temp_csv_basename"); then
|
|
1098
|
-
echo "[ERROR] AI model failed to generate novel ideas" >&2
|
|
1099
|
-
safe_popd
|
|
1100
|
-
rm -f "$temp_csv"
|
|
1101
|
-
return 1
|
|
1102
|
-
fi
|
|
1103
|
-
|
|
1104
|
-
# Restore working directory
|
|
1105
|
-
safe_popd
|
|
1106
|
-
|
|
1107
|
-
# Validate that the CSV file was actually modified with correct IDs
|
|
1108
|
-
# Pass original_csv_lines to prevent race conditions
|
|
1109
|
-
if ! validate_direct_csv_modification "$temp_csv" "$count" "novel" "$ai_response" "$required_ids_str" "$original_csv_lines"; then
|
|
1110
|
-
rm -f "$temp_csv"
|
|
1111
|
-
return 1
|
|
1112
|
-
fi
|
|
1113
|
-
|
|
1114
|
-
echo "[INFO] Novel exploration ideas generated successfully"
|
|
1115
|
-
return 0
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
# Generate hill climbing ideas using direct CSV modification
|
|
1119
|
-
generate_hill_climbing_direct() {
|
|
1120
|
-
local count="$1"
|
|
1121
|
-
local top_performers="$2"
|
|
1122
|
-
|
|
1123
|
-
# Get the next available ID BEFORE creating temp CSV
|
|
1124
|
-
local next_id
|
|
1125
|
-
next_id=$(get_next_id "$CURRENT_GENERATION")
|
|
1126
|
-
echo "[INFO] Next available ID for hill climbing: $next_id" >&2
|
|
1127
|
-
|
|
1128
|
-
# Generate the list of IDs this strategy should use
|
|
1129
|
-
local next_id_num
|
|
1130
|
-
next_id_num=$(echo "$next_id" | grep -o '[0-9]*$')
|
|
1131
|
-
# Strip leading zeros to avoid octal interpretation in arithmetic
|
|
1132
|
-
next_id_num=$((10#$next_id_num))
|
|
1133
|
-
local required_ids=()
|
|
1134
|
-
for ((i=0; i<count; i++)); do
|
|
1135
|
-
required_ids+=("$(printf "gen%s-%03d" "$CURRENT_GENERATION" $((next_id_num + i)))")
|
|
1136
|
-
done
|
|
1137
|
-
local required_ids_str="${required_ids[*]}"
|
|
1138
|
-
|
|
1139
|
-
# Create temporary CSV copy in evolution directory (so AI can access it)
|
|
1140
|
-
local temp_csv="$FULL_EVOLUTION_DIR/temp-csv-$$.csv"
|
|
1141
|
-
cp "$FULL_CSV_PATH" "$temp_csv"
|
|
1142
|
-
|
|
1143
|
-
# CRITICAL: Capture the original line count immediately after copying
|
|
1144
|
-
local original_csv_lines
|
|
1145
|
-
original_csv_lines=$(wc -l < "$temp_csv" | tr -d '[:space:]')
|
|
1146
|
-
echo "[DEBUG] Original CSV has $original_csv_lines lines (including header)" >&2
|
|
1147
|
-
|
|
1148
|
-
# Extract just the IDs from top performers for clarity (needed before pre-populating)
|
|
1149
|
-
local valid_parent_ids
|
|
1150
|
-
valid_parent_ids=$(echo "$top_performers" | cut -d',' -f1 | paste -sd ',' -)
|
|
1151
|
-
|
|
1152
|
-
# Pre-populate the CSV with stub rows containing the correct IDs and parent IDs
|
|
1153
|
-
echo "[INFO] Pre-populating CSV with stub rows: $required_ids_str"
|
|
1154
|
-
# Use first parent as default for stubs (AI will adjust if needed)
|
|
1155
|
-
local first_parent_id
|
|
1156
|
-
first_parent_id=$(echo "$valid_parent_ids" | cut -d',' -f1)
|
|
1157
|
-
for id in "${required_ids[@]}"; do
|
|
1158
|
-
echo "$id,$first_parent_id,\"[PLACEHOLDER: Replace with parameter tuning idea]\",,pending" >> "$temp_csv"
|
|
1159
|
-
done
|
|
1160
|
-
|
|
1161
|
-
echo "[INFO] Generating $count hill climbing ideas with IDs: $required_ids_str"
|
|
1162
|
-
|
|
1163
|
-
# Count total lines in temp CSV (including header)
|
|
1164
|
-
local total_lines
|
|
1165
|
-
total_lines=$(wc -l < "$temp_csv" | tr -d '[:space:]')
|
|
1166
|
-
local read_offset=$((total_lines - 25))
|
|
1167
|
-
if [[ $read_offset -lt 1 ]]; then
|
|
1168
|
-
read_offset=1
|
|
1169
|
-
fi
|
|
1170
|
-
|
|
1171
|
-
# Get existing Python files for this generation to avoid ID collisions
|
|
1172
|
-
local existing_py_files=$(get_existing_py_files_for_generation "$CURRENT_GENERATION")
|
|
1173
|
-
|
|
1174
|
-
# Use relative paths and change to evolution directory so AI can access files
|
|
1175
|
-
local temp_csv_basename=$(basename "$temp_csv")
|
|
1176
|
-
|
|
1177
|
-
# Build prompt using cat with heredoc to avoid variable expansion issues
|
|
1178
|
-
local prompt
|
|
1179
|
-
prompt="$(get_git_protection_warning)
|
|
1180
|
-
|
|
1181
|
-
$(cat <<EOF
|
|
1182
|
-
I need you to use your file editing capabilities to fill in PLACEHOLDER descriptions in the CSV file: $temp_csv_basename
|
|
1183
|
-
|
|
1184
|
-
THE FILE HAS $total_lines TOTAL LINES. Read from line $read_offset to see the placeholder rows at the end.
|
|
1185
|
-
|
|
1186
|
-
IMPORTANT: You MUST use one of these exact parent IDs: $valid_parent_ids
|
|
1187
|
-
|
|
1188
|
-
Successful algorithms to tune:
|
|
1189
|
-
$top_performers
|
|
1190
|
-
|
|
1191
|
-
IMPORTANT: Generate parameter tuning ideas based primarily on the descriptions and scores above.
|
|
1192
|
-
EOF
|
|
1193
|
-
)"
|
|
1194
|
-
|
|
1195
|
-
prompt+="
|
|
1196
|
-
|
|
1197
|
-
CROSS-POLLINATION OPPORTUNITY:
|
|
1198
|
-
Consider looking at the last 20-30 ideas from other evolution files in sibling directories (../*/evolution.csv).
|
|
1199
|
-
These may contain interesting parameter tuning approaches or strategies that could be applied to your top performers.
|
|
1200
|
-
This is OPTIONAL - only do it if you think it would help generate more diverse tuning ideas.
|
|
1201
|
-
|
|
1202
|
-
ONLY read parent source files (evolution_<PARENT_ID>.py) if:
|
|
1203
|
-
- The description is too vague to identify specific parameters
|
|
1204
|
-
- You need to verify actual parameter names/values
|
|
1205
|
-
- Reading is absolutely necessary for meaningful tuning
|
|
1206
|
-
|
|
1207
|
-
If you must read source files:
|
|
1208
|
-
- Read in small chunks (offset/limit) to minimize token usage
|
|
1209
|
-
- Focus only on finding parameter definitions at the top of the file
|
|
1210
|
-
- Do NOT read the entire implementation
|
|
1211
|
-
|
|
1212
|
-
Most of the time, you can infer parameters from descriptions like 'RSI with threshold 30' or 'MA period 20'.
|
|
1213
|
-
|
|
1214
|
-
CRITICAL TASK:
|
|
1215
|
-
The CSV file already contains $count stub rows with these IDs: $required_ids_str
|
|
1216
|
-
Each stub row has a PLACEHOLDER description like: \"[PLACEHOLDER: Replace with parameter tuning idea]\"
|
|
1217
|
-
Your job is to REPLACE each PLACEHOLDER with a real parameter tuning idea.
|
|
1218
|
-
|
|
1219
|
-
⚠️ CRITICAL FILE READING INSTRUCTIONS ⚠️
|
|
1220
|
-
THE CSV FILE IS VERY LARGE (OVER 100,000 TOKENS). YOU WILL RUN OUT OF CONTEXT IF YOU READ IT ALL!
|
|
1221
|
-
- DO NOT read the entire file or you will exceed context limits and CRASH
|
|
1222
|
-
- Use: Read(file_path='$temp_csv_basename', offset=$read_offset, limit=25)
|
|
1223
|
-
- This will read ONLY the last 25 lines where the placeholders are
|
|
1224
|
-
- DO NOT READ FROM OFFSET 0 - that will load the entire huge file and fail!
|
|
1225
|
-
|
|
1226
|
-
CRITICAL INSTRUCTIONS:
|
|
1227
|
-
1. Read ONLY the last 25 lines using the offset specified above
|
|
1228
|
-
2. DO NOT ADD OR DELETE ANY ROWS - only EDIT the placeholder descriptions
|
|
1229
|
-
3. DO NOT CHANGE THE IDs - they are already correct ($required_ids_str)
|
|
1230
|
-
4. Use the Edit tool to replace EACH PLACEHOLDER text with a parameter tuning idea
|
|
1231
|
-
5. When editing, preserve the CSV structure: keep the ID field unchanged
|
|
1232
|
-
6. You may change the parent_id field if needed to reference a different top performer
|
|
1233
|
-
7. Each description should focus on adjusting specific parameters - include current and new values
|
|
1234
|
-
Example: \"Lower rsi_entry from 21 to 18\" or \"Increase MA period from 20 to 50\"
|
|
1235
|
-
8. CRITICAL: When editing, preserve the CSV formatting with proper quoting"
|
|
1236
|
-
|
|
1237
|
-
# Change to evolution directory so AI can access files (with safe restoration on interrupt)
|
|
1238
|
-
safe_pushd "$FULL_EVOLUTION_DIR" || {
|
|
1239
|
-
echo "[ERROR] Failed to change to evolution directory" >&2
|
|
1240
|
-
rm -f "$temp_csv"
|
|
1241
|
-
return 1
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1244
|
-
# Get AI to directly edit the CSV file
|
|
1245
|
-
local ai_response
|
|
1246
|
-
if ! ai_response=$(call_ai_for_ideation "$prompt" "$CURRENT_GENERATION" "$count" "$temp_csv_basename"); then
|
|
1247
|
-
echo "[ERROR] AI model failed to generate hill climbing ideas" >&2
|
|
1248
|
-
safe_popd
|
|
1249
|
-
rm -f "$temp_csv"
|
|
1250
|
-
return 1
|
|
1251
|
-
fi
|
|
1252
|
-
|
|
1253
|
-
# Restore working directory
|
|
1254
|
-
safe_popd
|
|
1255
|
-
|
|
1256
|
-
# Validate that the CSV file was actually modified with correct IDs
|
|
1257
|
-
# Pass original_csv_lines to prevent race conditions
|
|
1258
|
-
if ! validate_direct_csv_modification "$temp_csv" "$count" "hill-climbing" "$ai_response" "$required_ids_str" "$original_csv_lines"; then
|
|
1259
|
-
rm -f "$temp_csv"
|
|
1260
|
-
return 1
|
|
1261
|
-
fi
|
|
1262
|
-
|
|
1263
|
-
echo "[INFO] Hill climbing ideas generated successfully"
|
|
1264
|
-
return 0
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
|
-
# Generate structural mutation ideas using direct CSV modification
|
|
1268
|
-
generate_structural_mutation_direct() {
|
|
1269
|
-
local count="$1"
|
|
1270
|
-
local top_performers="$2"
|
|
1271
|
-
|
|
1272
|
-
# Get the next available ID BEFORE creating temp CSV
|
|
1273
|
-
local next_id
|
|
1274
|
-
next_id=$(get_next_id "$CURRENT_GENERATION")
|
|
1275
|
-
echo "[INFO] Next available ID for structural mutation: $next_id" >&2
|
|
1276
|
-
|
|
1277
|
-
# Generate the list of IDs this strategy should use
|
|
1278
|
-
local next_id_num
|
|
1279
|
-
next_id_num=$(echo "$next_id" | grep -o '[0-9]*$')
|
|
1280
|
-
# Strip leading zeros to avoid octal interpretation in arithmetic
|
|
1281
|
-
next_id_num=$((10#$next_id_num))
|
|
1282
|
-
local required_ids=()
|
|
1283
|
-
for ((i=0; i<count; i++)); do
|
|
1284
|
-
required_ids+=("$(printf "gen%s-%03d" "$CURRENT_GENERATION" $((next_id_num + i)))")
|
|
1285
|
-
done
|
|
1286
|
-
local required_ids_str="${required_ids[*]}"
|
|
1287
|
-
|
|
1288
|
-
# Create temporary CSV copy in evolution directory (so AI can access it)
|
|
1289
|
-
local temp_csv="$FULL_EVOLUTION_DIR/temp-csv-$$.csv"
|
|
1290
|
-
cp "$FULL_CSV_PATH" "$temp_csv"
|
|
1291
|
-
|
|
1292
|
-
# CRITICAL: Capture the original line count immediately after copying
|
|
1293
|
-
local original_csv_lines
|
|
1294
|
-
original_csv_lines=$(wc -l < "$temp_csv" | tr -d '[:space:]')
|
|
1295
|
-
echo "[DEBUG] Original CSV has $original_csv_lines lines (including header)" >&2
|
|
1296
|
-
|
|
1297
|
-
# Extract just the IDs from top performers for clarity (needed before pre-populating)
|
|
1298
|
-
local valid_parent_ids
|
|
1299
|
-
valid_parent_ids=$(echo "$top_performers" | cut -d',' -f1 | paste -sd ',' -)
|
|
1300
|
-
|
|
1301
|
-
# Pre-populate the CSV with stub rows
|
|
1302
|
-
echo "[INFO] Pre-populating CSV with stub rows: $required_ids_str"
|
|
1303
|
-
local first_parent_id
|
|
1304
|
-
first_parent_id=$(echo "$valid_parent_ids" | cut -d',' -f1)
|
|
1305
|
-
for id in "${required_ids[@]}"; do
|
|
1306
|
-
echo "$id,$first_parent_id,\"[PLACEHOLDER: Replace with structural modification idea]\",,pending" >> "$temp_csv"
|
|
1307
|
-
done
|
|
1308
|
-
|
|
1309
|
-
echo "[INFO] Generating $count structural mutation ideas with IDs: $required_ids_str"
|
|
1310
|
-
|
|
1311
|
-
# Count total lines in temp CSV (including header)
|
|
1312
|
-
local total_lines
|
|
1313
|
-
total_lines=$(wc -l < "$temp_csv" | tr -d '[:space:]')
|
|
1314
|
-
local read_offset=$((total_lines - 25))
|
|
1315
|
-
if [[ $read_offset -lt 1 ]]; then
|
|
1316
|
-
read_offset=1
|
|
1317
|
-
fi
|
|
1318
|
-
|
|
1319
|
-
# Get existing Python files for this generation to avoid ID collisions
|
|
1320
|
-
local existing_py_files=$(get_existing_py_files_for_generation "$CURRENT_GENERATION")
|
|
1321
|
-
|
|
1322
|
-
# Use relative paths and change to evolution directory so AI can access files
|
|
1323
|
-
local temp_csv_basename=$(basename "$temp_csv")
|
|
1324
|
-
|
|
1325
|
-
# Build prompt using cat with heredoc to avoid variable expansion issues
|
|
1326
|
-
local prompt
|
|
1327
|
-
prompt="$(get_git_protection_warning)
|
|
1328
|
-
|
|
1329
|
-
$(cat <<EOF
|
|
1330
|
-
I need you to use your file editing capabilities to fill in PLACEHOLDER descriptions in the CSV file: $temp_csv_basename
|
|
1331
|
-
|
|
1332
|
-
THE FILE HAS $total_lines TOTAL LINES. Read from line $read_offset to see the placeholder rows at the end.
|
|
1333
|
-
|
|
1334
|
-
IMPORTANT: You MUST use one of these exact parent IDs: $valid_parent_ids
|
|
1335
|
-
|
|
1336
|
-
Successful algorithms to modify structurally:
|
|
1337
|
-
$top_performers
|
|
1338
|
-
|
|
1339
|
-
IMPORTANT: DO NOT read evolution_*.py files. Generate structural ideas based ONLY on:
|
|
1340
|
-
- The algorithm descriptions above
|
|
1341
|
-
- The performance scores
|
|
1342
|
-
- Your knowledge of common algorithmic structures and patterns
|
|
1343
|
-
Reading code files wastes tokens and time. Focus on high-level architectural ideas based on the descriptions.
|
|
1344
|
-
EOF
|
|
1345
|
-
)"
|
|
1346
|
-
|
|
1347
|
-
prompt+="
|
|
1348
|
-
|
|
1349
|
-
CROSS-POLLINATION OPPORTUNITY:
|
|
1350
|
-
Consider looking at the last 20-30 ideas from other evolution files in sibling directories (../*/evolution.csv).
|
|
1351
|
-
These may contain different structural approaches or architectural patterns that could be adapted to your top performers.
|
|
1352
|
-
This is OPTIONAL - only do it if you think it would help generate more creative structural modifications.
|
|
1353
|
-
|
|
1354
|
-
CRITICAL TASK:
|
|
1355
|
-
The CSV file already contains $count stub rows with these IDs: $required_ids_str
|
|
1356
|
-
Each stub row has a PLACEHOLDER description like: \"[PLACEHOLDER: Replace with structural modification idea]\"
|
|
1357
|
-
Your job is to REPLACE each PLACEHOLDER with a real structural modification idea.
|
|
1358
|
-
|
|
1359
|
-
⚠️ CRITICAL FILE READING INSTRUCTIONS ⚠️
|
|
1360
|
-
THE CSV FILE IS VERY LARGE (OVER 100,000 TOKENS). YOU WILL RUN OUT OF CONTEXT IF YOU READ IT ALL!
|
|
1361
|
-
- DO NOT read the entire file or you will exceed context limits and CRASH
|
|
1362
|
-
- Use: Read(file_path='$temp_csv_basename', offset=$read_offset, limit=25)
|
|
1363
|
-
- This will read ONLY the last 25 lines where the placeholders are
|
|
1364
|
-
- DO NOT READ FROM OFFSET 0 - that will load the entire huge file and fail!
|
|
1365
|
-
|
|
1366
|
-
CRITICAL INSTRUCTIONS:
|
|
1367
|
-
1. Read ONLY the last 25 lines using the offset specified above
|
|
1368
|
-
2. DO NOT ADD OR DELETE ANY ROWS - only EDIT the placeholder descriptions
|
|
1369
|
-
3. DO NOT CHANGE THE IDs - they are already correct ($required_ids_str)
|
|
1370
|
-
4. Use the Edit tool to replace EACH PLACEHOLDER text with a structural modification idea
|
|
1371
|
-
5. When editing, preserve the CSV structure: keep the ID field unchanged
|
|
1372
|
-
6. You may change the parent_id field if needed to reference a different top performer
|
|
1373
|
-
7. Each description should focus on architectural/structural changes
|
|
1374
|
-
8. CRITICAL: When editing, preserve the CSV formatting with proper quoting"
|
|
1375
|
-
|
|
1376
|
-
# Change to evolution directory so AI can access files (with safe restoration on interrupt)
|
|
1377
|
-
safe_pushd "$FULL_EVOLUTION_DIR" || {
|
|
1378
|
-
echo "[ERROR] Failed to change to evolution directory" >&2
|
|
1379
|
-
rm -f "$temp_csv"
|
|
1380
|
-
return 1
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
|
-
# Get AI to directly edit the CSV file
|
|
1384
|
-
local ai_response
|
|
1385
|
-
if ! ai_response=$(call_ai_for_ideation "$prompt" "$CURRENT_GENERATION" "$count" "$temp_csv_basename"); then
|
|
1386
|
-
echo "[ERROR] AI model failed to generate structural mutation ideas" >&2
|
|
1387
|
-
safe_popd
|
|
1388
|
-
rm -f "$temp_csv"
|
|
1389
|
-
return 1
|
|
1390
|
-
fi
|
|
1391
|
-
|
|
1392
|
-
# Restore working directory
|
|
1393
|
-
safe_popd
|
|
1394
|
-
|
|
1395
|
-
# Validate that the CSV file was actually modified with correct IDs
|
|
1396
|
-
# Pass original_csv_lines to prevent race conditions
|
|
1397
|
-
if ! validate_direct_csv_modification "$temp_csv" "$count" "structural" "$ai_response" "$required_ids_str" "$original_csv_lines"; then
|
|
1398
|
-
rm -f "$temp_csv"
|
|
1399
|
-
return 1
|
|
1400
|
-
fi
|
|
1401
|
-
|
|
1402
|
-
echo "[INFO] Structural mutation ideas generated successfully"
|
|
1403
|
-
return 0
|
|
1404
|
-
}
|
|
1405
|
-
|
|
1406
|
-
# Generate crossover hybrid ideas using direct CSV modification
|
|
1407
|
-
generate_crossover_direct() {
|
|
1408
|
-
local count="$1"
|
|
1409
|
-
local top_performers="$2"
|
|
1410
|
-
|
|
1411
|
-
# Get the next available ID BEFORE creating temp CSV
|
|
1412
|
-
local next_id
|
|
1413
|
-
next_id=$(get_next_id "$CURRENT_GENERATION")
|
|
1414
|
-
echo "[INFO] Next available ID for crossover: $next_id" >&2
|
|
1415
|
-
|
|
1416
|
-
# Generate the list of IDs this strategy should use
|
|
1417
|
-
local next_id_num
|
|
1418
|
-
next_id_num=$(echo "$next_id" | grep -o '[0-9]*$')
|
|
1419
|
-
# Strip leading zeros to avoid octal interpretation in arithmetic
|
|
1420
|
-
next_id_num=$((10#$next_id_num))
|
|
1421
|
-
local required_ids=()
|
|
1422
|
-
for ((i=0; i<count; i++)); do
|
|
1423
|
-
required_ids+=("$(printf "gen%s-%03d" "$CURRENT_GENERATION" $((next_id_num + i)))")
|
|
1424
|
-
done
|
|
1425
|
-
local required_ids_str="${required_ids[*]}"
|
|
1426
|
-
|
|
1427
|
-
# Create temporary CSV copy in evolution directory (so AI can access it)
|
|
1428
|
-
local temp_csv="$FULL_EVOLUTION_DIR/temp-csv-$$.csv"
|
|
1429
|
-
cp "$FULL_CSV_PATH" "$temp_csv"
|
|
1430
|
-
|
|
1431
|
-
# CRITICAL: Capture the original line count immediately after copying
|
|
1432
|
-
local original_csv_lines
|
|
1433
|
-
original_csv_lines=$(wc -l < "$temp_csv" | tr -d '[:space:]')
|
|
1434
|
-
echo "[DEBUG] Original CSV has $original_csv_lines lines (including header)" >&2
|
|
1435
|
-
|
|
1436
|
-
# Extract just the IDs from top performers for clarity (needed before pre-populating)
|
|
1437
|
-
local valid_parent_ids
|
|
1438
|
-
valid_parent_ids=$(echo "$top_performers" | cut -d',' -f1 | paste -sd ',' -)
|
|
1439
|
-
|
|
1440
|
-
# Pre-populate the CSV with stub rows
|
|
1441
|
-
echo "[INFO] Pre-populating CSV with stub rows: $required_ids_str"
|
|
1442
|
-
local first_parent_id
|
|
1443
|
-
first_parent_id=$(echo "$valid_parent_ids" | cut -d',' -f1)
|
|
1444
|
-
for id in "${required_ids[@]}"; do
|
|
1445
|
-
echo "$id,$first_parent_id,\"[PLACEHOLDER: Replace with crossover hybrid idea]\",,pending" >> "$temp_csv"
|
|
1446
|
-
done
|
|
1447
|
-
|
|
1448
|
-
echo "[INFO] Generating $count crossover hybrid ideas with IDs: $required_ids_str"
|
|
1449
|
-
|
|
1450
|
-
# Count total lines in temp CSV (including header)
|
|
1451
|
-
local total_lines
|
|
1452
|
-
total_lines=$(wc -l < "$temp_csv" | tr -d '[:space:]')
|
|
1453
|
-
local read_offset=$((total_lines - 25))
|
|
1454
|
-
if [[ $read_offset -lt 1 ]]; then
|
|
1455
|
-
read_offset=1
|
|
1456
|
-
fi
|
|
1457
|
-
|
|
1458
|
-
# Get existing Python files for this generation to avoid ID collisions
|
|
1459
|
-
local existing_py_files=$(get_existing_py_files_for_generation "$CURRENT_GENERATION")
|
|
1460
|
-
|
|
1461
|
-
# Use relative paths and change to evolution directory so AI can access files
|
|
1462
|
-
local temp_csv_basename=$(basename "$temp_csv")
|
|
1463
|
-
|
|
1464
|
-
# Build prompt using cat with heredoc to avoid variable expansion issues
|
|
1465
|
-
local prompt
|
|
1466
|
-
prompt="$(get_git_protection_warning)
|
|
1467
|
-
|
|
1468
|
-
$(cat <<EOF
|
|
1469
|
-
I need you to use your file editing capabilities to fill in PLACEHOLDER descriptions in the CSV file: $temp_csv_basename
|
|
1470
|
-
|
|
1471
|
-
THE FILE HAS $total_lines TOTAL LINES. Read from line $read_offset to see the placeholder rows at the end.
|
|
1472
|
-
|
|
1473
|
-
IMPORTANT: You MUST use ONLY these exact parent IDs: $valid_parent_ids
|
|
1474
|
-
|
|
1475
|
-
Top performers to combine (reference at least 2 in each idea):
|
|
1476
|
-
$top_performers
|
|
1477
|
-
|
|
1478
|
-
IMPORTANT: DO NOT read evolution_*.py files. Generate crossover ideas based ONLY on:
|
|
1479
|
-
- The algorithm descriptions above (which describe their key features)
|
|
1480
|
-
- The performance scores
|
|
1481
|
-
- Your knowledge of how different algorithmic approaches can be combined
|
|
1482
|
-
Reading code files wastes tokens and time. Focus on combining the described features creatively.
|
|
1483
|
-
EOF
|
|
1484
|
-
)"
|
|
1485
|
-
|
|
1486
|
-
prompt+="
|
|
1487
|
-
|
|
1488
|
-
CROSS-POLLINATION OPPORTUNITY:
|
|
1489
|
-
Consider looking at the last 20-30 ideas from other evolution files in sibling directories (../*/evolution.csv).
|
|
1490
|
-
These parallel evolutionary strains may have developed different successful features that could be crossed with your top performers.
|
|
1491
|
-
This is OPTIONAL - only do it if you think it would help generate more innovative hybrid ideas.
|
|
1492
|
-
|
|
1493
|
-
CRITICAL TASK:
|
|
1494
|
-
The CSV file already contains $count stub rows with these IDs: $required_ids_str
|
|
1495
|
-
Each stub row has a PLACEHOLDER description like: \"[PLACEHOLDER: Replace with crossover hybrid idea]\"
|
|
1496
|
-
Your job is to REPLACE each PLACEHOLDER with a real crossover/hybrid idea that combines 2+ algorithms.
|
|
1497
|
-
|
|
1498
|
-
⚠️ CRITICAL FILE READING INSTRUCTIONS ⚠️
|
|
1499
|
-
THE CSV FILE IS VERY LARGE (OVER 100,000 TOKENS). YOU WILL RUN OUT OF CONTEXT IF YOU READ IT ALL!
|
|
1500
|
-
- DO NOT read the entire file or you will exceed context limits and CRASH
|
|
1501
|
-
- Use: Read(file_path='$temp_csv_basename', offset=$read_offset, limit=25)
|
|
1502
|
-
- This will read ONLY the last 25 lines where the placeholders are
|
|
1503
|
-
- DO NOT READ FROM OFFSET 0 - that will load the entire huge file and fail!
|
|
1504
|
-
|
|
1505
|
-
CRITICAL INSTRUCTIONS:
|
|
1506
|
-
1. Read ONLY the last 25 lines using the offset specified above
|
|
1507
|
-
2. DO NOT ADD OR DELETE ANY ROWS - only EDIT the placeholder descriptions
|
|
1508
|
-
3. DO NOT CHANGE THE IDs - they are already correct ($required_ids_str)
|
|
1509
|
-
4. Use the Edit tool to replace EACH PLACEHOLDER text with a crossover idea
|
|
1510
|
-
5. When editing, preserve the CSV structure: keep the ID field unchanged
|
|
1511
|
-
6. You may change the parent_id field if needed (choose the primary parent)
|
|
1512
|
-
7. Each description should combine actual elements from 2+ top performers
|
|
1513
|
-
8. CRITICAL: When editing, preserve the CSV formatting with proper quoting"
|
|
1514
|
-
|
|
1515
|
-
# Change to evolution directory so AI can access files (with safe restoration on interrupt)
|
|
1516
|
-
safe_pushd "$FULL_EVOLUTION_DIR" || {
|
|
1517
|
-
echo "[ERROR] Failed to change to evolution directory" >&2
|
|
1518
|
-
rm -f "$temp_csv"
|
|
1519
|
-
return 1
|
|
1520
|
-
}
|
|
1521
|
-
|
|
1522
|
-
# Get AI to directly edit the CSV file
|
|
1523
|
-
local ai_response
|
|
1524
|
-
if ! ai_response=$(call_ai_for_ideation "$prompt" "$CURRENT_GENERATION" "$count" "$temp_csv_basename"); then
|
|
1525
|
-
echo "[ERROR] AI model failed to generate crossover hybrid ideas" >&2
|
|
1526
|
-
safe_popd
|
|
1527
|
-
rm -f "$temp_csv"
|
|
1528
|
-
return 1
|
|
1529
|
-
fi
|
|
1530
|
-
|
|
1531
|
-
# Restore working directory
|
|
1532
|
-
safe_popd
|
|
1533
|
-
|
|
1534
|
-
# Validate that the CSV file was actually modified with correct IDs
|
|
1535
|
-
# Pass original_csv_lines to prevent race conditions
|
|
1536
|
-
if ! validate_direct_csv_modification "$temp_csv" "$count" "crossover" "$ai_response" "$required_ids_str" "$original_csv_lines"; then
|
|
1537
|
-
rm -f "$temp_csv"
|
|
1538
|
-
return 1
|
|
1539
|
-
fi
|
|
1540
|
-
|
|
1541
|
-
echo "[INFO] Crossover hybrid ideas generated successfully"
|
|
1542
|
-
return 0
|
|
1543
|
-
}
|
|
1544
|
-
|
|
1545
|
-
# Legacy AI generation mode (for backward compatibility)
|
|
1546
|
-
ideate_ai_legacy() {
|
|
1547
|
-
if [[ ! -f "$FULL_BRIEF_PATH" ]]; then
|
|
1548
|
-
echo "[ERROR] $BRIEF_FILE not found. Run 'claude-evolve setup' first." >&2
|
|
1549
|
-
exit 1
|
|
1550
|
-
fi
|
|
1551
|
-
|
|
1552
|
-
# Create temporary CSV copy in evolution directory (so AI can access it)
|
|
1553
|
-
local temp_csv="$FULL_EVOLUTION_DIR/temp-csv-$$.csv"
|
|
1554
|
-
cp "$FULL_CSV_PATH" "$temp_csv"
|
|
1555
|
-
|
|
1556
|
-
# CRITICAL: Capture the original line count immediately after copying
|
|
1557
|
-
local original_csv_lines
|
|
1558
|
-
original_csv_lines=$(wc -l < "$temp_csv" | tr -d '[:space:]')
|
|
1559
|
-
echo "[DEBUG] Original CSV has $original_csv_lines lines (including header)" >&2
|
|
1560
|
-
|
|
1561
|
-
echo "[INFO] Generating $TOTAL_IDEAS ideas (legacy mode)..."
|
|
1562
|
-
|
|
1563
|
-
# Get top performers for context
|
|
1564
|
-
local top_performers=""
|
|
1565
|
-
if [[ -f "$FULL_CSV_PATH" ]]; then
|
|
1566
|
-
# Simple top performers extraction (lines with non-empty performance)
|
|
1567
|
-
top_performers=$(awk -F, 'NR > 1 && $4 != "" { print $1 ": " $3 " (score: " $4 ")" }' "$FULL_CSV_PATH" | head -5)
|
|
1568
|
-
fi
|
|
1569
|
-
|
|
1570
|
-
# Build prompt for direct CSV modification
|
|
1571
|
-
# Use relative paths and change to evolution directory so AI can access files
|
|
1572
|
-
local temp_csv_basename=$(basename "$temp_csv")
|
|
1573
|
-
|
|
1574
|
-
# Build initial prompt safely
|
|
1575
|
-
local prompt
|
|
1576
|
-
prompt="$(get_git_protection_warning)
|
|
1577
|
-
|
|
1578
|
-
$(cat <<EOF
|
|
1579
|
-
I need you to use your file editing capabilities to add exactly $TOTAL_IDEAS algorithmic ideas to the CSV file: $temp_csv_basename
|
|
1580
|
-
|
|
1581
|
-
Current evolution context:
|
|
1582
|
-
- Generation: $CURRENT_GENERATION
|
|
1583
|
-
- Algorithm: algorithm.py (base algorithm)
|
|
1584
|
-
- Brief: $(head -10 "$FULL_BRIEF_PATH" 2>/dev/null | head -c 1000 || echo "No brief file found")
|
|
1585
|
-
|
|
1586
|
-
IMPORTANT: DO NOT read algorithm.py or any evolution_*.py files - that uses too many tokens and is unnecessary for ideation. Just generate creative ideas based on the brief and top performers listed above. Focus your creativity on the problem space, not the implementation details.
|
|
1587
|
-
EOF
|
|
1588
|
-
)"
|
|
1589
|
-
|
|
1590
|
-
if [[ -n $top_performers ]]; then
|
|
1591
|
-
prompt+="
|
|
1592
|
-
|
|
1593
|
-
Top Performing Algorithms So Far:
|
|
1594
|
-
$top_performers"
|
|
1595
|
-
fi
|
|
1596
|
-
|
|
1597
|
-
prompt+="
|
|
1598
|
-
|
|
1599
|
-
CRITICAL INSTRUCTIONS:
|
|
1600
|
-
1. Use the Read tool to examine the current CSV file
|
|
1601
|
-
IMPORTANT: If the CSV file is large (>200 lines), read it in chunks using the offset and limit parameters to avoid context overload
|
|
1602
|
-
Example: Read(file_path='temp-csv-123.csv', offset=0, limit=100) then Read(offset=100, limit=100), etc.
|
|
1603
|
-
2. DO NOT DELETE OR REPLACE ANY EXISTING ROWS - YOU MUST PRESERVE ALL EXISTING DATA
|
|
1604
|
-
3. Find the highest ID number for generation $CURRENT_GENERATION (e.g., if gen$CURRENT_GENERATION-003 exists, next should be gen$CURRENT_GENERATION-004)
|
|
1605
|
-
4. If no gen$CURRENT_GENERATION entries exist yet, start with gen$CURRENT_GENERATION-001
|
|
1606
|
-
5. Use the Edit or MultiEdit tool to APPEND exactly $TOTAL_IDEAS new rows AT THE END of the CSV file
|
|
1607
|
-
6. For each idea, create a row with: id,parent_id,description,,pending
|
|
1608
|
-
7. CRITICAL CSV FORMATTING RULES:
|
|
1609
|
-
- ALWAYS wrap the description field in double quotes
|
|
1610
|
-
- If the description contains quotes, escape them by doubling them (\" becomes \"\")
|
|
1611
|
-
- Example: gen01-001,gen00-000,\"Implement adaptive RSI thresholds based on volatility\",,pending
|
|
1612
|
-
- BAD: gen01-001,gen00-000,Implement adaptive RSI thresholds based on volatility,,pending
|
|
1613
|
-
- NEVER omit quotes around descriptions - this causes CSV parsing errors that corrupt the data
|
|
1614
|
-
8. Mix both parameter tuning and structural changes
|
|
1615
|
-
9. If building on existing algorithms, use their ID as parent_id, otherwise leave parent_id empty
|
|
1616
|
-
|
|
1617
|
-
⚠️ AVOID ONLY: Kelly floor/cap adjustments that assume leverage > 1.0 (these get clamped and have no effect)
|
|
1618
|
-
|
|
1619
|
-
✅ EXPLORE ALL CREATIVE POSSIBILITIES INCLUDING:
|
|
1620
|
-
- Machine Learning: Neural networks, ensemble methods, reinforcement learning (use train() method)
|
|
1621
|
-
- Advanced Indicators: Custom combinations, multi-timeframe signals, cross-asset indicators
|
|
1622
|
-
- Market Regime Detection: VIX patterns, correlation analysis, volatility clustering
|
|
1623
|
-
- Risk Management: Dynamic stops, portfolio heat, correlation-based position sizing
|
|
1624
|
-
- Alternative Strategies: New sub-strategies, momentum variants, mean reversion innovations
|
|
1625
|
-
- Multi-Asset Signals: Sector rotation, bond yields, commodity signals
|
|
1626
|
-
- Time-Based Patterns: Intraday effects, calendar anomalies, volatility timing
|
|
1627
|
-
- Parameter Optimization: Entry thresholds, indicator periods, strategy weights
|
|
1628
|
-
|
|
1629
|
-
IMPORTANT: You must APPEND new rows to the existing CSV file. DO NOT replace the file contents. All existing rows must remain unchanged.
|
|
1630
|
-
CRITICAL: You must use your file editing tools (Edit/MultiEdit) to modify the CSV file. DO NOT return CSV text - use your tools to edit the file directly."
|
|
1631
|
-
|
|
1632
|
-
# Change to evolution directory so AI can access files (with safe restoration on interrupt)
|
|
1633
|
-
safe_pushd "$FULL_EVOLUTION_DIR" || {
|
|
1634
|
-
echo "[ERROR] Failed to change to evolution directory" >&2
|
|
1635
|
-
rm -f "$temp_csv"
|
|
1636
|
-
return 1
|
|
1637
|
-
}
|
|
1638
|
-
|
|
1639
|
-
# Get AI to directly edit the CSV file
|
|
1640
|
-
local ai_response
|
|
1641
|
-
if ! ai_response=$(call_ai_for_ideation "$prompt" "$CURRENT_GENERATION" "$TOTAL_IDEAS" "$temp_csv_basename"); then
|
|
1642
|
-
echo "[ERROR] AI model failed to generate ideas" >&2
|
|
1643
|
-
safe_popd
|
|
1644
|
-
rm -f "$temp_csv"
|
|
1645
|
-
return 1
|
|
1646
|
-
fi
|
|
1647
|
-
|
|
1648
|
-
# Restore working directory
|
|
1649
|
-
safe_popd
|
|
1650
|
-
|
|
1651
|
-
# Validate that the CSV file was actually modified
|
|
1652
|
-
# Pass original_csv_lines to prevent race conditions
|
|
1653
|
-
if ! validate_direct_csv_modification "$temp_csv" "$TOTAL_IDEAS" "mixed" "$ai_response" "" "$original_csv_lines"; then
|
|
1654
|
-
rm -f "$temp_csv"
|
|
1655
|
-
return 1
|
|
1656
|
-
fi
|
|
1657
|
-
|
|
1658
|
-
echo "[INFO] Legacy ideas generated"
|
|
1659
|
-
return 0
|
|
1660
|
-
}
|
|
1661
|
-
|
|
1662
|
-
# AIDEV-NOTE: Determine which generation to use for ideation
|
|
1663
|
-
# Check if the HIGHEST existing generation needs more ideas before creating a new one
|
|
1664
|
-
get_highest_generation() {
|
|
1665
|
-
if [[ ! -f "$FULL_CSV_PATH" ]]; then
|
|
1666
|
-
echo "0"
|
|
1667
|
-
return
|
|
1668
|
-
fi
|
|
1669
|
-
"$PYTHON_CMD" -c "
|
|
1670
|
-
import csv
|
|
1671
|
-
max_gen = 0
|
|
1672
|
-
with open('$FULL_CSV_PATH', 'r') as f:
|
|
1673
|
-
reader = csv.reader(f)
|
|
1674
|
-
next(reader, None) # Skip header
|
|
1675
|
-
for row in reader:
|
|
1676
|
-
if row and len(row) > 0:
|
|
1677
|
-
id_field = row[0].strip()
|
|
1678
|
-
if id_field.startswith('gen') and '-' in id_field:
|
|
1679
|
-
try:
|
|
1680
|
-
gen_part = id_field.split('-')[0]
|
|
1681
|
-
gen_num = int(gen_part[3:])
|
|
1682
|
-
max_gen = max(max_gen, gen_num)
|
|
1683
|
-
except (ValueError, IndexError):
|
|
1684
|
-
pass
|
|
1685
|
-
print(max_gen)
|
|
1686
|
-
"
|
|
1687
|
-
}
|
|
1688
|
-
|
|
1689
|
-
count_generation_ideas() {
|
|
1690
|
-
local gen="$1"
|
|
1691
|
-
local count
|
|
1692
|
-
count=$(grep -c "^gen${gen}-" "$FULL_CSV_PATH" 2>/dev/null) || true
|
|
1693
|
-
echo "${count:-0}"
|
|
1694
|
-
}
|
|
1695
|
-
|
|
1696
|
-
count_generation_pending() {
|
|
1697
|
-
local gen="$1"
|
|
1698
|
-
local count
|
|
1699
|
-
count=$(grep -c "^gen${gen}-.*,pending" "$FULL_CSV_PATH" 2>/dev/null) || true
|
|
1700
|
-
echo "${count:-0}"
|
|
1701
|
-
}
|
|
1702
|
-
|
|
1703
|
-
# Find the right generation to use
|
|
1704
|
-
# AIDEV-NOTE: Generation numbers must be zero-padded to 2 digits (gen01, gen02, etc.)
|
|
1705
|
-
# to maintain consistency with existing CSV data format
|
|
1706
|
-
highest_gen=$(get_highest_generation)
|
|
1707
|
-
if [[ ${highest_gen:-0} -eq 0 ]]; then
|
|
1708
|
-
# No generations yet, start with 01
|
|
1709
|
-
CURRENT_GENERATION="01"
|
|
1710
|
-
echo "[INFO] No existing generations, starting with generation 01"
|
|
1711
|
-
else
|
|
1712
|
-
# Check if highest generation needs more ideas
|
|
1713
|
-
# Use unpadded for grep patterns (matches both gen1- and gen01-)
|
|
1714
|
-
existing_ideas=$(count_generation_ideas "$highest_gen")
|
|
1715
|
-
pending_ideas=$(count_generation_pending "$highest_gen")
|
|
1716
|
-
# Also check padded format
|
|
1717
|
-
padded_gen=$(printf "%02d" "$((10#$highest_gen))")
|
|
1718
|
-
existing_ideas_padded=$(count_generation_ideas "$padded_gen")
|
|
1719
|
-
pending_ideas_padded=$(count_generation_pending "$padded_gen")
|
|
1720
|
-
# Use whichever found more (handles both gen1 and gen01 formats)
|
|
1721
|
-
if [[ ${existing_ideas_padded:-0} -gt ${existing_ideas:-0} ]]; then
|
|
1722
|
-
existing_ideas=$existing_ideas_padded
|
|
1723
|
-
pending_ideas=$pending_ideas_padded
|
|
1724
|
-
fi
|
|
1725
|
-
|
|
1726
|
-
echo "[INFO] Highest generation: $highest_gen with $existing_ideas total ideas ($pending_ideas pending)"
|
|
1727
|
-
|
|
1728
|
-
if [[ $existing_ideas -ge $TOTAL_IDEAS ]]; then
|
|
1729
|
-
# Highest generation is full, create a new one
|
|
1730
|
-
CURRENT_GENERATION=$(get_next_generation)
|
|
1731
|
-
# Ensure it's zero-padded
|
|
1732
|
-
CURRENT_GENERATION=$(printf "%02d" "$((10#$CURRENT_GENERATION))")
|
|
1733
|
-
echo "[INFO] Generation $highest_gen is full ($existing_ideas >= $TOTAL_IDEAS), creating generation $CURRENT_GENERATION"
|
|
1734
|
-
elif [[ $pending_ideas -ge $TOTAL_IDEAS ]]; then
|
|
1735
|
-
# Already have enough pending, skip ideation
|
|
1736
|
-
echo "[INFO] Generation $padded_gen already has $pending_ideas pending ideas (target: $TOTAL_IDEAS)"
|
|
1737
|
-
echo "[INFO] Skipping ideation - workers will process existing ideas"
|
|
1738
|
-
exit 0
|
|
1739
|
-
else
|
|
1740
|
-
# Continue filling the current highest generation - use padded format
|
|
1741
|
-
CURRENT_GENERATION=$(printf "%02d" "$((10#$highest_gen))")
|
|
1742
|
-
echo "[INFO] Continuing generation $CURRENT_GENERATION (need $((TOTAL_IDEAS - existing_ideas)) more ideas)"
|
|
1743
|
-
fi
|
|
1744
|
-
fi
|
|
1745
|
-
|
|
1746
|
-
echo "[INFO] Starting ideation for generation $CURRENT_GENERATION"
|
|
1747
|
-
|
|
1748
|
-
# Main execution with retry logic and exponential backoff
|
|
1749
|
-
retry_count=0
|
|
1750
|
-
wait_seconds=300 # Start with 5 minutes
|
|
1751
|
-
max_wait_seconds=300 # Cap at 5 minutes
|
|
1752
|
-
|
|
1753
|
-
while true; do
|
|
1754
|
-
# Re-check pending count in case another process added ideas
|
|
1755
|
-
pending_count=$(grep -c "^gen${CURRENT_GENERATION}-.*,pending" "$FULL_CSV_PATH" 2>/dev/null) || true
|
|
1756
|
-
pending_count=${pending_count:-0}
|
|
1757
|
-
total_count=$(grep -c "^gen${CURRENT_GENERATION}-" "$FULL_CSV_PATH" 2>/dev/null) || true
|
|
1758
|
-
total_count=${total_count:-0}
|
|
1759
|
-
|
|
1760
|
-
if [[ $total_count -ge $TOTAL_IDEAS ]]; then
|
|
1761
|
-
echo "[INFO] Generation $CURRENT_GENERATION now has $total_count ideas (target: $TOTAL_IDEAS)"
|
|
1762
|
-
echo "[INFO] Ideation complete for this generation"
|
|
1763
|
-
exit 0
|
|
1764
|
-
elif [[ ${pending_count:-0} -gt 0 ]]; then
|
|
1765
|
-
echo "[INFO] Found $pending_count pending, $total_count total for generation $CURRENT_GENERATION (target: $TOTAL_IDEAS)"
|
|
1766
|
-
fi
|
|
1767
|
-
|
|
1768
|
-
if [[ $use_strategies == true ]]; then
|
|
1769
|
-
echo "[INFO] Multi-strategy AI generation mode"
|
|
1770
|
-
if ideate_ai_strategies; then
|
|
1771
|
-
echo "[INFO] Ideation complete! Check $EVOLUTION_CSV for new ideas."
|
|
1772
|
-
exit 0
|
|
1773
|
-
fi
|
|
1774
|
-
else
|
|
1775
|
-
echo "[INFO] Legacy AI generation mode"
|
|
1776
|
-
if ideate_ai_legacy; then
|
|
1777
|
-
echo "[INFO] Ideation complete! Check $EVOLUTION_CSV for new ideas."
|
|
1778
|
-
exit 0
|
|
1779
|
-
fi
|
|
1780
|
-
fi
|
|
1781
|
-
|
|
1782
|
-
# If we reach here, ideation failed
|
|
1783
|
-
((retry_count++))
|
|
1784
|
-
|
|
1785
|
-
echo "[WARN] All ideation attempts failed (retry #$retry_count)" >&2
|
|
1786
|
-
echo "[INFO] This could be temporary API rate limits or service issues" >&2
|
|
1787
|
-
echo "[INFO] Waiting $wait_seconds seconds ($(($wait_seconds / 60)) minutes) before retrying..." >&2
|
|
1788
|
-
|
|
1789
|
-
# Sleep with countdown
|
|
1790
|
-
remaining=$wait_seconds
|
|
1791
|
-
while [[ $remaining -gt 0 ]]; do
|
|
1792
|
-
if [[ $((remaining % 60)) -eq 0 ]]; then
|
|
1793
|
-
echo "[INFO] Retrying in $((remaining / 60)) minutes..." >&2
|
|
1794
|
-
fi
|
|
1795
|
-
sleep 60
|
|
1796
|
-
remaining=$((remaining - 60))
|
|
1797
|
-
done
|
|
6
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'lib'))
|
|
1798
7
|
|
|
1799
|
-
|
|
8
|
+
from evolve_ideate import main
|
|
1800
9
|
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
if [[ $wait_seconds -gt $max_wait_seconds ]]; then
|
|
1804
|
-
wait_seconds=$max_wait_seconds
|
|
1805
|
-
fi
|
|
1806
|
-
done
|
|
10
|
+
if __name__ == '__main__':
|
|
11
|
+
sys.exit(main())
|