diff --git a/tests/tools/test_session_search.py b/tests/tools/test_session_search.py index 0d7414764..e998a58b8 100644 --- a/tests/tools/test_session_search.py +++ b/tests/tools/test_session_search.py @@ -214,3 +214,61 @@ class TestSessionSearch: # Current session should be skipped, only other_sid should appear assert result["sessions_searched"] == 1 assert current_sid not in [r.get("session_id") for r in result.get("results", [])] + + def test_current_child_session_excludes_parent_lineage(self): + """Compression/delegation parents should be excluded for the active child session.""" + from unittest.mock import MagicMock + from tools.session_search_tool import session_search + + mock_db = MagicMock() + mock_db.search_messages.return_value = [ + {"session_id": "parent_sid", "content": "match", "source": "cli", + "session_started": 1709500000, "model": "test"}, + ] + + def _get_session(session_id): + if session_id == "child_sid": + return {"parent_session_id": "parent_sid"} + if session_id == "parent_sid": + return {"parent_session_id": None} + return None + + mock_db.get_session.side_effect = _get_session + + result = json.loads(session_search( + query="test", db=mock_db, current_session_id="child_sid", + )) + + assert result["success"] is True + assert result["count"] == 0 + assert result["results"] == [] + assert result["sessions_searched"] == 0 + + def test_current_root_session_excludes_child_lineage(self): + """Delegation child hits should be excluded when they resolve to the current root session.""" + from unittest.mock import MagicMock + from tools.session_search_tool import session_search + + mock_db = MagicMock() + mock_db.search_messages.return_value = [ + {"session_id": "child_sid", "content": "match", "source": "cli", + "session_started": 1709500000, "model": "test"}, + ] + + def _get_session(session_id): + if session_id == "root_sid": + return {"parent_session_id": None} + if session_id == "child_sid": + return {"parent_session_id": "root_sid"} + return None + + mock_db.get_session.side_effect = _get_session + + result = json.loads(session_search( + query="test", db=mock_db, current_session_id="root_sid", + )) + + assert result["success"] is True + assert result["count"] == 0 + assert result["results"] == [] + assert result["sessions_searched"] == 0 diff --git a/tools/session_search_tool.py b/tools/session_search_tool.py index 13356ec9f..7f5332c54 100644 --- a/tools/session_search_tool.py +++ b/tools/session_search_tool.py @@ -251,13 +251,20 @@ def session_search( break return sid - # Group by resolved (parent) session_id, dedup, skip current session + current_lineage_root = ( + _resolve_to_parent(current_session_id) if current_session_id else None + ) + + # Group by resolved (parent) session_id, dedup, skip the current + # session lineage. Compression and delegation create child sessions + # that still belong to the same active conversation. seen_sessions = {} for result in raw_results: raw_sid = result["session_id"] resolved_sid = _resolve_to_parent(raw_sid) - # Skip the current session — the agent already has that context - if current_session_id and resolved_sid == current_session_id: + # Skip the current session lineage — the agent already has that + # context, even if older turns live in parent fragments. + if current_lineage_root and resolved_sid == current_lineage_root: continue if current_session_id and raw_sid == current_session_id: continue