tenseleyflow/loader / 847ad5b

Browse files

Shorten repeated chapter retries

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
847ad5bfdd5e1c1ae76d622df00acf3f95dac959
Parents
26a2822
Tree
00515a0

2 changed files

StatusFile+-
M src/loader/runtime/repair.py 83 0
M tests/test_repair.py 7 5
src/loader/runtime/repair.pymodified
@@ -265,6 +265,14 @@ class ResponseRepairer:
265265
         retry_number: int,
266266
         max_empty_retries: int,
267267
     ) -> str:
268
+        if dod is not None:
269
+            minimal_retry_message = self._build_early_concrete_write_retry_message(
270
+                dod,
271
+                retry_number=retry_number,
272
+                max_empty_retries=max_empty_retries,
273
+            )
274
+            if minimal_retry_message is not None:
275
+                return minimal_retry_message
268276
         if dod is not None and self._should_compact_empty_retry_message(dod):
269277
             compact_lines: list[str] = []
270278
             compact_lines.extend(self._compact_planned_artifact_lines(dod))
@@ -368,6 +376,81 @@ class ResponseRepairer:
368376
             ]
369377
         )
370378
 
379
+    def _build_early_concrete_write_retry_message(
380
+        self,
381
+        dod: DefinitionOfDone,
382
+        *,
383
+        retry_number: int,
384
+        max_empty_retries: int,
385
+    ) -> str | None:
386
+        if retry_number < 3:
387
+            return None
388
+        if not self._has_confirmed_output_file_progress(dod):
389
+            return None
390
+        if self._has_confirmed_substantive_output_file_progress(dod):
391
+            return None
392
+
393
+        next_missing_artifact = self._preferred_resume_missing_artifact(dod)
394
+        next_pending = self._preferred_resume_pending_item(
395
+            dod,
396
+            missing_artifact=next_missing_artifact,
397
+        )
398
+        inferred_pending_target = (
399
+            self._infer_pending_item_output_target(dod, next_pending)
400
+            if next_pending
401
+            else None
402
+        )
403
+        concrete_target: Path | None = None
404
+        if inferred_pending_target is not None and not inferred_pending_target.exists():
405
+            concrete_target = inferred_pending_target.expanduser().resolve(strict=False)
406
+        elif next_missing_artifact is not None and not next_missing_artifact[1]:
407
+            concrete_target = next_missing_artifact[0].expanduser().resolve(strict=False)
408
+        if concrete_target is None or not concrete_target.suffix:
409
+            return None
410
+
411
+        outline_label = infer_output_outline_label(
412
+            dod,
413
+            concrete_target,
414
+            project_root=self.context.project_root,
415
+            todo_label=next_pending or "",
416
+        )
417
+        if next_pending and _todo_is_mutation_step(next_pending):
418
+            first_line = (
419
+                f"Continue `{next_pending}` by creating `{concrete_target.name}`."
420
+            )
421
+        else:
422
+            first_line = f"Create `{concrete_target.name}` now."
423
+
424
+        lines = [
425
+            first_line,
426
+            self._mutation_tool_scaffold(concrete_target, tool_name="write"),
427
+        ]
428
+        if outline_label:
429
+            lines.append(
430
+                f"Use the existing outline label `{outline_label}` for that file so it matches the current guide structure."
431
+            )
432
+        if _should_encourage_initial_version(
433
+            target=concrete_target,
434
+            has_confirmed_output_file_progress=True,
435
+            has_confirmed_substantive_output_file_progress=False,
436
+        ):
437
+            lines.append(
438
+                "Write a compact but real initial version of this file now, then refine or expand it in later edits."
439
+            )
440
+        lines.append(
441
+            "No narration, no TodoWrite, no rereads, and no empty response; emit the mutation tool call now."
442
+        )
443
+        return "\n".join(
444
+            [
445
+                "[EMPTY ASSISTANT RESPONSE]",
446
+                (
447
+                    "Your last response was empty "
448
+                    f"(retry {retry_number}/{max_empty_retries}). Emit the exact next mutation now."
449
+                ),
450
+                *[f"- {line}" for line in lines],
451
+            ]
452
+        )
453
+
371454
     def _payload_retry_lines(self, dod: DefinitionOfDone | None) -> list[str]:
372455
         recovery_context = self.context.recovery_context
373456
         if recovery_context is None or not recovery_context.attempts:
tests/test_repair.pymodified
@@ -752,7 +752,9 @@ def test_empty_response_retry_budget_extends_further_after_first_output_file_exi
752752
     assert decision.should_continue is True
753753
     assert decision.retry_message is not None
754754
     assert "retry 5/6" in decision.retry_message
755
-    assert "01-introduction.html" in decision.retry_message
755
+    assert "Continue `Create 01-introduction.html` by creating `01-introduction.html`." in decision.retry_message
756
+    assert 'Emit this tool shape now: `write(file_path="' in decision.retry_message
757
+    assert "No narration, no TodoWrite, no rereads, and no empty response" in decision.retry_message
756758
 
757759
 
758760
 def test_empty_response_retry_uses_compact_prompt_after_substantial_progress(
@@ -1078,20 +1080,20 @@ def test_empty_response_retry_uses_concrete_file_language_for_aggregate_chapter_
10781080
 
10791081
     assert decision.should_continue is True
10801082
     assert decision.retry_message is not None
1081
-    assert "Next missing planned artifact: `01-introduction.html`" in decision.retry_message
1083
+    assert "Next missing planned artifact:" not in decision.retry_message
10821084
     assert (
1083
-        "Resume with this exact next step: continue `Create chapter files with content and structure` "
1084
-        "by creating `01-introduction.html`."
1085
+        "Continue `Create chapter files with content and structure` by creating `01-introduction.html`."
10851086
         in decision.retry_message
10861087
     )
10871088
     assert (
1088
-        "It is the next concrete output needed to continue `Create chapter files with content and structure`."
1089
+        'Emit this tool shape now: `write(file_path="'
10891090
         in decision.retry_message
10901091
     )
10911092
     assert (
10921093
         "Write a compact but real initial version of this file now, then refine or expand it in later edits."
10931094
         in decision.retry_message
10941095
     )
1096
+    assert "No narration, no TodoWrite, no rereads, and no empty response" in decision.retry_message
10951097
     assert "Follow the same full-payload one-file-at-a-time write pattern" not in decision.retry_message
10961098
     assert "Remaining planned artifacts:" not in decision.retry_message
10971099
     assert "Next pending item:" not in decision.retry_message