|
10 | 10 | from typing import Any, Dict, List, Optional, Tuple |
11 | 11 |
|
12 | 12 | from agents import Agent, Runner |
| 13 | +from agents.exceptions import MaxTurnsExceeded |
13 | 14 | from rich import print |
14 | 15 |
|
15 | 16 | from .budget_checker import BudgetChecker |
@@ -48,6 +49,7 @@ class Coder: |
48 | 49 | STEP_TEXT_CONTEXT_LIMIT = 4000 |
49 | 50 | STEP_TABLE_CONTEXT_LIMIT = 5000 |
50 | 51 | STEP_TODO_CONTEXT_LIMIT = 3000 |
| 52 | + OPENAI_CODER_MAX_TURNS = 24 |
51 | 53 |
|
52 | 54 | def __init__( |
53 | 55 | self, |
@@ -295,7 +297,17 @@ def run( |
295 | 297 | self.setup_agent() |
296 | 298 |
|
297 | 299 | # Run experiments |
298 | | - success = self._run_experiment_loop(idea, baseline_results) |
| 300 | + recovery_note: Optional[str] = None |
| 301 | + try: |
| 302 | + success = self._run_experiment_loop(idea, baseline_results) |
| 303 | + except MaxTurnsExceeded: |
| 304 | + success, recovery_note = self._recover_from_codegen_interruption( |
| 305 | + idea, |
| 306 | + interruption_reason=( |
| 307 | + "Code generation hit the agent max-turn limit; " |
| 308 | + "using the current workspace state." |
| 309 | + ), |
| 310 | + ) |
299 | 311 |
|
300 | 312 | if not success: |
301 | 313 | # Even if failed, save an empty result file to avoid breaking writer |
@@ -326,7 +338,42 @@ def run( |
326 | 338 |
|
327 | 339 | self.cost_tracker.report("Coder Total Cost") |
328 | 340 |
|
329 | | - return True, self.output_dir, None |
| 341 | + return True, self.output_dir, recovery_note |
| 342 | + |
| 343 | + def _recover_from_codegen_interruption( |
| 344 | + self, |
| 345 | + idea: Dict[str, Any], |
| 346 | + interruption_reason: str, |
| 347 | + ) -> Tuple[bool, Optional[str]]: |
| 348 | + """Try to continue from the current workspace when agent coding is interrupted.""" |
| 349 | + main_path = self._entrypoint_path() |
| 350 | + if not osp.exists(main_path): |
| 351 | + print("[System] Code generation stopped before main.py was created.") |
| 352 | + return False, None |
| 353 | + |
| 354 | + experiment_table = str(idea.get("ExperimentTable", "")).strip() |
| 355 | + if not experiment_table: |
| 356 | + print("[System] Cannot recover coder run without ExperimentTable.") |
| 357 | + return False, None |
| 358 | + |
| 359 | + table_rows = self._extract_table_rows(experiment_table) |
| 360 | + print( |
| 361 | + "[System] Code generation stopped early. " |
| 362 | + "Attempting to run the current workspace and continue with partial results..." |
| 363 | + ) |
| 364 | + return_code, message = self._run_single_experiment( |
| 365 | + run_num=1, |
| 366 | + idea=idea, |
| 367 | + experiment_table=experiment_table, |
| 368 | + table_rows=table_rows, |
| 369 | + ) |
| 370 | + if return_code != 0: |
| 371 | + print("[System] Recovery run failed after code generation interruption.") |
| 372 | + self._print_run_summary(success=False, error_message=message) |
| 373 | + return False, None |
| 374 | + |
| 375 | + self._print_run_summary(success=True) |
| 376 | + return True, f"{interruption_reason} Recovered by running the current workspace." |
330 | 377 |
|
331 | 378 | def _format_experiment_for_prompt( |
332 | 379 | self, exp: Dict[str, Any] |
@@ -716,7 +763,11 @@ def _generate_experiment(self, prompt: str) -> str: |
716 | 763 |
|
717 | 764 | if self.agent is None: |
718 | 765 | raise RuntimeError("Agent not initialized. Call setup_agent() first.") |
719 | | - result = Runner.run_sync(self.agent, task_prompt) |
| 766 | + result = Runner.run_sync( |
| 767 | + self.agent, |
| 768 | + task_prompt, |
| 769 | + max_turns=self.OPENAI_CODER_MAX_TURNS, |
| 770 | + ) |
720 | 771 | track_sdk_cost(result, self.cost_tracker, self.model, "generate_experiment") |
721 | 772 | return result.final_output or "CONTINUE" |
722 | 773 |
|
|
0 commit comments