fix(compression): always append END OF CONTEXT SUMMARY marker to standalone summaries regardless of role

When the compression summary lands as an assistant-role message (head ends
with user), the end marker was not appended. Models may regurgitate the
summary text as their own visible output when there's no clear boundary
signal (#33256).

The end marker was already appended for user-role summaries (#11475, #14521)
but the assistant-role path was missed in the original fix. This ensures ALL
standalone summary messages carry the boundary marker, preventing summary
text from leaking into user-visible chat output.
This commit is contained in:
Tranquil-Flow
2026-06-12 15:05:00 -07:00
committed by Teknium
parent a118b94a85
commit 749b7219c4
2 changed files with 48 additions and 4 deletions
+42
View File
@@ -1252,6 +1252,48 @@ class TestCompressWithClient:
"respond to the message below, not the summary above ---"
)
def test_assistant_role_summary_carries_end_marker(self):
"""When the summary lands as standalone role='assistant' (head ends
with user), the message body must include the explicit
'--- END OF CONTEXT SUMMARY ---' marker. Without it, models may
regurgitate the summary text as their own output (#33256).
"""
mock_client = MagicMock()
mock_response = MagicMock()
mock_response.choices = [MagicMock()]
mock_response.choices[0].message.content = "[CONTEXT SUMMARY]: stuff happened"
mock_client.chat.completions.create.return_value = mock_response
with patch("agent.context_compressor.get_model_context_length", return_value=100000):
c = ContextCompressor(model="test", quiet_mode=True, protect_first_n=2, protect_last_n=2)
# head_last=user → summary_role="assistant" (same setup as
# test_summary_role_avoids_consecutive_user_when_head_ends_with_user).
# With min_tail=3, tail = last 3 messages (indices 5-7).
# head_last=user, tail_first=user → the assistant-role summary does
# not collide with either neighbor and should be inserted standalone.
msgs = [
{"role": "system", "content": "system prompt"},
{"role": "user", "content": "msg 1"},
{"role": "user", "content": "msg 2"}, # last head — user
{"role": "assistant", "content": "msg 3"},
{"role": "user", "content": "msg 4"},
{"role": "user", "content": "msg 5"},
{"role": "assistant", "content": "msg 6"},
{"role": "user", "content": "msg 7"},
]
with patch("agent.context_compressor.call_llm", return_value=mock_response):
result = c.compress(msgs)
summary_msg = next(
m for m in result if (m.get("content") or "").startswith(SUMMARY_PREFIX)
)
assert summary_msg["role"] == "assistant"
assert "END OF CONTEXT SUMMARY" in summary_msg["content"]
assert summary_msg["content"].rstrip().endswith(
"respond to the message below, not the summary above ---"
)
def test_summary_role_avoids_consecutive_user_messages(self):
"""Summary role should alternate with the last head message to avoid consecutive same-role messages."""
mock_client = MagicMock()