Nudge missing asset retries
Authored by
mfwolffe <wolffemf@dukes.jmu.edu>
- SHA
3942db8529f158d59a4e8c7d4ecb963aefbfd4a9- Parents
-
35c0bcb - Tree
6653dd8
3942db8
3942db8529f158d59a4e8c7d4ecb963aefbfd4a935c0bcb
6653dd8| Status | File | + | - |
|---|---|---|---|
| M |
src/loader/runtime/tool_batches.py
|
45 | 0 |
| M |
tests/test_tool_batches.py
|
53 | 0 |
src/loader/runtime/tool_batches.pymodified@@ -358,6 +358,10 @@ class ToolBatchRunner: | ||
| 358 | 358 | outcome.event_content, |
| 359 | 359 | dod=dod, |
| 360 | 360 | ) |
| 361 | + self._queue_blocked_html_asset_nudge( | |
| 362 | + tool_call, | |
| 363 | + outcome.event_content, | |
| 364 | + ) | |
| 361 | 365 | self._queue_blocked_active_repair_nudge(outcome.event_content) |
| 362 | 366 | self._queue_blocked_active_repair_mutation_nudge(outcome.event_content) |
| 363 | 367 | self._queue_blocked_completed_artifact_scope_nudge( |
@@ -1348,6 +1352,47 @@ class ToolBatchRunner: | ||
| 1348 | 1352 | + verification_suffix |
| 1349 | 1353 | ) |
| 1350 | 1354 | |
| 1355 | + def _queue_blocked_html_asset_nudge( | |
| 1356 | + self, | |
| 1357 | + tool_call: ToolCall, | |
| 1358 | + event_content: str, | |
| 1359 | + ) -> None: | |
| 1360 | + """Make missing local asset feedback sticky for the immediate retry.""" | |
| 1361 | + | |
| 1362 | + if tool_call.name not in {"write", "edit", "patch"}: | |
| 1363 | + return | |
| 1364 | + if "HTML local asset references do not exist" not in event_content: | |
| 1365 | + return | |
| 1366 | + | |
| 1367 | + target = str( | |
| 1368 | + tool_call.arguments.get("file_path") | |
| 1369 | + or tool_call.arguments.get("path") | |
| 1370 | + or "" | |
| 1371 | + ).strip() | |
| 1372 | + if not target: | |
| 1373 | + return | |
| 1374 | + | |
| 1375 | + missing_assets = _extract_blocked_html_target_list( | |
| 1376 | + event_content, | |
| 1377 | + "Missing local asset href(s):", | |
| 1378 | + ) | |
| 1379 | + asset_preview = "" | |
| 1380 | + if missing_assets: | |
| 1381 | + asset_preview = ( | |
| 1382 | + " Remove or replace " | |
| 1383 | + + ", ".join(f"`{asset}`" for asset in missing_assets[:3]) | |
| 1384 | + + "." | |
| 1385 | + ) | |
| 1386 | + | |
| 1387 | + self.context.queue_steering_message( | |
| 1388 | + f"The last HTML mutation for `{target}` was blocked, so that file was " | |
| 1389 | + "not created or updated. Retry the same file with one concrete " | |
| 1390 | + "`write`, `edit`, or `patch` call that omits missing local asset hrefs." | |
| 1391 | + f"{asset_preview} Inline necessary styling/content, or create the referenced " | |
| 1392 | + "asset before linking it. Do not resend the same `<link>` tag and do " | |
| 1393 | + "not claim completion until the blocked file write succeeds." | |
| 1394 | + ) | |
| 1395 | + | |
| 1351 | 1396 | def _queue_blocked_invalid_mutation_nudge( |
| 1352 | 1397 | self, |
| 1353 | 1398 | tool_call: ToolCall, |
tests/test_tool_batches.pymodified@@ -7781,6 +7781,59 @@ def test_tool_batch_runner_blocked_html_missing_target_after_outputs_exist_prefe | ||
| 7781 | 7781 | assert "Replace broken hrefs with existing local targets or remove the broken link." in queued[0] |
| 7782 | 7782 | |
| 7783 | 7783 | |
| 7784 | +def test_tool_batch_runner_blocked_html_asset_nudge_retries_same_file( | |
| 7785 | + temp_dir: Path, | |
| 7786 | +) -> None: | |
| 7787 | + async def assess_confidence( | |
| 7788 | + tool_name: str, | |
| 7789 | + tool_args: dict, | |
| 7790 | + context: str, | |
| 7791 | + ) -> ConfidenceAssessment: | |
| 7792 | + raise AssertionError("Confidence scoring should not run in this scenario") | |
| 7793 | + | |
| 7794 | + async def verify_action( | |
| 7795 | + tool_name: str, | |
| 7796 | + tool_args: dict, | |
| 7797 | + result: str, | |
| 7798 | + expected: str = "", | |
| 7799 | + ) -> ActionVerification: | |
| 7800 | + raise AssertionError("Verification should not run in this scenario") | |
| 7801 | + | |
| 7802 | + context = build_context( | |
| 7803 | + temp_dir=temp_dir, | |
| 7804 | + messages=[], | |
| 7805 | + safeguards=FakeSafeguards(), | |
| 7806 | + assess_confidence=assess_confidence, | |
| 7807 | + verify_action=verify_action, | |
| 7808 | + ) | |
| 7809 | + queued: list[str] = [] | |
| 7810 | + context.queue_steering_message_callback = queued.append | |
| 7811 | + runner = ToolBatchRunner(context, DefinitionOfDoneStore(temp_dir)) | |
| 7812 | + target = temp_dir / "guide" / "chapters" / "03-configuration.html" | |
| 7813 | + | |
| 7814 | + runner._queue_blocked_html_asset_nudge( | |
| 7815 | + ToolCall( | |
| 7816 | + id="write-config", | |
| 7817 | + name="write", | |
| 7818 | + arguments={"file_path": str(target)}, | |
| 7819 | + ), | |
| 7820 | + ( | |
| 7821 | + "[Blocked - HTML local asset references do not exist] Suggestion: " | |
| 7822 | + "Use only existing local assets for non-HTML href values. " | |
| 7823 | + "Missing local asset href(s): ../styles.css. Remove the asset link, " | |
| 7824 | + "create the referenced asset first, inline the styling/content, or point " | |
| 7825 | + "the href at an existing local file." | |
| 7826 | + ), | |
| 7827 | + ) | |
| 7828 | + | |
| 7829 | + assert queued | |
| 7830 | + assert str(target) in queued[0] | |
| 7831 | + assert "was not created or updated" in queued[0] | |
| 7832 | + assert "Remove or replace `../styles.css`." in queued[0] | |
| 7833 | + assert "Do not resend the same `<link>` tag" in queued[0] | |
| 7834 | + assert "do not claim completion" in queued[0] | |
| 7835 | + | |
| 7836 | + | |
| 7784 | 7837 | @pytest.mark.asyncio |
| 7785 | 7838 | async def test_tool_batch_runner_blocked_empty_file_path_nudges_concrete_next_artifact( |
| 7786 | 7839 | temp_dir: Path, |