fix(compression): ignore assistant handoff summaries in tail anchor

Assistant-role compaction summaries were treated as the last visible assistant reply after head protection decayed. That pulled the tail boundary back to the summary itself and left zero new turns to summarize.

Exclude internal context summaries from both the visible-reply search and the assistant fallback, mirroring the existing user-role summary exclusion.
This commit is contained in:
McHermes 2026-07-21 14:34:07 +00:00 committed by kshitij
parent 9fa2906c18
commit f4896015c1
2 changed files with 39 additions and 2 deletions

View file

@ -2988,7 +2988,13 @@ This compaction should PRIORITISE preserving all information related to the focu
indicators and aren't what the reporter means by "the output
of the last message you sent" (#29824).
Falling back to the most recent assistant message of ANY kind
Context-compaction handoff banners can also carry
``role="assistant"``. They are internal continuity state, not a
user-visible reply, so ignore them both as text-bearing anchors and
as candidates for the fallback below. This mirrors the user-role
summary exclusion in ``_find_last_user_message_idx``.
Falling back to the most recent non-summary assistant message of ANY kind
only kicks in when no content-bearing assistant message exists
in the compressible region typically a fresh session that
just started a multi-step tool sequence with no prior reply
@ -2998,7 +3004,9 @@ This compaction should PRIORITISE preserving all information related to the focu
last_any = -1
for i in range(len(messages) - 1, head_end - 1, -1):
msg = messages[i]
if msg.get("role") != "assistant":
if msg.get("role") != "assistant" or self._is_context_summary_content(
msg.get("content")
):
continue
if last_any < 0:
last_any = i

View file

@ -74,6 +74,35 @@ def compressor():
class TestFindLastAssistantMessageIdx:
def test_skips_assistant_role_context_summary_marker(self, compressor):
"""A persisted assistant-role handoff is internal continuity state,
not the last reply the user saw. Tool-call-only assistant messages
after it must remain eligible for the fallback anchor."""
from agent.context_compressor import SUMMARY_PREFIX
messages = [
{"role": "assistant", "content": f"{SUMMARY_PREFIX}\nold handoff"},
{"role": "user", "content": "continue the task"},
{"role": "assistant", "content": None,
"tool_calls": [{"function": {"name": "t",
"arguments": "{}"}}]},
{"role": "tool", "content": "result", "tool_call_id": "c1"},
]
assert compressor._find_last_assistant_message_idx(
messages, head_end=0
) == 2
def test_all_assistant_messages_are_summaries_returns_minus_one(self, compressor):
from agent.context_compressor import SUMMARY_PREFIX
messages = [
{"role": "assistant", "content": f"{SUMMARY_PREFIX}\nold handoff"},
{"role": "user", "content": "continue the task"},
]
assert compressor._find_last_assistant_message_idx(
messages, head_end=0
) == -1
def test_finds_content_bearing_assistant(self, compressor):
messages = [
{"role": "system", "content": "sys"},