"""Regression tests for blank user echoes displacing actionable compaction state.""" from __future__ import annotations from unittest.mock import patch import pytest from agent.context_compressor import ( COMPRESSED_SUMMARY_METADATA_KEY, SUMMARY_PREFIX, ContextCompressor, ) @pytest.fixture() def compressor() -> ContextCompressor: with patch( "agent.context_compressor.get_model_context_length", return_value=100_000, ): instance = ContextCompressor( model="test/model", threshold_percent=0.85, protect_first_n=2, protect_last_n=2, quiet_mode=True, ) instance.tail_token_budget = 10 return instance def _append_tool_run(messages: list[dict], prefix: str, count: int = 6) -> None: for index in range(count): call_id = f"{prefix}-{index}" messages.extend( [ { "role": "assistant", "content": None, "tool_calls": [ { "id": call_id, "function": {"name": "read_file", "arguments": "{}"}, } ], }, { "role": "tool", "tool_call_id": call_id, "content": "x" * 400, }, ] ) def _compress(compressor: ContextCompressor, messages: list[dict]) -> list[dict]: with patch.object( compressor, "_generate_summary", return_value=f"{SUMMARY_PREFIX}\nsummary of older work", ): return compressor.compress(messages, current_tokens=90_000) def _assert_no_adjacent_user_roles(messages: list[dict]) -> None: for previous, current in zip(messages, messages[1:]): assert (previous.get("role"), current.get("role")) != ("user", "user") def test_leading_blank_without_actionable_user_is_not_removed(compressor): messages = [ {"role": "user", "content": ""}, {"role": "assistant", "content": "visible reply"}, ] assert compressor._find_last_user_message_idx(messages, 0) == -1 assert compressor._blank_echo_indices_after(messages, -1) == set() def test_completion_survives_compaction_verbatim_after_blank_echo(compressor): completion = ( "[ASYNC DELEGATION BATCH COMPLETE — deleg_current]\n" "The newest result that must remain actionable." ) messages: list[dict] = [ {"role": "system", "content": "sys"}, {"role": "user", "content": "initial request"}, {"role": "assistant", "content": "initial reply"}, ] messages += [ {"role": "user", "content": f"older question {index}"} if index % 2 == 0 else {"role": "assistant", "content": f"older reply {index}"} for index in range(6) ] messages += [ {"role": "user", "content": completion}, {"role": "user", "content": " \n"}, {"role": "assistant", "content": "working from the completion"}, ] _append_tool_run(messages, "tail") result = _compress(compressor, messages) completion_rows = [message for message in result if message.get("content") == completion] assert len(completion_rows) == 1 assert not completion_rows[0].get(COMPRESSED_SUMMARY_METADATA_KEY) summary_rows = [ message for message in result if message.get(COMPRESSED_SUMMARY_METADATA_KEY) ] assert len(summary_rows) == 1 assert summary_rows[0].get("role") == "user" assert all(not compressor._is_blank_user_turn(message) for message in result) _assert_no_adjacent_user_roles(result) second_result = _compress(compressor, result) second_completion_rows = [ message for message in second_result if message.get("content") == completion ] assert len(second_completion_rows) == 1 assert not second_completion_rows[0].get(COMPRESSED_SUMMARY_METADATA_KEY) def test_completion_at_compress_start_survives_when_blank_echo_is_compress_end( compressor, ): completion = "latest actionable completion at the compression boundary" messages = [ {"role": "system", "content": "sys"}, {"role": "user", "content": "initial request"}, {"role": "assistant", "content": "initial reply"}, {"role": "user", "content": completion}, {"role": "user", "content": ""}, { "role": "assistant", "content": None, "tool_calls": [ { "id": "boundary-call", "function": {"name": "read_file", "arguments": "{}"}, } ], }, {"role": "tool", "tool_call_id": "boundary-call", "content": "result"}, {"role": "assistant", "content": "working from the completion"}, ] with ( patch.object(compressor, "_protect_head_size", return_value=3), patch.object(compressor, "_find_tail_cut_by_tokens", return_value=3), patch.object(compressor, "_generate_summary") as generate_summary, ): result = compressor.compress(messages, current_tokens=90_000) completion_rows = [message for message in result if message.get("content") == completion] assert len(completion_rows) == 1 assert not completion_rows[0].get(COMPRESSED_SUMMARY_METADATA_KEY) assert not any( message.get(COMPRESSED_SUMMARY_METADATA_KEY) for message in result ) assert len(result) == len(messages) - 1 assert compressor.compression_count == 0 assert compressor._last_compression_savings_pct == 0.0 generate_summary.assert_not_called() assert any(message.get("tool_call_id") == "boundary-call" for message in result) assert result[-1].get("content") == "working from the completion" assert [message.get("role") for message in result] == [ "system", "user", "assistant", "user", "assistant", "tool", "assistant", ] _assert_no_adjacent_user_roles(result)