Extend multi-file retry budget
- SHA
a53d8d87392f3609cce9b492c69548ccae3b4929- Parents
-
a41b947 - Tree
50a2684
a53d8d8
a53d8d87392f3609cce9b492c69548ccae3b4929a41b947
50a2684| Status | File | + | - |
|---|---|---|---|
| M |
src/loader/runtime/repair.py
|
21 | 1 |
| M |
tests/test_repair.py
|
55 | 0 |
src/loader/runtime/repair.pymodified@@ -28,6 +28,7 @@ _SPECIAL_DOD_ITEMS = { | ||
| 28 | 28 | "Collect verification evidence", |
| 29 | 29 | } |
| 30 | 30 | _LATE_STAGE_EMPTY_RETRY_EXTRA = 2 |
| 31 | +_MULTI_FILE_OUTPUT_EMPTY_RETRY_EXTRA = 2 | |
| 31 | 32 | _WORKING_NOTE_TOOL_NAMES = ( |
| 32 | 33 | "notepad_write_working", |
| 33 | 34 | "notepad_append", |
@@ -419,7 +420,10 @@ class ResponseRepairer: | ||
| 419 | 420 | if completed_artifacts >= 3 and missing_artifacts > 0: |
| 420 | 421 | return base_max_empty_retries + _LATE_STAGE_EMPTY_RETRY_EXTRA |
| 421 | 422 | if self._has_concrete_next_output_step(dod): |
| 422 | - return base_max_empty_retries + _LATE_STAGE_EMPTY_RETRY_EXTRA | |
| 423 | + extra_retries = _LATE_STAGE_EMPTY_RETRY_EXTRA | |
| 424 | + if self._has_confirmed_output_file_progress(dod): | |
| 425 | + extra_retries += _MULTI_FILE_OUTPUT_EMPTY_RETRY_EXTRA | |
| 426 | + return base_max_empty_retries + extra_retries | |
| 423 | 427 | return base_max_empty_retries |
| 424 | 428 | |
| 425 | 429 | def _should_compact_empty_retry_message(self, dod: DefinitionOfDone) -> bool: |
@@ -483,6 +487,22 @@ class ResponseRepairer: | ||
| 483 | 487 | ) |
| 484 | 488 | return next_output_file is not None |
| 485 | 489 | |
| 490 | + def _has_confirmed_output_file_progress(self, dod: DefinitionOfDone) -> bool: | |
| 491 | + return any( | |
| 492 | + not expect_directory | |
| 493 | + and planned_artifact_target_satisfied( | |
| 494 | + dod, | |
| 495 | + target=target, | |
| 496 | + expect_directory=False, | |
| 497 | + project_root=self.context.project_root, | |
| 498 | + ) | |
| 499 | + for target, expect_directory in collect_planned_artifact_targets( | |
| 500 | + dod, | |
| 501 | + project_root=self.context.project_root, | |
| 502 | + max_paths=12, | |
| 503 | + ) | |
| 504 | + ) | |
| 505 | + | |
| 486 | 506 | def _planned_artifact_progress_lines(self, dod: DefinitionOfDone) -> list[str]: |
| 487 | 507 | targets = collect_planned_artifact_targets( |
| 488 | 508 | dod, |
tests/test_repair.pymodified@@ -497,6 +497,61 @@ def test_empty_response_retry_budget_extends_when_concrete_next_output_is_known( | ||
| 497 | 497 | ) |
| 498 | 498 | |
| 499 | 499 | |
| 500 | +def test_empty_response_retry_budget_extends_further_after_first_output_file_exists( | |
| 501 | + temp_dir: Path, | |
| 502 | +) -> None: | |
| 503 | + context = build_context( | |
| 504 | + temp_dir=temp_dir, | |
| 505 | + use_react=False, | |
| 506 | + ) | |
| 507 | + repairer = ResponseRepairer(context) | |
| 508 | + | |
| 509 | + guide_root = temp_dir / "guides" / "nginx" | |
| 510 | + chapters = guide_root / "chapters" | |
| 511 | + guide_root.mkdir(parents=True) | |
| 512 | + chapters.mkdir() | |
| 513 | + index_path = guide_root / "index.html" | |
| 514 | + index_path.write_text("<html></html>\n") | |
| 515 | + | |
| 516 | + implementation_plan = temp_dir / "implementation.md" | |
| 517 | + implementation_plan.write_text( | |
| 518 | + "\n".join( | |
| 519 | + [ | |
| 520 | + "# Implementation Plan", | |
| 521 | + "", | |
| 522 | + "## File Changes", | |
| 523 | + f"- `{chapters}/`", | |
| 524 | + f"- `{index_path}`", | |
| 525 | + "", | |
| 526 | + ] | |
| 527 | + ) | |
| 528 | + ) | |
| 529 | + | |
| 530 | + dod = create_definition_of_done("Create a multi-file nginx guide.") | |
| 531 | + dod.implementation_plan = str(implementation_plan) | |
| 532 | + dod.touched_files.append(str(index_path)) | |
| 533 | + dod.completed_items.extend( | |
| 534 | + [ | |
| 535 | + "Create the new nginx guide directory structure", | |
| 536 | + "Develop the main index.html file with proper structure", | |
| 537 | + ] | |
| 538 | + ) | |
| 539 | + dod.pending_items.append("Create 01-introduction.html") | |
| 540 | + | |
| 541 | + decision = repairer.handle_empty_response( | |
| 542 | + task="Create a multi-file nginx guide.", | |
| 543 | + original_task=None, | |
| 544 | + empty_retry_count=5, | |
| 545 | + max_empty_retries=2, | |
| 546 | + dod=dod, | |
| 547 | + ) | |
| 548 | + | |
| 549 | + assert decision.should_continue is True | |
| 550 | + assert decision.retry_message is not None | |
| 551 | + assert "retry 5/6" in decision.retry_message | |
| 552 | + assert "01-introduction.html" in decision.retry_message | |
| 553 | + | |
| 554 | + | |
| 500 | 555 | def test_empty_response_retry_uses_compact_prompt_after_substantial_progress( |
| 501 | 556 | temp_dir: Path, |
| 502 | 557 | ) -> None: |