Ease first-file empty retries
Authored by
mfwolffe <wolffemf@dukes.jmu.edu>
- SHA
7302f96b5cb0ebf12edb0770a6b22f4da5658f2e- Parents
-
e82c3e2 - Tree
5b37320
7302f96
7302f96b5cb0ebf12edb0770a6b22f4da5658f2ee82c3e2
5b37320| Status | File | + | - |
|---|---|---|---|
| M |
src/loader/runtime/repair.py
|
30 | 2 |
| M |
src/loader/runtime/tool_batches.py
|
20 | 0 |
| M |
tests/test_repair.py
|
8 | 0 |
| M |
tests/test_tool_batches.py
|
2 | 0 |
src/loader/runtime/repair.pymodified@@ -33,6 +33,7 @@ _SPECIAL_DOD_ITEMS = { | ||
| 33 | 33 | "Complete the requested work", |
| 34 | 34 | "Collect verification evidence", |
| 35 | 35 | } |
| 36 | +_FIRST_FILE_EMPTY_RETRY_EXTRA = 2 | |
| 36 | 37 | _LATE_STAGE_EMPTY_RETRY_EXTRA = 2 |
| 37 | 38 | _MULTI_FILE_OUTPUT_EMPTY_RETRY_EXTRA = 2 |
| 38 | 39 | _WORKING_NOTE_TOOL_NAMES = ( |
@@ -502,6 +503,8 @@ class ResponseRepairer: | ||
| 502 | 503 | extra_retries = _LATE_STAGE_EMPTY_RETRY_EXTRA |
| 503 | 504 | if self._has_confirmed_output_file_progress(dod): |
| 504 | 505 | extra_retries += _MULTI_FILE_OUTPUT_EMPTY_RETRY_EXTRA |
| 506 | + elif completed_artifacts > 0: | |
| 507 | + extra_retries += _FIRST_FILE_EMPTY_RETRY_EXTRA | |
| 505 | 508 | return base_max_empty_retries + extra_retries |
| 506 | 509 | return base_max_empty_retries |
| 507 | 510 | |
@@ -691,6 +694,7 @@ class ResponseRepairer: | ||
| 691 | 694 | retry_number: int, |
| 692 | 695 | ) -> list[str]: |
| 693 | 696 | completed_artifacts, _ = self._planned_artifact_counts(dod) |
| 697 | + has_confirmed_output_file_progress = self._has_confirmed_output_file_progress(dod) | |
| 694 | 698 | next_missing_artifact = self._preferred_resume_missing_artifact(dod) |
| 695 | 699 | next_pending = self._preferred_resume_pending_item( |
| 696 | 700 | dod, |
@@ -763,7 +767,13 @@ class ResponseRepairer: | ||
| 763 | 767 | lines.append( |
| 764 | 768 | f"Use the existing outline label `{outline_label}` for that file so it matches the current guide structure." |
| 765 | 769 | ) |
| 766 | - if self._has_confirmed_output_file_progress(dod): | |
| 770 | + if not has_confirmed_output_file_progress: | |
| 771 | + lines.append( | |
| 772 | + "Do not wait to perfect the entire multi-file output before this write. " | |
| 773 | + "Write a compact but real initial version of this file now, then refine " | |
| 774 | + "or expand it in later edits." | |
| 775 | + ) | |
| 776 | + if has_confirmed_output_file_progress: | |
| 767 | 777 | lines.append( |
| 768 | 778 | "Follow the same full-payload one-file-at-a-time write pattern that " |
| 769 | 779 | "already created the confirmed output files." |
@@ -818,7 +828,13 @@ class ResponseRepairer: | ||
| 818 | 828 | lines.append( |
| 819 | 829 | f"Use the existing outline label `{outline_label}` for that file so it matches the current guide structure." |
| 820 | 830 | ) |
| 821 | - if self._has_confirmed_output_file_progress(dod): | |
| 831 | + if not has_confirmed_output_file_progress and not inferred_is_directory: | |
| 832 | + lines.append( | |
| 833 | + "Do not wait to perfect the entire multi-file output before this write. " | |
| 834 | + "Write a compact but real initial version of this file now, then refine " | |
| 835 | + "or expand it in later edits." | |
| 836 | + ) | |
| 837 | + if has_confirmed_output_file_progress: | |
| 822 | 838 | lines.append( |
| 823 | 839 | "Follow the same full-payload one-file-at-a-time write pattern that " |
| 824 | 840 | "already created the confirmed output files." |
@@ -900,6 +916,12 @@ class ResponseRepairer: | ||
| 900 | 916 | lines.append( |
| 901 | 917 | f"Use the existing outline label `{outline_label}` for that file so it matches the current guide structure." |
| 902 | 918 | ) |
| 919 | + if not has_confirmed_output_file_progress: | |
| 920 | + lines.append( | |
| 921 | + "Do not wait to perfect the entire multi-file output before this write. " | |
| 922 | + "Write a compact but real initial version of this file now, then refine " | |
| 923 | + "or expand it in later edits." | |
| 924 | + ) | |
| 903 | 925 | if not next_output_file.parent.exists(): |
| 904 | 926 | lines.append( |
| 905 | 927 | "The `write` tool can create that file's parent directories " |
@@ -948,6 +970,12 @@ class ResponseRepairer: | ||
| 948 | 970 | "automatically, so do the write in one step instead of stopping " |
| 949 | 971 | "for a separate mkdir." |
| 950 | 972 | ) |
| 973 | + if not has_confirmed_output_file_progress: | |
| 974 | + lines.append( | |
| 975 | + "Do not wait to perfect the entire multi-file output before this write. " | |
| 976 | + "Write a compact but real initial version of this file now, then refine " | |
| 977 | + "or expand it in later edits." | |
| 978 | + ) | |
| 951 | 979 | lines.append( |
| 952 | 980 | self._mutation_tool_scaffold( |
| 953 | 981 | target, |
src/loader/runtime/tool_batches.pymodified@@ -1046,6 +1046,7 @@ class ToolBatchRunner: | ||
| 1046 | 1046 | missing_artifact, |
| 1047 | 1047 | project_root=self.context.project_root, |
| 1048 | 1048 | messages=list(getattr(self.context.session, "messages", []) or []), |
| 1049 | + encourage_initial_version=True, | |
| 1049 | 1050 | ) |
| 1050 | 1051 | if compact_handoff: |
| 1051 | 1052 | self.context.queue_steering_message( |
@@ -1191,6 +1192,7 @@ class ToolBatchRunner: | ||
| 1191 | 1192 | (resume_target, False), |
| 1192 | 1193 | project_root=self.context.project_root, |
| 1193 | 1194 | messages=list(getattr(self.context.session, "messages", []) or []), |
| 1195 | + encourage_initial_version=True, | |
| 1194 | 1196 | ) |
| 1195 | 1197 | if compact_resume: |
| 1196 | 1198 | self.context.queue_steering_message( |
@@ -1222,6 +1224,7 @@ class ToolBatchRunner: | ||
| 1222 | 1224 | (resume_target, False), |
| 1223 | 1225 | project_root=self.context.project_root, |
| 1224 | 1226 | messages=session_messages, |
| 1227 | + encourage_initial_version=not has_file_artifact_progress, | |
| 1225 | 1228 | ) |
| 1226 | 1229 | if compact_resume: |
| 1227 | 1230 | queue_message( |
@@ -1239,6 +1242,7 @@ class ToolBatchRunner: | ||
| 1239 | 1242 | missing_artifact, |
| 1240 | 1243 | project_root=self.context.project_root, |
| 1241 | 1244 | messages=session_messages, |
| 1245 | + encourage_initial_version=True, | |
| 1242 | 1246 | ) |
| 1243 | 1247 | if compact_handoff: |
| 1244 | 1248 | queue_message( |
@@ -1381,6 +1385,10 @@ class ToolBatchRunner: | ||
| 1381 | 1385 | ) |
| 1382 | 1386 | return |
| 1383 | 1387 | |
| 1388 | + has_file_artifact_progress = _has_confirmed_file_artifact_progress( | |
| 1389 | + dod, | |
| 1390 | + project_root=self.context.project_root, | |
| 1391 | + ) | |
| 1384 | 1392 | todo_refresh = _todo_refresh_guidance( |
| 1385 | 1393 | dod, |
| 1386 | 1394 | project_root=self.context.project_root, |
@@ -1407,6 +1415,7 @@ class ToolBatchRunner: | ||
| 1407 | 1415 | (resume_target, False), |
| 1408 | 1416 | project_root=self.context.project_root, |
| 1409 | 1417 | messages=session_messages, |
| 1418 | + encourage_initial_version=not has_file_artifact_progress, | |
| 1410 | 1419 | ) |
| 1411 | 1420 | if compact_resume: |
| 1412 | 1421 | self.context.queue_steering_message( |
@@ -2006,6 +2015,7 @@ def _compact_missing_artifact_handoff( | ||
| 2006 | 2015 | *, |
| 2007 | 2016 | project_root: Path, |
| 2008 | 2017 | messages: list[Any] | None = None, |
| 2018 | + encourage_initial_version: bool = False, | |
| 2009 | 2019 | ) -> str: |
| 2010 | 2020 | """Build a shorter first-mutation handoff once the next output target is known.""" |
| 2011 | 2021 | |
@@ -2041,6 +2051,11 @@ def _compact_missing_artifact_handoff( | ||
| 2041 | 2051 | guidance += ( |
| 2042 | 2052 | " The `write` tool can create that file's parent directories automatically." |
| 2043 | 2053 | ) |
| 2054 | + if encourage_initial_version: | |
| 2055 | + guidance += ( | |
| 2056 | + " Write a compact but real initial version of that file now; you can expand " | |
| 2057 | + "or refine it in later edits." | |
| 2058 | + ) | |
| 2044 | 2059 | guidance += " Make your next response the concrete mutation tool call itself." |
| 2045 | 2060 | return guidance |
| 2046 | 2061 | |
@@ -2052,6 +2067,11 @@ def _compact_missing_artifact_handoff( | ||
| 2052 | 2067 | guidance += ( |
| 2053 | 2068 | " The `write` tool can create that file's parent directories automatically." |
| 2054 | 2069 | ) |
| 2070 | + if encourage_initial_version: | |
| 2071 | + guidance += ( | |
| 2072 | + " Write a compact but real initial version of that file now; you can expand " | |
| 2073 | + "or refine it in later edits." | |
| 2074 | + ) | |
| 2055 | 2075 | guidance += " Make your next response the concrete mutation tool call itself." |
| 2056 | 2076 | return guidance |
| 2057 | 2077 | |
tests/test_repair.pymodified@@ -327,6 +327,10 @@ def test_empty_response_retry_mentions_write_can_create_missing_parent_directori | ||
| 327 | 327 | f'Emit this tool shape now: `write(file_path="{display_runtime_path(index_path)}", content="...")`.' |
| 328 | 328 | in decision.retry_message |
| 329 | 329 | ) |
| 330 | + assert ( | |
| 331 | + "Write a compact but real initial version of this file now, then refine or expand it in later edits." | |
| 332 | + in decision.retry_message | |
| 333 | + ) | |
| 330 | 334 | assert "Do not restart discovery unless one specific missing fact blocks this step." in decision.retry_message |
| 331 | 335 | |
| 332 | 336 | |
@@ -445,6 +449,10 @@ def test_empty_response_retry_uses_home_relative_path_for_home_artifacts( | ||
| 445 | 449 | 'Emit this tool shape now: `write(file_path="~/Loader/guides/nginx/index.html", content="...")`.' |
| 446 | 450 | in decision.retry_message |
| 447 | 451 | ) |
| 452 | + assert ( | |
| 453 | + "Write a compact but real initial version of this file now, then refine or expand it in later edits." | |
| 454 | + in decision.retry_message | |
| 455 | + ) | |
| 448 | 456 | |
| 449 | 457 | |
| 450 | 458 | def test_empty_response_retry_recovers_blocked_empty_file_path_to_concrete_target( |
tests/test_tool_batches.pymodified@@ -2246,6 +2246,7 @@ async def test_tool_batch_runner_missing_artifact_nudge_names_next_file_after_se | ||
| 2246 | 2246 | message = persistent_messages[-1] |
| 2247 | 2247 | assert "Directory setup is complete." in message |
| 2248 | 2248 | assert "Next step: create `index.html`." in message |
| 2249 | + assert "Write a compact but real initial version of that file now" in message | |
| 2249 | 2250 | assert ephemeral_messages == [] |
| 2250 | 2251 | |
| 2251 | 2252 | |
@@ -2467,6 +2468,7 @@ async def test_tool_batch_runner_directory_handoff_uses_home_relative_path( | ||
| 2467 | 2468 | message = persistent_messages[-1] |
| 2468 | 2469 | assert "Next step: create `index.html`." in message |
| 2469 | 2470 | assert "`~/Loader/guides/nginx/index.html`" in message |
| 2471 | + assert "Write a compact but real initial version of that file now" in message | |
| 2470 | 2472 | |
| 2471 | 2473 | |
| 2472 | 2474 | @pytest.mark.asyncio |