From 0e370241140f93eff57a3d24eb54e18e9258b081 Mon Sep 17 00:00:00 2001 From: chengyongru Date: Mon, 1 Jun 2026 13:44:17 +0800 Subject: [PATCH] fix(session): archive actual idle compact drops --- nanobot/agent/memory.py | 5 ++- tests/agent/test_auto_compact.py | 5 ++- tests/agent/test_consolidator.py | 38 +++++++++++++++++++++ tests/agent/test_session_manager_history.py | 4 +-- 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/nanobot/agent/memory.py b/nanobot/agent/memory.py index ffc9c5f0e..da7a7cb65 100644 --- a/nanobot/agent/memory.py +++ b/nanobot/agent/memory.py @@ -807,10 +807,9 @@ class Consolidator: metadata={}, last_consolidated=0, ) - probe.retain_recent_legal_suffix(max_suffix) + dropped, already_consolidated = probe.retain_recent_legal_suffix(max_suffix) kept = probe.messages - cut = len(tail) - len(kept) - archive_msgs = tail[:cut] + archive_msgs = dropped[already_consolidated:] if not archive_msgs and not kept: session.updated_at = datetime.now() diff --git a/tests/agent/test_auto_compact.py b/tests/agent/test_auto_compact.py index 37fcbfdae..9376289da 100644 --- a/tests/agent/test_auto_compact.py +++ b/tests/agent/test_auto_compact.py @@ -76,10 +76,9 @@ def _make_fake_compact( metadata={}, last_consolidated=0, ) - probe.retain_recent_legal_suffix(max_suffix) + dropped, already_consolidated = probe.retain_recent_legal_suffix(max_suffix) kept = probe.messages - cut = len(tail) - len(kept) - archive_msgs = tail[:cut] + archive_msgs = dropped[already_consolidated:] if not archive_msgs and not kept: session.updated_at = datetime.now() diff --git a/tests/agent/test_consolidator.py b/tests/agent/test_consolidator.py index 1fa05d3c8..071ba9da2 100644 --- a/tests/agent/test_consolidator.py +++ b/tests/agent/test_consolidator.py @@ -440,6 +440,44 @@ class TestCompactIdleSession: assert "u0" not in user_content assert "u25" in user_content or "a25" in user_content + @pytest.mark.asyncio + async def test_non_contiguous_suffix_archives_actual_dropped_messages( + self, + real_consolidator, + mock_provider, + ): + """Assistant-only tails retain a non-contiguous slice, so archive the + actual dropped messages rather than a computed prefix.""" + mock_provider.chat_with_retry.return_value = MagicMock( + content="Tail summary.", finish_reason="stop" + ) + sessions = real_consolidator.sessions + session = sessions.get_or_create("cli:noncontiguous") + for i in range(15): + session.add_message("user", f"user-{i:02d}") + for i in range(10): + session.add_message("assistant", f"assistant-{i:02d}") + sessions.save(session) + + result = await real_consolidator.compact_idle_session("cli:noncontiguous", max_suffix=6) + assert result == "Tail summary." + + reloaded = sessions.get_or_create("cli:noncontiguous") + assert [m["content"] for m in reloaded.messages] == [ + "user-14", + "assistant-00", + "assistant-01", + "assistant-02", + "assistant-03", + "assistant-04", + ] + + archived_call = mock_provider.chat_with_retry.call_args + user_content = archived_call.kwargs["messages"][1]["content"] + assert "user-14" not in user_content + assert "assistant-00" not in user_content + assert "assistant-09" in user_content + @pytest.mark.asyncio async def test_acquires_consolidation_lock(self, real_consolidator, mock_provider): """Verify lock is held during execution.""" diff --git a/tests/agent/test_session_manager_history.py b/tests/agent/test_session_manager_history.py index 449af494a..c4be32172 100644 --- a/tests/agent/test_session_manager_history.py +++ b/tests/agent/test_session_manager_history.py @@ -691,6 +691,6 @@ def test_retain_recent_legal_suffix_last_consolidated_correct_in_else_branch(): # Retained messages start from latest user (u9) + max_messages forward # so retained = [u9, a0..a9][:4] → but these are from original indices 9..12 # Of those, indices 9,10,11 are < 12 (before_lc), so new_lc = 3 - assert session.last_consolidated <= 12 + assert session.last_consolidated == 3 # already_cons should count dropped messages with original index < 12 - assert already_cons >= 0 + assert already_cons == 9