claude-evolve 1.3.39 → 1.3.40

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.
@@ -0,0 +1,293 @@
1
+ #!/bin/bash
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
+
10
+ # Use CLAUDE_EVOLVE_CONFIG if set, otherwise default
11
+ if [[ -n ${CLAUDE_EVOLVE_CONFIG:-} ]]; then
12
+ load_config "$CLAUDE_EVOLVE_CONFIG"
13
+ else
14
+ load_config
15
+ fi
16
+
17
+ # Function to show help
18
+ show_help() {
19
+ cat <<EOF
20
+ claude-evolve edit - Manage evolution candidate statuses by generation or status
21
+
22
+ USAGE:
23
+ claude-evolve edit <selector> <action>
24
+
25
+ SELECTORS:
26
+ gen01, gen02, etc. Target specific generation
27
+ all Target all generations
28
+ failed Target all candidates with failed status
29
+ complete Target all candidates with complete status
30
+ pending Target all candidates with pending status
31
+ running Target all candidates with running status
32
+
33
+ ACTIONS:
34
+ failed Mark candidates as failed (keeps scores)
35
+ complete Mark candidates as complete (keeps scores)
36
+ pending Mark candidates as pending (keeps scores)
37
+ reboot Reset completely (delete .py files, clear scores, set pending)
38
+
39
+ EXAMPLES:
40
+ claude-evolve edit gen03 failed # Mark all gen03 as failed
41
+ claude-evolve edit failed pending # Reset all failed candidates to pending
42
+ claude-evolve edit complete failed # Mark all complete as failed for re-run
43
+ claude-evolve edit all pending # Mark everything as pending for re-run
44
+ claude-evolve edit gen02 reboot # Full reset of gen02 (delete files + clear data)
45
+
46
+ DESCRIPTION:
47
+ This command helps manage evolution runs when you need to re-evaluate candidates.
48
+ Use status selectors (failed, complete, etc.) to bulk-change candidates by status.
49
+ Use 'reboot' for complete reset including file deletion.
50
+ EOF
51
+ }
52
+
53
+ # Parse arguments
54
+ if [[ $# -ne 2 ]]; then
55
+ show_help
56
+ exit 1
57
+ fi
58
+
59
+ SELECTOR="$1"
60
+ ACTION="$2"
61
+
62
+ # Validate configuration
63
+ if ! validate_config; then
64
+ echo "[ERROR] Configuration validation failed" >&2
65
+ exit 1
66
+ fi
67
+
68
+ # Validate selector format
69
+ if [[ "$SELECTOR" != "all" && ! "$SELECTOR" =~ ^gen[0-9]+$ && "$SELECTOR" != "failed" && "$SELECTOR" != "complete" && "$SELECTOR" != "pending" && "$SELECTOR" != "running" ]]; then
70
+ echo "[ERROR] Selector must be 'all', 'genXX' (e.g., gen01), or status ('failed', 'complete', 'pending', 'running')" >&2
71
+ exit 1
72
+ fi
73
+
74
+ # Validate action
75
+ case "$ACTION" in
76
+ failed|complete|pending|reboot) ;;
77
+ *)
78
+ echo "[ERROR] Action must be one of: failed, complete, pending, reboot" >&2
79
+ exit 1
80
+ ;;
81
+ esac
82
+
83
+ # Check if CSV exists
84
+ if [[ ! -f "$FULL_CSV_PATH" ]]; then
85
+ echo "[ERROR] Evolution CSV not found: $FULL_CSV_PATH" >&2
86
+ echo "Run 'claude-evolve setup' first or navigate to the correct directory" >&2
87
+ exit 1
88
+ fi
89
+
90
+ # Function to update CSV status for specific selector
91
+ update_candidates_status() {
92
+ local selector="$1"
93
+ local new_status="$2"
94
+ local clear_scores="$3"
95
+
96
+ echo "[INFO] Updating candidates matching '$selector' to status: $new_status"
97
+
98
+ # Use Python to safely edit the CSV
99
+ "$PYTHON_CMD" -c "
100
+ import csv
101
+ import sys
102
+ import os
103
+
104
+ csv_file = '$FULL_CSV_PATH'
105
+ selector = '$selector'
106
+ new_status = '$new_status'
107
+ clear_scores = '$clear_scores' == 'true'
108
+
109
+ try:
110
+ # Read CSV
111
+ with open(csv_file, 'r') as f:
112
+ reader = csv.reader(f)
113
+ rows = list(reader)
114
+
115
+ if not rows:
116
+ print('[ERROR] CSV is empty')
117
+ sys.exit(1)
118
+
119
+ header = rows[0]
120
+ updated_count = 0
121
+
122
+ # Update matching rows
123
+ for i in range(1, len(rows)):
124
+ row = rows[i]
125
+ if len(row) < 1:
126
+ continue
127
+
128
+ candidate_id = row[0]
129
+ current_status = row[4] if len(row) > 4 else ''
130
+
131
+ # Check if this row matches selector
132
+ matches = False
133
+ if selector == 'all':
134
+ matches = True
135
+ elif selector.startswith('gen') and '-' in candidate_id:
136
+ # Generation selector (e.g., gen01, gen02)
137
+ matches = candidate_id.startswith(selector + '-')
138
+ elif selector in ['failed', 'complete', 'pending', 'running']:
139
+ # Status selector
140
+ if selector == 'pending':
141
+ matches = current_status == '' or current_status == 'pending'
142
+ else:
143
+ matches = current_status == selector
144
+
145
+ if matches:
146
+ if clear_scores:
147
+ # Reboot: clear everything after description (keep id, basedOnId, description)
148
+ if len(row) >= 3:
149
+ rows[i] = [row[0], row[1], row[2], '', ''] # id, basedOnId, description, empty performance, empty status
150
+ updated_count += 1
151
+ else:
152
+ # Just update status (preserve other fields)
153
+ # Ensure row has at least 5 fields
154
+ while len(row) < 5:
155
+ row.append('')
156
+ row[4] = new_status # Update status field
157
+ updated_count += 1
158
+
159
+ # Write back to CSV
160
+ with open(csv_file + '.tmp', 'w', newline='') as f:
161
+ writer = csv.writer(f)
162
+ writer.writerows(rows)
163
+
164
+ # Atomic replace
165
+ os.rename(csv_file + '.tmp', csv_file)
166
+
167
+ print(f'[INFO] Updated {updated_count} candidates')
168
+
169
+ except Exception as e:
170
+ print(f'[ERROR] Failed to update CSV: {e}')
171
+ sys.exit(1)
172
+ "
173
+ }
174
+
175
+ # Function to delete evolution files for selector
176
+ delete_evolution_files() {
177
+ local selector="$1"
178
+
179
+ if [[ ! -d "$FULL_EVOLUTION_DIR" ]]; then
180
+ echo "[WARN] Evolution directory not found: $FULL_EVOLUTION_DIR"
181
+ return
182
+ fi
183
+
184
+ local deleted_count=0
185
+
186
+ if [[ "$selector" == "all" ]]; then
187
+ echo "[INFO] Deleting all evolution_*.py files..."
188
+ for file in "$FULL_EVOLUTION_DIR"/evolution_*.py; do
189
+ if [[ -f "$file" ]]; then
190
+ rm "$file"
191
+ ((deleted_count++))
192
+ echo "[INFO] Deleted: $(basename "$file")"
193
+ fi
194
+ done
195
+ elif [[ "$selector" =~ ^gen[0-9]+$ ]]; then
196
+ echo "[INFO] Deleting evolution files for $selector..."
197
+ for file in "$FULL_EVOLUTION_DIR"/evolution_${selector}-*.py; do
198
+ if [[ -f "$file" ]]; then
199
+ rm "$file"
200
+ ((deleted_count++))
201
+ echo "[INFO] Deleted: $(basename "$file")"
202
+ fi
203
+ done
204
+ else
205
+ # Status-based selector - need to query CSV for candidate IDs
206
+ echo "[INFO] Finding files to delete for status '$selector'..."
207
+
208
+ # Use Python to get list of candidate IDs matching the status
209
+ local candidates_to_delete
210
+ candidates_to_delete=$("$PYTHON_CMD" -c "
211
+ import csv
212
+ import sys
213
+
214
+ csv_file = '$FULL_CSV_PATH'
215
+ selector = '$selector'
216
+
217
+ try:
218
+ with open(csv_file, 'r') as f:
219
+ reader = csv.reader(f)
220
+ next(reader) # Skip header
221
+
222
+ candidates = []
223
+ for row in reader:
224
+ if len(row) < 1:
225
+ continue
226
+
227
+ candidate_id = row[0]
228
+ current_status = row[4] if len(row) > 4 else ''
229
+
230
+ # Check if matches status selector
231
+ matches = False
232
+ if selector == 'pending':
233
+ matches = current_status == '' or current_status == 'pending'
234
+ else:
235
+ matches = current_status == selector
236
+
237
+ if matches:
238
+ candidates.append(candidate_id)
239
+
240
+ print(' '.join(candidates))
241
+
242
+ except Exception as e:
243
+ print('', file=sys.stderr) # Empty output on error
244
+ sys.exit(1)
245
+ ")
246
+
247
+ if [[ -n "$candidates_to_delete" ]]; then
248
+ for candidate_id in $candidates_to_delete; do
249
+ # Determine file format
250
+ if [[ "$candidate_id" =~ ^[0-9]+$ ]]; then
251
+ file="$FULL_EVOLUTION_DIR/evolution_id${candidate_id}.py"
252
+ else
253
+ file="$FULL_EVOLUTION_DIR/evolution_${candidate_id}.py"
254
+ fi
255
+
256
+ if [[ -f "$file" ]]; then
257
+ rm "$file"
258
+ ((deleted_count++))
259
+ echo "[INFO] Deleted: $(basename "$file")"
260
+ fi
261
+ done
262
+ fi
263
+ fi
264
+
265
+ echo "[INFO] Deleted $deleted_count evolution files"
266
+ }
267
+
268
+ # Main execution
269
+ echo "[INFO] Processing '$SELECTOR' with action: $ACTION"
270
+
271
+ case "$ACTION" in
272
+ failed)
273
+ update_candidates_status "$SELECTOR" "failed" "false"
274
+ ;;
275
+ complete)
276
+ update_candidates_status "$SELECTOR" "complete" "false"
277
+ ;;
278
+ pending)
279
+ update_candidates_status "$SELECTOR" "" "false" # Empty status means pending
280
+ ;;
281
+ reboot)
282
+ echo "[INFO] Performing full reboot of '$SELECTOR'..."
283
+ delete_evolution_files "$SELECTOR"
284
+ update_candidates_status "$SELECTOR" "" "true" # Clear scores and set pending
285
+ echo "[INFO] Reboot complete: files deleted, scores cleared, status set to pending"
286
+ ;;
287
+ esac
288
+
289
+ echo "[INFO] Edit operation complete"
290
+
291
+ # Call status command to show current state
292
+ echo ""
293
+ "$SCRIPT_DIR/claude-evolve-status" --brief
@@ -30,7 +30,7 @@ get_model_for_generation() {
30
30
  if (( gen_num % 2 == 1 )); then
31
31
  echo "opus" # Odd generations: use Opus for exploration
32
32
  else
33
- echo "o3-pro" # Even generations: use o3-pro for refinement
33
+ echo "o3" # Even generations: use o3 for refinement
34
34
  fi
35
35
  }
36
36
 
@@ -46,19 +46,19 @@ call_ai_with_limit_check() {
46
46
  echo "[INFO] Generation $generation: Using $preferred_model" >&2
47
47
 
48
48
  # Try preferred model first
49
- if [[ "$preferred_model" == "o3-pro" ]] && command -v codex >/dev/null 2>&1; then
50
- echo "[INFO] Using codex o3-pro for ideation" >&2
49
+ if [[ "$preferred_model" == "o3" ]] && command -v codex >/dev/null 2>&1; then
50
+ echo "[INFO] Using codex o3 for ideation" >&2
51
51
 
52
- # Call codex with o3-pro model using -q flag and --full-auto
52
+ # Call codex with o3 model using -q flag and --full-auto
53
53
  local ai_output
54
- ai_output=$(codex -m o3-pro --full-auto -q "$prompt" 2>&1)
54
+ ai_output=$(codex -m o3 --full-auto -q "$prompt" 2>&1)
55
55
  local ai_exit_code=$?
56
56
 
57
57
  if [[ $ai_exit_code -eq 0 ]]; then
58
58
  echo "$ai_output"
59
59
  return 0
60
60
  else
61
- echo "[WARN] Codex o3-pro failed, falling back to Claude Opus" >&2
61
+ echo "[WARN] Codex o3 failed, falling back to Claude Opus" >&2
62
62
  preferred_model="opus"
63
63
  fi
64
64
  fi
@@ -58,11 +58,15 @@ USAGE:
58
58
  claude-evolve [--config=PATH] [COMMAND] [OPTIONS]
59
59
 
60
60
  COMMANDS:
61
- setup Initialize evolution workspace
62
- ideate Generate new algorithm ideas
63
- run Execute evolution candidates
64
- analyze Analyze evolution results
65
- help Show this help message
61
+ setup Initialize evolution workspace
62
+ ideate Generate new algorithm ideas
63
+ run Execute evolution candidates
64
+ analyze Analyze evolution results
65
+ edit Manage candidate statuses by generation
66
+ status Show evolution progress and current leader
67
+ cleanup Clean up unchanged algorithms and descendants
68
+ cleanup-duplicates Alias for cleanup (deprecated)
69
+ help Show this help message
66
70
 
67
71
  GLOBAL OPTIONS:
68
72
  --config=PATH Use alternate config file (default: evolution/config.yaml)
@@ -91,9 +95,11 @@ show_menu() {
91
95
  echo " 2) ideate - Generate new algorithm ideas"
92
96
  echo " 3) run - Execute evolution candidates"
93
97
  echo " 4) analyze - Analyze evolution results"
94
- echo " 5) config - Manage configuration settings"
95
- echo " 6) help - Show help message"
96
- echo " 7) exit - Exit"
98
+ echo " 5) edit - Manage candidate statuses by generation"
99
+ echo " 6) status - Show evolution progress and current leader"
100
+ echo " 7) config - Manage configuration settings"
101
+ echo " 8) help - Show help message"
102
+ echo " 9) exit - Exit"
97
103
  echo
98
104
 
99
105
  # Show workspace status
@@ -137,21 +143,23 @@ check_for_updates
137
143
  # Main logic
138
144
  if [[ $# -eq 0 ]]; then
139
145
  show_menu
140
- read -r -p "Enter your choice (1-7): " choice
146
+ read -r -p "Enter your choice (1-9): " choice
141
147
 
142
148
  case $choice in
143
149
  1) exec "$SCRIPT_DIR/claude-evolve-setup" ;;
144
150
  2) exec "$SCRIPT_DIR/claude-evolve-ideate" ;;
145
- 3) exec "$SCRIPT_DIR/claude-evolve-run-unified" ;;
151
+ 3) exec "$SCRIPT_DIR/claude-evolve-run" ;;
146
152
  4) exec "$SCRIPT_DIR/claude-evolve-analyze" ;;
147
- 5) exec "$SCRIPT_DIR/claude-evolve-config" ;;
148
- 6) show_help ;;
149
- 7)
153
+ 5) exec "$SCRIPT_DIR/claude-evolve-edit" ;;
154
+ 6) exec "$SCRIPT_DIR/claude-evolve-status" ;;
155
+ 7) exec "$SCRIPT_DIR/claude-evolve-config" ;;
156
+ 8) show_help ;;
157
+ 9)
150
158
  echo "Goodbye!"
151
159
  exit 0
152
160
  ;;
153
161
  *)
154
- echo -e "${RED}Invalid choice. Please select 1-7.${NC}"
162
+ echo -e "${RED}Invalid choice. Please select 1-9.${NC}"
155
163
  exit 1
156
164
  ;;
157
165
  esac
@@ -174,12 +182,24 @@ ideate)
174
182
  ;;
175
183
  run)
176
184
  shift
177
- exec "$SCRIPT_DIR/claude-evolve-run-unified" "$@"
185
+ exec "$SCRIPT_DIR/claude-evolve-run" "$@"
178
186
  ;;
179
187
  analyze)
180
188
  shift
181
189
  exec "$SCRIPT_DIR/claude-evolve-analyze" "$@"
182
190
  ;;
191
+ edit)
192
+ shift
193
+ exec "$SCRIPT_DIR/claude-evolve-edit" "$@"
194
+ ;;
195
+ status)
196
+ shift
197
+ exec "$SCRIPT_DIR/claude-evolve-status" "$@"
198
+ ;;
199
+ cleanup-duplicates|cleanup)
200
+ shift
201
+ exec "$SCRIPT_DIR/claude-evolve-cleanup" "$@"
202
+ ;;
183
203
  config)
184
204
  shift
185
205
  exec "$SCRIPT_DIR/claude-evolve-config" "$@"
@@ -236,12 +236,32 @@ cleanup_workers() {
236
236
  # Worker finished
237
237
  if wait "$pid" 2>/dev/null; then
238
238
  echo "[DISPATCHER] Worker $pid completed successfully"
239
+ consecutive_failures=0 # Reset counter on success
239
240
  else
240
241
  local exit_code=$?
241
242
  if [[ $exit_code -eq 2 ]]; then
242
243
  echo "[DISPATCHER] Worker $pid hit rate limit, will retry later"
244
+ # Rate limits don't count as consecutive failures
243
245
  else
244
246
  echo "[DISPATCHER] Worker $pid failed with exit code $exit_code"
247
+ ((consecutive_failures++))
248
+
249
+ # Check if we've hit the failure limit
250
+ if [[ $consecutive_failures -ge $MAX_CONSECUTIVE_FAILURES ]]; then
251
+ echo "" >&2
252
+ echo "🚨🚨🚨 EVOLUTION STOPPED: TOO MANY FAILURES 🚨🚨🚨" >&2
253
+ echo "ERROR: $consecutive_failures consecutive worker failures detected" >&2
254
+ echo "ERROR: This indicates a systemic problem (Claude API issues, evaluator bugs, etc.)" >&2
255
+ echo "ERROR: Check logs and fix issues before restarting evolution" >&2
256
+ echo "🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨" >&2
257
+ echo "" >&2
258
+
259
+ # Shutdown all workers and exit
260
+ shutdown_workers
261
+ exit 1
262
+ fi
263
+
264
+ echo "[DISPATCHER] Consecutive failures: $consecutive_failures/$MAX_CONSECUTIVE_FAILURES"
245
265
  fi
246
266
  fi
247
267
  fi
@@ -269,9 +289,26 @@ get_csv_stats() {
269
289
  total_rows=$(wc -l < "$csv_path" | tr -d '[:space:]')
270
290
  complete_count=$(grep ',complete' "$csv_path" 2>/dev/null | wc -l | tr -d '[:space:]')
271
291
 
272
- # Count pending: empty status or "pending"
273
- # Handle potential Windows line endings by stripping carriage returns
274
- pending_count=$(awk -F, 'NR>1 {gsub(/\r/, "", $5); if($5=="" || $5=="pending") count++} END {print count+0}' "$csv_path")
292
+ # Count pending using same logic as find_next_pending_with_lock
293
+ # This includes rows with <5 fields AND rows with empty/pending status
294
+ pending_count=$("$PYTHON_CMD" -c "
295
+ import csv
296
+ import sys
297
+
298
+ pending_count = 0
299
+ with open('$csv_path', 'r') as f:
300
+ reader = csv.reader(f)
301
+ rows = list(reader)
302
+
303
+ for i in range(1, len(rows)):
304
+ # Same logic as find_next_pending_with_lock
305
+ if len(rows[i]) < 5:
306
+ pending_count += 1
307
+ elif len(rows[i]) >= 5 and (rows[i][4] == 'pending' or rows[i][4] == ''):
308
+ pending_count += 1
309
+
310
+ print(pending_count)
311
+ ")
275
312
 
276
313
  echo "$total_rows $complete_count $pending_count"
277
314
  }
@@ -279,6 +316,100 @@ get_csv_stats() {
279
316
  echo "[DISPATCHER] Starting unified evolution engine"
280
317
  echo "[DISPATCHER] Configuration: max_workers=$MAX_WORKERS, timeout=${timeout_seconds:-none}"
281
318
 
319
+ # Validate CSV and clean up stuck statuses
320
+ if [[ -f "$FULL_CSV_PATH" ]]; then
321
+ echo "[DISPATCHER] Validating CSV and cleaning up..."
322
+ if ! "$PYTHON_CMD" -c "
323
+ import csv
324
+ import sys
325
+
326
+ csv_file = '$FULL_CSV_PATH'
327
+
328
+ try:
329
+ # Read CSV - let Python's csv module handle all the complexity
330
+ with open(csv_file, 'r') as f:
331
+ reader = csv.reader(f)
332
+ rows = list(reader)
333
+
334
+ if not rows:
335
+ print('[ERROR] CSV is empty')
336
+ sys.exit(1)
337
+
338
+ # Basic sanity checks
339
+ header = rows[0]
340
+ num_fields = len(header)
341
+
342
+ if len(rows) == 1:
343
+ print('[INFO] CSV has no data rows (only header)')
344
+
345
+ # Clean up any stuck 'running' statuses
346
+ changed = 0
347
+ for i in range(1, len(rows)):
348
+ if len(rows[i]) > 4 and rows[i][4] == 'running':
349
+ rows[i][4] = ''
350
+ changed += 1
351
+
352
+ if changed > 0:
353
+ # Write back
354
+ with open(csv_file + '.tmp', 'w', newline='') as f:
355
+ writer = csv.writer(f)
356
+ writer.writerows(rows)
357
+ import os
358
+ os.rename(csv_file + '.tmp', csv_file)
359
+ print(f'[INFO] Reset {changed} stuck running candidates to pending')
360
+
361
+ # Count pending candidates
362
+ pending = 0
363
+ for i in range(1, len(rows)):
364
+ # Row with < 5 fields or empty/pending status in field 5
365
+ if len(rows[i]) < 5 or (len(rows[i]) >= 5 and rows[i][4] in ['', 'pending']):
366
+ pending += 1
367
+
368
+ print(f'[INFO] CSV loaded: {len(rows)-1} total candidates, {pending} pending')
369
+
370
+ except csv.Error as e:
371
+ print(f'[ERROR] CSV parsing error: {e}')
372
+ print('[ERROR] The CSV file appears to be malformed')
373
+ sys.exit(1)
374
+ except Exception as e:
375
+ print(f'[ERROR] Failed to read CSV: {e}')
376
+ sys.exit(1)
377
+ "; then
378
+ echo "[ERROR] CSV validation failed. Please check the error message above."
379
+ exit 1
380
+ fi
381
+ fi
382
+
383
+ # Automatic cleanup detection - check for unchanged algorithms and warn user
384
+ echo "[DISPATCHER] Checking for duplicate/unchanged algorithms..."
385
+ cleanup_output=$("$SCRIPT_DIR/claude-evolve-cleanup" --dry-run 2>&1)
386
+
387
+ # Check if cleanup found any issues (look for "UNCHANGED:" in output)
388
+ if echo "$cleanup_output" | grep -q "📋 UNCHANGED:"; then
389
+ echo "⚠️ WARNING: Issues detected that may need cleanup:"
390
+ echo "$cleanup_output"
391
+ echo ""
392
+ echo "🔧 RECOMMENDATION: Run 'claude-evolve cleanup --force' to fix these issues before continuing"
393
+ echo " This will delete unchanged algorithms and reset their descendants to pending status"
394
+ echo ""
395
+ echo "⏰ Continuing in 10 seconds (Ctrl+C to abort and run cleanup)..."
396
+
397
+ # Give user time to read and potentially abort
398
+ for i in {10..1}; do
399
+ echo -n " $i..."
400
+ sleep 1
401
+ done
402
+ echo ""
403
+ echo "🚀 Proceeding with evolution run..."
404
+ echo ""
405
+ else
406
+ echo "[DISPATCHER] No cleanup issues detected - proceeding with run"
407
+ fi
408
+
409
+ # Consecutive failure tracking
410
+ consecutive_failures=0
411
+ MAX_CONSECUTIVE_FAILURES=5
412
+
282
413
  # Main dispatch loop
283
414
  while true; do
284
415
  # Clean up finished workers
@@ -337,7 +468,7 @@ while true; do
337
468
  done
338
469
 
339
470
  # Brief pause to avoid busy waiting
340
- sleep 2
471
+ sleep 5
341
472
  done
342
473
 
343
474
  # Clean shutdown