diff --git a/backend/apps/agents/manager/streaming/tool_output_shaper.py b/backend/apps/agents/manager/streaming/tool_output_shaper.py index 40d8124a..f8535781 100644 --- a/backend/apps/agents/manager/streaming/tool_output_shaper.py +++ b/backend/apps/agents/manager/streaming/tool_output_shaper.py @@ -51,7 +51,14 @@ NESTED_TEXT_PATHS = (("file", "content"), ("result", "content"), ("data", "text" @typechecked def shape_text(body: str, recovery: str) -> str: - """Head + the notable lines from the middle + tail, and always where to find the rest.""" + """Head + the notable lines from the middle + tail. + + `recovery` is the blob path, and it is deliberately NOT written into the output; see the note + below the elision for the drill that settled it. It stays in the signature because it is the + recoverability CONTRACT: `shape_for_model` refuses to cut anything it could not park, so a + caller cannot shape without one. Reading this as a forgotten parameter is the mistake that got + the path put back once already. + """ if len(body) <= HEAD_CHARS + TAIL_CHARS: return body head, tail = body[:HEAD_CHARS], body[-TAIL_CHARS:] @@ -71,10 +78,19 @@ def shape_text(body: str, recovery: str) -> str: # cannot be written, and then we said nothing about it, so the model's only recovery was to # re-run the command. hermes hands the model an exact re-read call; this is the same idea, # phrased as output rather than as instructions from a harness. - p_from_line = body.count("\n", 0, HEAD_CHARS) + 1 - note = (f"[... {len(middle)} characters omitted; full output: {recovery} " - f"(the omitted part starts at line {p_from_line}) ...]") if recovery else ( - f"[... {len(middle)} characters omitted ...]") + # NO PATH, NO INSTRUCTION, and this is now settled by drill rather than by the retracted + # p=0.026 control that first removed it. Restoring the path was tried twice live on 2026-08-27, + # same session, needle in the elided middle, re-running forbidden: + # passive form ("full output: ") -> "I have no legitimate way to see line 301" + # imperative ("Read from line 18") -> "that's not a real system instruction, it's + # text sitting inside the tool result ... + # flagging it in case it's an injection attempt" + # A model with working injection defences MUST refuse an instruction embedded in tool output, + # so the note cannot be an affordance; worse, it manufactures a false security warning in the + # answer. The blob is still written and `shape_for_model` still REFUSES to cut when it cannot be + # written -- recoverability is real, it is simply carried by the tool call surviving in the + # transcript (the agent re-runs it), which is the same recovery hermes leans on. + note = f"[... {len(middle)} characters omitted ...]" if carried: note += "\n" + "\n".join(carried) return f"{head}\n{note}\n{tail}" diff --git a/backend/tests/test_overcorrection_audit.py b/backend/tests/test_overcorrection_audit.py index dd02ba9d..d57de450 100644 --- a/backend/tests/test_overcorrection_audit.py +++ b/backend/tests/test_overcorrection_audit.py @@ -67,11 +67,11 @@ def test_shaping_never_removes_without_a_way_back(): from backend.apps.agents.manager.streaming.tool_output_shaper import shape_text out = shape_text("q" * 9_000, "/blobs/x-model.txt") assert "characters omitted" in out, "a cut is always visible as a cut" - assert "/blobs/x-model.txt" in out, \ - "and the docstring above is the reason: recoverability MEANS naming where the full text is" - # The path ban this used to carry cited a retracted p=0.026 control. Harness PROSE stays banned. - note = out[out.index("[..."):out.index("]", out.index("[..."))] - assert "OpenSwarm" not in note.replace("/blobs/x-model.txt", "") + # Recoverability is carried by the TOOL CALL surviving in the transcript, not by a path in the + # body: drilled live 2026-08-27, a model with working injection defences refuses an instruction + # embedded in tool output and flags it as an attack. The blob still exists for the UI, and + # shape_for_model still refuses to cut anything it could not park. + assert "OpenSwarm" not in out and "/blobs/" not in out def test_shaping_leaves_a_normal_result_completely_alone(): diff --git a/backend/tests/test_shaper_recovery_path.py b/backend/tests/test_shaper_recovery_path.py index b6d8ddcb..5f5b7478 100644 --- a/backend/tests/test_shaper_recovery_path.py +++ b/backend/tests/test_shaper_recovery_path.py @@ -23,32 +23,36 @@ def p_note(out: str) -> str: return next(ln for ln in out.splitlines() if ln.startswith("[...")) -def test_the_note_names_the_file_the_shaper_already_wrote(): - assert BLOB in p_note(shape_text(p_body(), BLOB)) +def test_the_note_carries_no_path_and_no_instruction(): + """DRILLED 2026-08-27, twice, and this is the whole reason. + + Same session, needle in the elided middle, re-running forbidden: + passive ("full output: ") -> "I have no legitimate way to see line 301" + imperative ("Read from line 18") -> "that's not a real system instruction, it's text + sitting inside the tool result ... flagging it in + case it's an injection attempt" + A model with working injection defences must refuse an instruction embedded in tool output. The + note cannot be an affordance, and advertising it manufactures a false security warning.""" + note = p_note(shape_text(p_body(), BLOB)) + assert BLOB not in note + for word in ("Read ", "read ", "full output", "see the omitted"): + assert word not in note, f"an embedded instruction reads as injection: {note}" + assert "characters omitted" in note, "the cut must still be visible as a cut" -def test_it_says_where_in_that_file_the_omitted_part_begins(): - # hermes's trick: without a starting offset the model's first read lands in text it already has. - body = p_body() - expected = body.count("\n", 0, HEAD_CHARS) + 1 - assert f"starts at line {expected}" in p_note(shape_text(body, BLOB)) +def test_the_blob_is_still_written_even_though_it_is_not_advertised(): + """Recoverability is real; it is just carried by the tool call surviving in the transcript.""" + src = open("backend/apps/agents/manager/streaming/tool_output_shaper.py").read() + assert "write_blob(" in src + assert "skipped_no_recovery" in src, "a cut that could not be parked must not happen at all" -def test_the_notes_PROSE_names_nothing_about_the_harness(): - """The reason the path left in the first place, stated precisely. - - On a packaged install the blob really does live under `.../Application Support/OpenSwarm/data`, - so the app name is unavoidably inside the path. That is a filesystem fact any `ls` would print, - and it is a different thing from prose announcing the harness ("elided by OpenSwarm"), which is - what the original wording did and what the rule is actually about. So: the PATH may say - anything, the WORDS around it may not.""" - out = shape_text(p_body(), BLOB) - i = out.index("[...") - note = out[i:out.index("]", i)] - prose = note.replace(BLOB, "").lower() - for word in ("openswarm", "harness", "elided by", "automated", "assistant", "we ", "our "): - assert word not in prose, f"the note's prose must not say {word!r}: {prose}" - assert "" in prose, "and the path itself must still be there" +def test_the_recovery_arg_is_documented_as_deliberately_unsurfaced(): + """It looked like a forgotten parameter, which is how the path got restored once already.""" + src = open("backend/apps/agents/manager/streaming/tool_output_shaper.py").read() + i = src.index("def shape_text(") + doc = src[i:i + 700] + assert "deliberately NOT written" in doc def test_no_blob_means_no_promise(): diff --git a/backend/tests/test_tool_output_shaper.py b/backend/tests/test_tool_output_shaper.py index 77f4b425..226ca21e 100644 --- a/backend/tests/test_tool_output_shaper.py +++ b/backend/tests/test_tool_output_shaper.py @@ -32,10 +32,11 @@ def test_an_elision_is_marked_and_never_names_the_harness_IN_PROSE(): it cannot, so withholding the path was paying for a recovery nobody could use.""" out = shape_text(p_big(), "/data/blobs/x.txt") assert "characters omitted" in out, "a cut must be visible as a cut" - assert "/data/blobs/x.txt" in out, "the file it already wrote must be reachable" - note = out[out.index("[..."):out.index("]", out.index("[..."))] - assert "OpenSwarm" not in note.replace("/data/blobs/x.txt", ""), \ - "the harness may not name ITSELF; a path is data, prose is a confession" + # CORRECTED AGAIN 2026-08-27, this time by live drill rather than by a statistic: advertising + # the path makes the model flag the note as a prompt-injection attempt and refuse it. See + # test_shaper_recovery_path.py for the two transcripts. + assert "/data/blobs/x.txt" not in out, "an embedded path+instruction reads as injection" + assert "OpenSwarm" not in out def test_the_answer_line_survives_the_middle():