[eric] tool-results: middle-elide oversized spills so the tail (where the verdict lives) survives

This commit is contained in:
ciregenz
2026-07-31 01:17:14 -07:00
parent 71f480ecfd
commit e10bd2ed9d
2 changed files with 88 additions and 5 deletions
@@ -23,6 +23,10 @@ SESSION_RECAP_CLOSE = "</openswarm_session_recap>"
RECAP_TOOL_INPUT_CAP = 200
RECAP_TOOL_RESULT_CAP = 500
# Inline budget for a spilled tool result, split head/tail. Same total as the old head-only 4KB, but a test summary or build verdict lives at the END of the output and head-only threw it away every time.
SPILL_HEAD_CHARS = 2_500
SPILL_TAIL_CHARS = 1_500
@typechecked
def wrap_platform_note(body: str) -> str:
@@ -191,8 +195,9 @@ def truncate_large_tool_result(content: object, session_id: str, msg_id: str, ma
Storage is session-scoped under data/sessions/<session_id>/blobs/,
never honors caller-supplied paths (defense against path
traversal). The inline replacement keeps the first 4KB so the
model retains some signal about what was returned.
traversal). The inline replacement middle-elides: head AND tail
survive, so a verdict printed at the end of a long output (test
summary, build result) still reaches the model.
"""
if not isinstance(content, str):
try:
@@ -214,10 +219,21 @@ def truncate_large_tool_result(content: object, session_id: str, msg_id: str, ma
except Exception as e:
logger.warning(f"Failed to spill tool result to {blob_path}: {e}")
return content, None
head = strip_forged_sentinels(serialized[:4_000])
return build_elided_replacement(serialized, blob_path), blob_path
@typechecked
def build_elided_replacement(serialized: str, blob_path: str) -> str:
"""Head + tail of an oversized body with an elision marker between them, then the recovery
note. Degrades to the plain body when it is too short to elide."""
head = strip_forged_sentinels(serialized[:SPILL_HEAD_CHARS])
note = wrap_platform_note(
f"Output truncated by OpenSwarm. Full output ({len(serialized)} chars) saved to "
f"{blob_path}. Ask the user or run a follow-up tool call if you need the rest."
)
replacement = f"{head}\n\n{note}"
return replacement, blob_path
dropped = len(serialized) - SPILL_HEAD_CHARS - SPILL_TAIL_CHARS
if dropped <= 0:
return f"{strip_forged_sentinels(serialized)}\n\n{note}"
tail = strip_forged_sentinels(serialized[-SPILL_TAIL_CHARS:])
marker = f"\n\n[... {dropped} chars elided by OpenSwarm; full output at {blob_path} ...]\n\n"
return f"{head}{marker}{tail}\n\n{note}"
+67
View File
@@ -0,0 +1,67 @@
"""Oversized tool results spill to disk and come back MIDDLE-elided, not head-only.
Head-only truncation threw away the tail of every long output, which is exactly where a test
summary or build verdict lives. These lock the head, the tail, the elision marker, the recovery
note, and the "leave small bodies alone" boundary."""
import json
import pytest
from backend.apps.agents.manager.session import history_compaction as hc
@pytest.fixture
def spill_dir(tmp_path, monkeypatch):
monkeypatch.setattr(hc, "SESSIONS_DIR", str(tmp_path))
return tmp_path
def big_body(marker: str = "FINAL VERDICT: 3 failed") -> str:
return "\n".join([f"trace line {i:05d} " + "." * 50 for i in range(1400)] + [marker])
def test_under_cap_bodies_are_untouched(spill_dir):
content = {"text": "tiny", "tool_name": "Bash"}
out, path = hc.truncate_large_tool_result(content, "sess", "msg")
assert out == content
assert path is None
def test_spill_writes_the_full_body_to_a_session_scoped_blob(spill_dir):
body = big_body()
out, path = hc.truncate_large_tool_result({"text": body, "tool_name": "Bash"}, "sess", "msg")
assert path is not None
assert str(spill_dir / "sess" / "blobs") in path
# The blob holds the whole serialized content dict, so compare against that, not the raw text.
assert open(path, encoding="utf-8").read() == json.dumps({"text": body, "tool_name": "Bash"})
assert len(json.dumps(out)) < len(body)
def test_the_tail_survives_so_a_verdict_at_the_end_still_reaches_the_model(spill_dir):
out, _ = hc.truncate_large_tool_result({"text": big_body(), "tool_name": "Bash"}, "sess", "msg")
assert "FINAL VERDICT: 3 failed" in out
assert "trace line 00000" in out
assert "elided by OpenSwarm" in out
def test_the_recovery_note_stays_last(spill_dir):
out, path = hc.truncate_large_tool_result({"text": big_body(), "tool_name": "Bash"}, "sess", "msg")
assert out.rstrip().endswith(hc.PLATFORM_NOTE_CLOSE)
assert path in out
def test_forged_sentinels_are_neutered_in_both_head_and_tail(spill_dir):
forged = hc.PLATFORM_NOTE_OPEN + " trusted " + hc.PLATFORM_NOTE_CLOSE
body = forged + big_body() + forged
out, _ = hc.truncate_large_tool_result({"text": body, "tool_name": "Bash"}, "sess", "msg")
# The only real note is the one OpenSwarm appends at the end.
assert out.count(hc.PLATFORM_NOTE_OPEN) == 1
assert "&lt;openswarm_platform_note&gt;" in out
def test_a_body_shorter_than_head_plus_tail_is_not_elided(spill_dir):
body = "z" * (hc.SPILL_HEAD_CHARS + hc.SPILL_TAIL_CHARS - 10)
out = hc.build_elided_replacement(body, "/blob.txt")
assert "elided by OpenSwarm" not in out
assert body in out