claude-evolve 1.9.6 → 1.9.7

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/lib/evolve_run.py CHANGED
@@ -63,12 +63,9 @@ class WorkerPool:
63
63
  cmd.extend(['--timeout', str(self.timeout)])
64
64
 
65
65
  try:
66
- proc = subprocess.Popen(
67
- cmd,
68
- stdout=subprocess.PIPE,
69
- stderr=subprocess.STDOUT,
70
- text=True
71
- )
66
+ # Don't capture output - let it stream directly to terminal
67
+ # This provides real-time visibility into which models are being used
68
+ proc = subprocess.Popen(cmd)
72
69
  self.workers[proc.pid] = proc
73
70
  print(f"[RUN] Spawned worker {proc.pid}", file=sys.stderr)
74
71
  return proc.pid
@@ -86,14 +83,6 @@ class WorkerPool:
86
83
  if ret is not None:
87
84
  finished_pids.append(pid)
88
85
  exit_codes.append(ret)
89
-
90
- # Log output
91
- if proc.stdout:
92
- output = proc.stdout.read()
93
- if output:
94
- for line in output.strip().split('\n'):
95
- print(f"[WORKER-{pid}] {line}", file=sys.stderr)
96
-
97
86
  print(f"[RUN] Worker {pid} exited with code {ret}", file=sys.stderr)
98
87
 
99
88
  for pid in finished_pids:
@@ -31,7 +31,7 @@ SCRIPT_DIR = Path(__file__).parent
31
31
  sys.path.insert(0, str(SCRIPT_DIR.parent))
32
32
 
33
33
  from lib.evolution_csv import EvolutionCSV
34
- from lib.ai_cli import call_ai, get_git_protection_warning, AIError, RateLimitError, APIExhaustedError, TimeoutError
34
+ from lib.ai_cli import call_ai_with_backoff, get_git_protection_warning, AIError
35
35
 
36
36
 
37
37
  @dataclass
@@ -46,8 +46,11 @@ class Config:
46
46
  python_cmd: str = "python3"
47
47
  memory_limit_mb: int = 0
48
48
  timeout_seconds: int = 600
49
- max_ai_retries: int = 3
50
49
  max_candidates: int = 5
50
+ # Retry configuration with exponential backoff
51
+ max_rounds: int = 10
52
+ initial_wait: int = 60
53
+ max_wait: int = 600
51
54
 
52
55
 
53
56
  @dataclass
@@ -139,52 +142,42 @@ This is especially important for models with smaller context windows (like GLM).
139
142
 
140
143
  CRITICAL: If you do not know how to implement what was asked for, or if the requested change is unclear or not feasible, you MUST refuse to make any changes. DO NOT modify the code if you are uncertain about the implementation. Simply respond that you cannot implement the requested change and explain why. It is better to refuse than to make incorrect or random changes."""
141
144
 
142
- def _call_ai_with_retries(self, prompt: str, target_file: Path, source_file: Path) -> Tuple[bool, str]:
145
+ def _call_ai_with_backoff(self, prompt: str, target_file: Path) -> Tuple[bool, str]:
143
146
  """
144
- Call AI with retries.
147
+ Call AI with round-based retry and exponential backoff.
148
+
149
+ AIDEV-NOTE: Uses call_ai_with_backoff which tries all models in the pool,
150
+ then waits with exponential backoff if all fail, and repeats.
145
151
 
146
152
  Returns:
147
153
  Tuple of (success, model_name)
148
154
  """
149
- for attempt in range(1, self.config.max_ai_retries + 1):
150
- print(f"[WORKER-{os.getpid()}] AI attempt {attempt}/{self.config.max_ai_retries}", file=sys.stderr)
155
+ # Get file hash before AI call
156
+ hash_before = self._file_hash(target_file) if target_file.exists() else None
151
157
 
152
- # Re-copy source if this is a retry
153
- if attempt > 1:
154
- print(f"[WORKER-{os.getpid()}] Re-copying source file for retry", file=sys.stderr)
155
- shutil.copy(source_file, target_file)
158
+ try:
159
+ output, model = call_ai_with_backoff(
160
+ prompt,
161
+ command="run",
162
+ working_dir=self.config.evolution_dir,
163
+ max_rounds=self.config.max_rounds,
164
+ initial_wait=self.config.initial_wait,
165
+ max_wait=self.config.max_wait
166
+ )
156
167
 
157
- # Get file hash before AI call
158
- hash_before = self._file_hash(target_file) if target_file.exists() else None
168
+ # Check if file was modified
169
+ hash_after = self._file_hash(target_file) if target_file.exists() else None
159
170
 
160
- try:
161
- output, model = call_ai(prompt, command="run", working_dir=self.config.evolution_dir)
162
-
163
- # Check if file was modified
164
- hash_after = self._file_hash(target_file) if target_file.exists() else None
165
-
166
- if hash_before != hash_after and hash_after is not None:
167
- print(f"[WORKER-{os.getpid()}] AI successfully modified file (model: {model})", file=sys.stderr)
168
- return True, model
169
- else:
170
- print(f"[WORKER-{os.getpid()}] AI did not modify file", file=sys.stderr)
171
-
172
- except RateLimitError as e:
173
- print(f"[WORKER-{os.getpid()}] Rate limit: {e}", file=sys.stderr)
174
- raise # Propagate to caller
175
- except APIExhaustedError as e:
176
- print(f"[WORKER-{os.getpid()}] API exhausted: {e}", file=sys.stderr)
177
- raise # Propagate to caller
178
- except TimeoutError as e:
179
- print(f"[WORKER-{os.getpid()}] Timeout: {e}", file=sys.stderr)
180
- except AIError as e:
181
- print(f"[WORKER-{os.getpid()}] AI error: {e}", file=sys.stderr)
182
-
183
- if attempt < self.config.max_ai_retries:
184
- print(f"[WORKER-{os.getpid()}] Will retry with different model...", file=sys.stderr)
185
- time.sleep(2)
186
-
187
- return False, ""
171
+ if hash_before != hash_after and hash_after is not None:
172
+ print(f"[WORKER-{os.getpid()}] AI successfully modified file (model: {model})", file=sys.stderr)
173
+ return True, model
174
+ else:
175
+ print(f"[WORKER-{os.getpid()}] AI completed but did not modify file", file=sys.stderr)
176
+ return False, model
177
+
178
+ except AIError as e:
179
+ print(f"[WORKER-{os.getpid()}] All AI retries exhausted: {e}", file=sys.stderr)
180
+ return False, ""
188
181
 
189
182
  def _file_hash(self, path: Path) -> Optional[str]:
190
183
  """Get file hash."""
@@ -329,33 +322,19 @@ CRITICAL: If you do not know how to implement what was asked for, or if the requ
329
322
  print(f"[WORKER-{os.getpid()}] Copying {source_file} to {target_file}", file=sys.stderr)
330
323
  shutil.copy(source_file, target_file)
331
324
 
332
- # Call AI to modify
325
+ # Call AI to modify (uses round-based retry with backoff)
333
326
  prompt = self._build_prompt(candidate, target_file.name)
327
+ success, model = self._call_ai_with_backoff(prompt, target_file)
334
328
 
335
- try:
336
- success, model = self._call_ai_with_retries(prompt, target_file, source_file)
337
-
338
- if not success:
339
- print(f"[WORKER-{os.getpid()}] AI failed after all retries", file=sys.stderr)
340
- target_file.unlink(missing_ok=True)
341
- return 77 # AI generation failed
342
-
343
- # Record model used
344
- if model:
345
- with EvolutionCSV(self.config.csv_path) as csv:
346
- csv.update_candidate_field(candidate.id, 'run-LLM', model)
347
-
348
- except RateLimitError:
329
+ if not success:
330
+ print(f"[WORKER-{os.getpid()}] AI failed after all retries", file=sys.stderr)
349
331
  target_file.unlink(missing_ok=True)
350
- with EvolutionCSV(self.config.csv_path) as csv:
351
- csv.update_candidate_status(candidate.id, 'pending')
352
- return 2 # Rate limit
332
+ return 77 # AI generation failed
353
333
 
354
- except APIExhaustedError:
355
- target_file.unlink(missing_ok=True)
334
+ # Record model used
335
+ if model:
356
336
  with EvolutionCSV(self.config.csv_path) as csv:
357
- csv.update_candidate_status(candidate.id, 'pending')
358
- return 3 # API exhausted
337
+ csv.update_candidate_field(candidate.id, 'run-LLM', model)
359
338
 
360
339
  # Check syntax
361
340
  if not self._check_syntax(target_file):
@@ -475,6 +454,8 @@ def load_config_from_yaml(config_path: Optional[str] = None) -> Config:
475
454
  p = base_dir / p
476
455
  return str(p.resolve())
477
456
 
457
+ ideation = data.get('ideation', {})
458
+
478
459
  return Config(
479
460
  csv_path=resolve(data.get('csv_file', 'evolution.csv')),
480
461
  evolution_dir=str(base_dir.resolve()),
@@ -485,8 +466,10 @@ def load_config_from_yaml(config_path: Optional[str] = None) -> Config:
485
466
  python_cmd=data.get('python_cmd', 'python3'),
486
467
  memory_limit_mb=data.get('memory_limit_mb', 0),
487
468
  timeout_seconds=data.get('timeout_seconds', 600),
488
- max_ai_retries=data.get('max_retries', 3),
489
- max_candidates=data.get('worker_max_candidates', 5)
469
+ max_candidates=data.get('worker_max_candidates', 5),
470
+ max_rounds=ideation.get('max_rounds', 10),
471
+ initial_wait=ideation.get('initial_wait', 60),
472
+ max_wait=ideation.get('max_wait', 600)
490
473
  )
491
474
 
492
475
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-evolve",
3
- "version": "1.9.6",
3
+ "version": "1.9.7",
4
4
  "bin": {
5
5
  "claude-evolve": "bin/claude-evolve",
6
6
  "claude-evolve-main": "bin/claude-evolve-main",