From f138d4929db2b7fda5b22b526ded3f48fabf9fa3 Mon Sep 17 00:00:00 2001 From: z3tz3r0 Date: Sun, 12 Apr 2026 13:06:41 +0700 Subject: [PATCH 1/3] fix: disambiguate hook block reasons to name MemPalace explicitly (#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. --- CONTRIBUTING.md | 5 ++++- hooks/mempal_precompact_hook.sh | 2 +- hooks/mempal_save_hook.sh | 2 +- mempalace/hooks_cli.py | 22 +++++++++++++--------- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f98df74bc1..0ac629db56 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,8 +5,11 @@ Thanks for wanting to help. MemPalace is open source and we welcome contribution ## Getting Started ```bash -git clone https://github.com/milla-jovovich/mempalace.git +# Fork the repo on GitHub first, then clone your fork +git clone https://github.com//mempalace.git cd mempalace +git remote add upstream https://github.com/milla-jovovich/mempalace.git + pip install -e ".[dev]" # installs with dev dependencies (pytest, build, twine) ``` diff --git a/hooks/mempal_precompact_hook.sh b/hooks/mempal_precompact_hook.sh index 550a813be1..784080ac8a 100755 --- a/hooks/mempal_precompact_hook.sh +++ b/hooks/mempal_precompact_hook.sh @@ -72,6 +72,6 @@ fi cat << 'HOOKJSON' { "decision": "block", - "reason": "COMPACTION IMMINENT. Save ALL topics, decisions, quotes, code, and important context from this session to your memory system. Be thorough — after compaction, detailed context will be lost. Organize into appropriate categories. Use verbatim quotes where possible. Save everything, then allow compaction to proceed." + "reason": "COMPACTION IMMINENT (MemPalace). Save ALL session content before context is lost:\n1. mempalace_diary_write — thorough AAAK-compressed session summary\n2. mempalace_add_drawer — ALL verbatim quotes, decisions, code, context\n3. mempalace_kg_add — entity relationships (optional)\nBe thorough — after compaction, detailed context will be lost. Do NOT write to Claude Code's native auto-memory (.md files). Save everything to MemPalace, then allow compaction to proceed." } HOOKJSON diff --git a/hooks/mempal_save_hook.sh b/hooks/mempal_save_hook.sh index a0e4681fca..df6715e30f 100755 --- a/hooks/mempal_save_hook.sh +++ b/hooks/mempal_save_hook.sh @@ -145,7 +145,7 @@ if [ "$SINCE_LAST" -ge "$SAVE_INTERVAL" ] && [ "$EXCHANGE_COUNT" -gt 0 ]; then cat << 'HOOKJSON' { "decision": "block", - "reason": "AUTO-SAVE checkpoint. Save key topics, decisions, quotes, and code from this session to your memory system. Organize into appropriate categories. Use verbatim quotes where possible. Continue conversation after saving." + "reason": "AUTO-SAVE checkpoint (MemPalace). Save this session's key content:\n1. mempalace_diary_write — AAAK-compressed session summary\n2. mempalace_add_drawer — verbatim quotes, decisions, code snippets\n3. mempalace_kg_add — entity relationships (optional)\nDo NOT write to Claude Code's native auto-memory (.md files). Continue conversation after saving." } HOOKJSON else diff --git a/mempalace/hooks_cli.py b/mempalace/hooks_cli.py index b6d229082b..2ce13f432d 100644 --- a/mempalace/hooks_cli.py +++ b/mempalace/hooks_cli.py @@ -18,18 +18,22 @@ STATE_DIR = Path.home() / ".mempalace" / "hook_state" STOP_BLOCK_REASON = ( - "AUTO-SAVE checkpoint. Save key topics, decisions, quotes, and code " - "from this session to your memory system. Organize into appropriate " - "categories. Use verbatim quotes where possible. Continue conversation " - "after saving." + "AUTO-SAVE checkpoint (MemPalace). Save this session's key content:\n" + "1. mempalace_diary_write — AAAK-compressed session summary\n" + "2. mempalace_add_drawer — verbatim quotes, decisions, code snippets\n" + "3. mempalace_kg_add — entity relationships (optional)\n" + "Do NOT write to Claude Code's native auto-memory (.md files). " + "Continue conversation after saving." ) PRECOMPACT_BLOCK_REASON = ( - "COMPACTION IMMINENT. Save ALL topics, decisions, quotes, code, and " - "important context from this session to your memory system. Be thorough " - "\u2014 after compaction, detailed context will be lost. Organize into " - "appropriate categories. Use verbatim quotes where possible. Save " - "everything, then allow compaction to proceed." + "COMPACTION IMMINENT (MemPalace). Save ALL session content before context is lost:\n" + "1. mempalace_diary_write — thorough AAAK-compressed session summary\n" + "2. mempalace_add_drawer — ALL verbatim quotes, decisions, code, context\n" + "3. mempalace_kg_add — entity relationships (optional)\n" + "Be thorough \u2014 after compaction, detailed context will be lost. " + "Do NOT write to Claude Code's native auto-memory (.md files). " + "Save everything to MemPalace, then allow compaction to proceed." ) From 99eb38dfd485ab43b0f7fdbbb2bab0ac09ac99bb Mon Sep 17 00:00:00 2001 From: Arnold Wender Date: Fri, 10 Apr 2026 22:38:42 +0200 Subject: [PATCH 2/3] fix: default collection to cosine distance and clamp similarity scores --- mempalace/mcp_server.py | 6 ++++-- mempalace/palace.py | 2 +- mempalace/searcher.py | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/mempalace/mcp_server.py b/mempalace/mcp_server.py index 09203b6b58..f1f06c919e 100644 --- a/mempalace/mcp_server.py +++ b/mempalace/mcp_server.py @@ -119,7 +119,9 @@ def _get_collection(create=False): try: client = _get_client() if create: - _collection_cache = client.get_or_create_collection(_config.collection_name) + _collection_cache = client.get_or_create_collection( + _config.collection_name, metadata={"hnsw:space": "cosine"} + ) elif _collection_cache is None: _collection_cache = client.get_collection(_config.collection_name) return _collection_cache @@ -347,7 +349,7 @@ def tool_check_duplicate(content: str, threshold: float = 0.9): if results["ids"] and results["ids"][0]: for i, drawer_id in enumerate(results["ids"][0]): dist = results["distances"][0][i] - similarity = round(1 - dist, 3) + similarity = round(max(0, 1 - dist), 3) if similarity >= threshold: meta = results["metadatas"][0][i] doc = results["documents"][0][i] diff --git a/mempalace/palace.py b/mempalace/palace.py index 6ddf19084c..f2fd598d3f 100644 --- a/mempalace/palace.py +++ b/mempalace/palace.py @@ -45,7 +45,7 @@ def get_collection(palace_path: str, collection_name: str = "mempalace_drawers") try: return client.get_collection(collection_name) except Exception: - return client.create_collection(collection_name) + return client.create_collection(collection_name, metadata={"hnsw:space": "cosine"}) def file_already_mined(collection, source_file: str, check_mtime: bool = False) -> bool: diff --git a/mempalace/searcher.py b/mempalace/searcher.py index 163abd88c5..95426de921 100644 --- a/mempalace/searcher.py +++ b/mempalace/searcher.py @@ -72,7 +72,7 @@ def search(query: str, palace_path: str, wing: str = None, room: str = None, n_r print(f"{'=' * 60}\n") for i, (doc, meta, dist) in enumerate(zip(docs, metas, dists), 1): - similarity = round(1 - dist, 3) + similarity = round(max(0, 1 - dist), 3) source = Path(meta.get("source_file", "?")).name wing_name = meta.get("wing", "?") room_name = meta.get("room", "?") @@ -141,7 +141,7 @@ def search_memories( "wing": meta.get("wing", "unknown"), "room": meta.get("room", "unknown"), "source_file": Path(meta.get("source_file", "?")).name, - "similarity": round(1 - dist, 3), + "similarity": round(max(0, 1 - dist), 3), } ) From f860cb8db6fa7305ca30a442f663229a15fae5f0 Mon Sep 17 00:00:00 2001 From: Arnold Wender Date: Sun, 12 Apr 2026 21:39:22 +0200 Subject: [PATCH 3/3] fix: use 0.0 literal in max() to guarantee float similarity scores 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. --- mempalace/searcher.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mempalace/searcher.py b/mempalace/searcher.py index 95426de921..3710bf3ca8 100644 --- a/mempalace/searcher.py +++ b/mempalace/searcher.py @@ -72,7 +72,7 @@ def search(query: str, palace_path: str, wing: str = None, room: str = None, n_r print(f"{'=' * 60}\n") for i, (doc, meta, dist) in enumerate(zip(docs, metas, dists), 1): - similarity = round(max(0, 1 - dist), 3) + similarity = round(max(0.0, 1 - dist), 3) source = Path(meta.get("source_file", "?")).name wing_name = meta.get("wing", "?") room_name = meta.get("room", "?") @@ -141,7 +141,7 @@ def search_memories( "wing": meta.get("wing", "unknown"), "room": meta.get("room", "unknown"), "source_file": Path(meta.get("source_file", "?")).name, - "similarity": round(max(0, 1 - dist), 3), + "similarity": round(max(0.0, 1 - dist), 3), } )