fix: default collection to cosine distance and clamp similarity scores#568
fix: default collection to cosine distance and clamp similarity scores#568arnoldwender wants to merge 3 commits intoMemPalace:developfrom
Conversation
web3guru888
left a comment
There was a problem hiding this comment.
This fixes a real problem we've hit ourselves.
The L2 vs cosine mismatch is subtle but devastating: new palaces silently produce negative similarities, and since the code just displays the number users may not notice something is wrong — they just see low scores and think their queries are bad.
Two things I especially like about this approach:
- **Setting at collection creation in both and ** — fixes the root cause for new palaces in both code paths.
- ** clamp** — correct backwards-compat strategy for existing L2 palaces. A negative clamp to 0 is honest ("not similar") rather than misleading.
One gap worth noting: only applies cosine to but not (the retrieval path). That's correct — you can't change a collection's metric after creation — but users migrating from L2 won't automatically get the fix without running
No palace found at /root/.mempalace/palace. Worth a sentence in the PR description or CHANGELOG noting that existing palaces need
No palace found at /root/.mempalace/palace to adopt cosine.
We hit this exact issue when first deploying — our similarity scores looked fine after repair but were broken on fresh test palaces created via before repair ran. Approved.
…compress keys - Default new collections to cosine distance (hnsw:space=cosine) instead of L2 — fixes negative similarity scores (MemPalace#568) - Filter unexpected MCP tool args before dispatch — prevents TypeError crash from extra params like top_k (MemPalace#572) - Rotate WAL at 10 MB with one backup to prevent unbounded growth (MemPalace#573) - Fix cmd_compress KeyError: align dict keys with compression_stats() return values (MemPalace#569) - Fix spellcheck _load_known_names: read from "people" key, use dict keys as canonical names (MemPalace#570) - Repair command uses cosine distance for rebuilt collection Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
Incorporated into #562 — both the |
web3guru888
left a comment
There was a problem hiding this comment.
Correct, narrow fix. LGTM.
[MemPalace-AGI integration — 215 tests, 710 KG entities]
- Batch upserts (100 docs/call) in both general miner and convo_miner instead of one-at-a-time writes — 3-5x faster on large projects - _prepare_file() separates read/chunk (thread-safe) from write, enabling ThreadPoolExecutor concurrency with --workers flag - bulk_check_mined() pre-fetches all source_file/mtime pairs in one paginated scan instead of per-file ChromaDB queries - Epsilon mtime comparison (abs < 0.01) instead of float == (MemPalace#483) - Cosine distance default for new collections (hnsw:space=cosine) - SKIP_PATTERNS/JUNK_FILE_SIZE filter minified JS, lockfiles, large dumps - detect_room() uses word-boundary regex and exact-match priority - chunk_text() accepts configurable size/overlap/min params - Entity detector STOPWORDS expanded (73 technical terms) - Configurable chunk_size/chunk_overlap/min_chunk_size in palace config Split from MemPalace#562 per maintainer request. Closes MemPalace#483, MemPalace#568. Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Batch upserts (100 docs/call) in both general miner and convo_miner instead of one-at-a-time writes — 3-5x faster on large projects - _prepare_file() separates read/chunk (thread-safe) from write, enabling ThreadPoolExecutor concurrency with --workers flag - bulk_check_mined() pre-fetches all source_file/mtime pairs in one paginated scan instead of per-file ChromaDB queries - Epsilon mtime comparison (abs < 0.01) instead of float == (MemPalace#483) - Cosine distance default for new collections (hnsw:space=cosine) - SKIP_PATTERNS/JUNK_FILE_SIZE filter minified JS, lockfiles, large dumps - detect_room() uses word-boundary regex and exact-match priority - chunk_text() accepts configurable size/overlap/min params - Entity detector STOPWORDS expanded (73 technical terms) - Configurable chunk_size/chunk_overlap/min_chunk_size in palace config Split from MemPalace#562 per maintainer request. Closes MemPalace#483, MemPalace#568. Co-Authored-By: Claude Opus 4.6 <[email protected]>
…mPalace#666) Replace "your memory system" with explicit MemPalace references and tool names (mempalace_diary_write, mempalace_add_drawer, mempalace_kg_add) in stop and precompact hook block reasons. This prevents Claude Code from misinterpreting the hook as a native auto-memory save instruction. Updated in both Python (hooks_cli.py) and standalone shell scripts. Also fix CONTRIBUTING.md Getting Started to show the fork-first workflow, matching the PR Guidelines section.
d68c73c to
99eb38d
Compare
max(0, negative_float) returns int 0 in Python when the L2 distance exceeds 1.0, causing isinstance(score, float) to fail. Replace max(0, ...) with max(0.0, ...) in both clamp sites so the return type is always float regardless of the distance value.
- Batch upserts (100 docs/call) in both general miner and convo_miner instead of one-at-a-time writes — 3-5x faster on large projects - _prepare_file() separates read/chunk (thread-safe) from write, enabling ThreadPoolExecutor concurrency with --workers flag - bulk_check_mined() pre-fetches all source_file/mtime pairs in one paginated scan instead of per-file ChromaDB queries - Epsilon mtime comparison (abs < 0.01) instead of float == (MemPalace#483) - Cosine distance default for new collections (hnsw:space=cosine) - SKIP_PATTERNS/JUNK_FILE_SIZE filter minified JS, lockfiles, large dumps - detect_room() uses word-boundary regex and exact-match priority - chunk_text() accepts configurable size/overlap/min params - Entity detector STOPWORDS expanded (73 technical terms) - Configurable chunk_size/chunk_overlap/min_chunk_size in palace config Split from MemPalace#562 per maintainer request. Closes MemPalace#483, MemPalace#568. Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Batch upserts (100 docs/call) in both general miner and convo_miner instead of one-at-a-time writes — 3-5x faster on large projects - _prepare_file() separates read/chunk (thread-safe) from write, enabling ThreadPoolExecutor concurrency with --workers flag - bulk_check_mined() pre-fetches all source_file/mtime pairs in one paginated scan instead of per-file ChromaDB queries - Epsilon mtime comparison (abs < 0.01) instead of float == (MemPalace#483) - Cosine distance default for new collections (hnsw:space=cosine) - SKIP_PATTERNS/JUNK_FILE_SIZE filter minified JS, lockfiles, large dumps - detect_room() uses word-boundary regex and exact-match priority - chunk_text() accepts configurable size/overlap/min params - Entity detector STOPWORDS expanded (73 technical terms) - Configurable chunk_size/chunk_overlap/min_chunk_size in palace config Split from MemPalace#562 per maintainer request. Closes MemPalace#483, MemPalace#568. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Summary
palace.py:create_collection()now passesmetadata={"hnsw:space": "cosine"}so new palaces use cosine distance instead of ChromaDB's L2 defaultmcp_server.py: Same fix applied toget_or_create_collection()in the MCP pathsearcher.py+mcp_server.py: Similarity clamped withmax(0, 1 - dist)to prevent negative scores from existing L2-based palacesProblem
Similarity is calculated as
round(1 - dist, 3)throughout the codebase. This formula is correct for cosine distance (range 0-1) but produces negative similarity scores for L2/Euclidean distance (ChromaDB's default). Collections created viapalace.pyhave no distance metric specified, so they default to L2. Onlyrepair.pycreates withhnsw:space=cosine.Any palace that hasn't been through
mempalace repairsilently produces nonsensical negative similarity values.Migration note
This fix only applies to new palaces. Existing palaces created before this change still use L2 distance internally —
get_or_create_collectiondoes not retroactively change the distance metric on an existing collection. Users with existing palaces should runmempalace repairto rebuild the collection with cosine distance. Themax(0, ...)clamp provides a safe fallback in the meantime.Test plan
mempalace init+mempalace mine— verify collection uses cosinemempalace repairstill works correctly (already uses cosine)