tenseleyflow/loader / 7302f96

Browse files

Ease first-file empty retries

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
7302f96b5cb0ebf12edb0770a6b22f4da5658f2e
Parents
e82c3e2
Tree
5b37320

4 changed files

StatusFile+-
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 = {
3333
     "Complete the requested work",
3434
     "Collect verification evidence",
3535
 }
36
+_FIRST_FILE_EMPTY_RETRY_EXTRA = 2
3637
 _LATE_STAGE_EMPTY_RETRY_EXTRA = 2
3738
 _MULTI_FILE_OUTPUT_EMPTY_RETRY_EXTRA = 2
3839
 _WORKING_NOTE_TOOL_NAMES = (
@@ -502,6 +503,8 @@ class ResponseRepairer:
502503
             extra_retries = _LATE_STAGE_EMPTY_RETRY_EXTRA
503504
             if self._has_confirmed_output_file_progress(dod):
504505
                 extra_retries += _MULTI_FILE_OUTPUT_EMPTY_RETRY_EXTRA
506
+            elif completed_artifacts > 0:
507
+                extra_retries += _FIRST_FILE_EMPTY_RETRY_EXTRA
505508
             return base_max_empty_retries + extra_retries
506509
         return base_max_empty_retries
507510
 
@@ -691,6 +694,7 @@ class ResponseRepairer:
691694
         retry_number: int,
692695
     ) -> list[str]:
693696
         completed_artifacts, _ = self._planned_artifact_counts(dod)
697
+        has_confirmed_output_file_progress = self._has_confirmed_output_file_progress(dod)
694698
         next_missing_artifact = self._preferred_resume_missing_artifact(dod)
695699
         next_pending = self._preferred_resume_pending_item(
696700
             dod,
@@ -763,7 +767,13 @@ class ResponseRepairer:
763767
                 lines.append(
764768
                     f"Use the existing outline label `{outline_label}` for that file so it matches the current guide structure."
765769
                 )
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:
767777
                 lines.append(
768778
                     "Follow the same full-payload one-file-at-a-time write pattern that "
769779
                     "already created the confirmed output files."
@@ -818,7 +828,13 @@ class ResponseRepairer:
818828
                 lines.append(
819829
                     f"Use the existing outline label `{outline_label}` for that file so it matches the current guide structure."
820830
                 )
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:
822838
                 lines.append(
823839
                     "Follow the same full-payload one-file-at-a-time write pattern that "
824840
                     "already created the confirmed output files."
@@ -900,6 +916,12 @@ class ResponseRepairer:
900916
                         lines.append(
901917
                             f"Use the existing outline label `{outline_label}` for that file so it matches the current guide structure."
902918
                         )
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
+                        )
903925
                     if not next_output_file.parent.exists():
904926
                         lines.append(
905927
                             "The `write` tool can create that file's parent directories "
@@ -948,6 +970,12 @@ class ResponseRepairer:
948970
                         "automatically, so do the write in one step instead of stopping "
949971
                         "for a separate mkdir."
950972
                     )
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
+                    )
951979
                 lines.append(
952980
                     self._mutation_tool_scaffold(
953981
                         target,
src/loader/runtime/tool_batches.pymodified
@@ -1046,6 +1046,7 @@ class ToolBatchRunner:
10461046
                     missing_artifact,
10471047
                     project_root=self.context.project_root,
10481048
                     messages=list(getattr(self.context.session, "messages", []) or []),
1049
+                    encourage_initial_version=True,
10491050
                 )
10501051
                 if compact_handoff:
10511052
                     self.context.queue_steering_message(
@@ -1191,6 +1192,7 @@ class ToolBatchRunner:
11911192
                     (resume_target, False),
11921193
                     project_root=self.context.project_root,
11931194
                     messages=list(getattr(self.context.session, "messages", []) or []),
1195
+                    encourage_initial_version=True,
11941196
                 )
11951197
                 if compact_resume:
11961198
                     self.context.queue_steering_message(
@@ -1222,6 +1224,7 @@ class ToolBatchRunner:
12221224
                 (resume_target, False),
12231225
                 project_root=self.context.project_root,
12241226
                 messages=session_messages,
1227
+                encourage_initial_version=not has_file_artifact_progress,
12251228
             )
12261229
             if compact_resume:
12271230
                 queue_message(
@@ -1239,6 +1242,7 @@ class ToolBatchRunner:
12391242
                 missing_artifact,
12401243
                 project_root=self.context.project_root,
12411244
                 messages=session_messages,
1245
+                encourage_initial_version=True,
12421246
             )
12431247
             if compact_handoff:
12441248
                 queue_message(
@@ -1381,6 +1385,10 @@ class ToolBatchRunner:
13811385
             )
13821386
             return
13831387
 
1388
+        has_file_artifact_progress = _has_confirmed_file_artifact_progress(
1389
+            dod,
1390
+            project_root=self.context.project_root,
1391
+        )
13841392
         todo_refresh = _todo_refresh_guidance(
13851393
             dod,
13861394
             project_root=self.context.project_root,
@@ -1407,6 +1415,7 @@ class ToolBatchRunner:
14071415
                 (resume_target, False),
14081416
                 project_root=self.context.project_root,
14091417
                 messages=session_messages,
1418
+                encourage_initial_version=not has_file_artifact_progress,
14101419
             )
14111420
             if compact_resume:
14121421
                 self.context.queue_steering_message(
@@ -2006,6 +2015,7 @@ def _compact_missing_artifact_handoff(
20062015
     *,
20072016
     project_root: Path,
20082017
     messages: list[Any] | None = None,
2018
+    encourage_initial_version: bool = False,
20092019
 ) -> str:
20102020
     """Build a shorter first-mutation handoff once the next output target is known."""
20112021
 
@@ -2041,6 +2051,11 @@ def _compact_missing_artifact_handoff(
20412051
             guidance += (
20422052
                 " The `write` tool can create that file's parent directories automatically."
20432053
             )
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
+            )
20442059
         guidance += " Make your next response the concrete mutation tool call itself."
20452060
         return guidance
20462061
 
@@ -2052,6 +2067,11 @@ def _compact_missing_artifact_handoff(
20522067
         guidance += (
20532068
             " The `write` tool can create that file's parent directories automatically."
20542069
         )
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
+        )
20552075
     guidance += " Make your next response the concrete mutation tool call itself."
20562076
     return guidance
20572077
 
tests/test_repair.pymodified
@@ -327,6 +327,10 @@ def test_empty_response_retry_mentions_write_can_create_missing_parent_directori
327327
         f'Emit this tool shape now: `write(file_path="{display_runtime_path(index_path)}", content="...")`.'
328328
         in decision.retry_message
329329
     )
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
+    )
330334
     assert "Do not restart discovery unless one specific missing fact blocks this step." in decision.retry_message
331335
 
332336
 
@@ -445,6 +449,10 @@ def test_empty_response_retry_uses_home_relative_path_for_home_artifacts(
445449
         'Emit this tool shape now: `write(file_path="~/Loader/guides/nginx/index.html", content="...")`.'
446450
         in decision.retry_message
447451
     )
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
+    )
448456
 
449457
 
450458
 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
22462246
     message = persistent_messages[-1]
22472247
     assert "Directory setup is complete." in message
22482248
     assert "Next step: create `index.html`." in message
2249
+    assert "Write a compact but real initial version of that file now" in message
22492250
     assert ephemeral_messages == []
22502251
 
22512252
 
@@ -2467,6 +2468,7 @@ async def test_tool_batch_runner_directory_handoff_uses_home_relative_path(
24672468
     message = persistent_messages[-1]
24682469
     assert "Next step: create `index.html`." in message
24692470
     assert "`~/Loader/guides/nginx/index.html`" in message
2471
+    assert "Write a compact but real initial version of that file now" in message
24702472
 
24712473
 
24722474
 @pytest.mark.asyncio