Skip to content

Commit f9f5cc4

Browse files
jpheinclaude
andcommitted
fix(search): aggressive over-fetch for kind=content/checkpoint
Companion to 398f42f's filter-planner workaround. After moving the kind= exclusion to the post-filter, search results were dominated by CHECKPOINT diary entries because the post-filter doesn't reach far enough into the ranking — top-10 hits for typical content queries on the canonical 151K palace were ALL checkpoints (sims 0.30-0.44), because checkpoints are short word-dense user-prompt snippets that embed strongly to many queries and out-rank the longer substantive content drawers. Without over-fetch, post-filter empties the candidate set entirely: limit=5 → vector returns 5 → 5 checkpoints → post-filter drops 5 → "vector ranked 0" warning even though content drawers exist further down the ranking. Fix: pull max(n_results*20, 100) candidates when kind != "all", so the post-filter has somewhere to find substantive content. The 3x over-fetch for kind="all" stays — no post-filter runs, no need to over-pull. Trade-off: kind=content vector queries fetch ~100 candidates typically. Negligible cost given chromadb HNSW is fast on top-N, and this is the difference between "kind=content returns useful content" vs "kind=content returns empty." Same root cause as the engram-2 critique: vector recall is high (content drawers ARE in the index, findable by query) but checkpoint shape dominates ranking. Over-fetch is the surgical fix; structural fix is to stop indexing checkpoints as searchable drawers (separate session-recovery table) — captured in roadmap, not in this commit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 398f42f commit f9f5cc4

1 file changed

Lines changed: 15 additions & 1 deletion

File tree

mempalace/searcher.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -658,10 +658,24 @@ def search_memories(
658658
# and closet-first routing hides drawers that direct search would find.
659659
warnings: list[str] = []
660660
drawer_results: dict = {"documents": [[]], "metadatas": [[]], "distances": [[]]}
661+
# Over-fetch for re-ranking + post-filter survival.
662+
#
663+
# When kind != "all", _apply_kind_text_filter drops checkpoints
664+
# (or non-checkpoints, for kind="checkpoint") from the candidate
665+
# pool. On a checkpoint-heavy palace, top-N vector hits are
666+
# dominated by CHECKPOINT diary entries (short, word-dense, embed
667+
# strongly) — observed 2026-04-25 on the 151K-drawer canonical
668+
# palace where top-10 hits were all checkpoints for typical
669+
# content queries. Without aggressive over-fetch the post-filter
670+
# empties the result set even when substantive content drawers
671+
# exist further down the ranking. Pull 20× the requested limit
672+
# (capped at 100) when filtering applies; keep the cheaper 3×
673+
# over-fetch for kind="all" where no post-filter runs.
674+
pull_size = max(n_results * 20, 100) if kind != "all" else n_results * 3
661675
try:
662676
dkwargs = {
663677
"query_texts": [query],
664-
"n_results": n_results * 3, # over-fetch for re-ranking
678+
"n_results": pull_size,
665679
"include": ["documents", "metadatas", "distances"],
666680
}
667681
if where:

0 commit comments

Comments
 (0)