Merge pull request #6112 from MervinPraison/claude/issue-5051-20261028

fix: exclude compaction summary from session search scoring
This commit is contained in:
praisonai-triage-agent[bot] authored and GitHub committed 2026-10-29 00:25:49 +00:00
commit 2c6bdb9bca
3 files changed
+68 -4

No files matched your search

@@ -57,7 +57,8 @@ class SqliteSessionStore(DefaultSessionStore):
# Bumped whenever ``_flatten`` changes what content lands in the index, so
# a store built by an older release rebuilds its FTS rows once on upgrade.
# v2: archived_messages included (Issue #5031).
INDEX_CONTENT_VERSION = 2
# v3: compaction summaries excluded (Issue #5051).
INDEX_CONTENT_VERSION = 3
def __init__(
self,
@@ -235,11 +236,16 @@ class SqliteSessionStore(DefaultSessionStore):
Includes ``archived_messages`` (turns rolled out of the active window
by ``retention="compact"``) ahead of the active ``messages`` so a
compaction re-index preserves — rather than deletes — the raw turns,
keeping the whole conversation searchable (Issue #5031).
keeping the whole conversation searchable (Issue #5031). Compaction
summaries are skipped: search never scores them, so indexing them
would only spend the candidate allowance on misses (Issue #5051).
"""
parts = []
for msg in (*session.archived_messages, *session.messages):
content = getattr(msg, "content", "")
metadata = getattr(msg, "metadata", None)
if isinstance(metadata, dict) and metadata.get("compaction"):
continue
if content:
parts.append(str(content))
return "\n".join(parts)
@@ -926,7 +932,7 @@ class SqliteSessionStore(DefaultSessionStore):
if not isinstance(msg, dict):
continue
content = str(msg.get("content", ""))
if not content:
if not content or self._is_compaction_summary(msg):
continue
lowered = content.lower()
score = 0.0
@@ -2401,6 +2401,17 @@ class DefaultSessionStore:
snippet = snippet + "…"
return snippet
@staticmethod
def _is_compaction_summary(msg: Dict[str, Any]) -> bool:
"""True for the synthetic ``retention="compact"`` rollup message.
The summary paraphrases archived turns that are scanned verbatim, so
it is kept for context/replay but excluded from scoring — otherwise a
session's rank would depend on how often it was compacted (Issue #5051).
"""
metadata = msg.get("metadata")
return isinstance(metadata, dict) and bool(metadata.get("compaction"))
@staticmethod
def _searchable_messages(data: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Project a persisted session into the list of messages to scan.
@@ -2477,7 +2488,7 @@ class DefaultSessionStore:
if not isinstance(msg, dict):
continue
content = str(msg.get("content", ""))
if not content:
if not content or self._is_compaction_summary(msg):
continue
lowered = content.lower()
score = 0.0
@@ -332,6 +332,53 @@ class TestArchivedRecall:
hits = reopened.search("zx-9271-alpha")
assert [h.session_id for h in hits] == ["old"]
@pytest.mark.parametrize("store_cls", [DefaultSessionStore, SqliteSessionStore])
def test_compaction_summary_not_scored(self, tmp_dir, store_cls):
# Issue #5051: the synthetic summary paraphrases archived turns that are
# already scored verbatim, so compaction count must not change rank.
from praisonaiagents.session.store import SessionData, SessionMessage
store = store_cls(session_dir=tmp_dir)
turn = ("user", "deploy token zx-9271-alpha")
self._seed_with_archive(store, "plain", archived=[turn], active=[])
compacted = SessionData(session_id="compacted")
compacted.archived_messages.append(SessionMessage(role=turn[0], content=turn[1]))
compacted.messages.append(
SessionMessage(
role="system",
content="[Session Summary]\nuser: deploy token zx-9271-alpha",
metadata={"compaction": True, "compacted_count": 1},
)
)
store._save_session(compacted)
hits = {h.session_id: h for h in store.search("zx-9271-alpha")}
assert hits["plain"].score == hits["compacted"].score
hit = hits["compacted"]
anchor = next(m for m in hit.messages if m["index"] == hit.anchor_index)
assert anchor["archived"]
def test_summary_only_matches_do_not_exhaust_candidates(self, tmp_dir):
# Unscored summaries must not be indexed, or they spend the bounded
# candidate allowance and hide a real match (Issue #5051, Greptile P1).
from praisonaiagents.session.store import SessionData, SessionMessage
store = SqliteSessionStore(session_dir=tmp_dir)
for i in range(6):
data = SessionData(session_id=f"compacted-{i}")
data.archived_messages.append(SessionMessage(role="user", content="hello"))
data.messages.append(
SessionMessage(
role="system",
content="[Session Summary]\n" + "pelican " * 20,
metadata={"compaction": True},
)
)
store._save_session(data)
self._seed_with_archive(store, "real", archived=[], active=[("user", "a pelican flew by")])
assert [h.session_id for h in store.search("pelican", limit=1)] == ["real"]
class TestIndexedSessionRoute:
"""Indexed gateway/agent routing lookups (Issue #2956)."""