Skip to content

release: v3.2.0#762

Merged
bensig merged 64 commits intomainfrom
release/3.2.0
Apr 13, 2026
Merged

release: v3.2.0#762
bensig merged 64 commits intomainfrom
release/3.2.0

Conversation

@igorls
Copy link
Copy Markdown
Member

@igorls igorls commented Apr 13, 2026

Summary

Cuts v3.2.0 — the final v3 release, giving every user a safe pin target (pip install 'mempalace<4') before v4 work begins on a separate branch.

This PR merges 58 commits accumulated on develop since v3.1.0 (2026-04-09), plus one commit on this branch finalizing the release metadata.

What's in v3.2.0

See CHANGELOG.md for the full list. Highlights:

Packaging

Security

Notable Bug Fixes

Features

This PR's single commit

PR #761 bumped pyproject.toml to 3.2.0 but missed three other version strings, causing test_version_consistency to fail on develop CI. This PR adds one commit fixing that:

Verification

  • Local: 689/689 tests pass
  • Local: ruff check clean
  • CI green on this branch
  • Smoke test on real Claude Code session data (Igor)

Post-merge steps

  1. Tag v3.2.0 on main
  2. python -m build && twine upload dist/* (Milla's PyPI token)
  3. Create v4 branch off develop HEAD for v4 work
  4. Draft GitHub Release using the [3.2.0] CHANGELOG section

igorls and others added 30 commits April 9, 2026 19:41
- 22 content pages across Guide, Concepts, and Reference sections
- Custom indigo/cyan theme with Lucide icons and Mermaid diagrams
- GitHub Actions workflow for GitHub Pages deployment
- Live preview: https://mempalace-docs.netlify.app/
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
On Windows, projects containing git-submodule junctions or dev-drive
reparse points cause iterdir() to list the entry successfully but
Path.is_dir() to raise OSError when it calls stat() internally.

Reproducer: any Windows project with a submodule checked out as a
junction (e.g. skills/pr-perfect) crashes mempalace init with:
  OSError: [WinError 448] The path cannot be traversed because it
  contains an untrusted mount point

Fix: wrap every is_dir() call in detect_rooms_from_folders with
try/except OSError so the scanner skips inaccessible entries and
continues rather than aborting.

Covers both the top-level pass and the one-level-deep nested pass.
Two new tests mock the OSError on specific paths and verify the
function returns correct rooms from the remaining accessible entries.
…ues (#569)

* fix: align cmd_compress dict keys with compression_stats() return values

* test: align compress test mocks with actual compression_stats() keys

* fix: address review — add Total: assertion, move stats key test to test_dialect.py
* refactor: add stage-1 backend abstraction seam

Introduce the first upstreamable storage seam for MemPalace without
bringing in the PostgreSQL spike or any benchmark artifacts.

This change adds a small backend package with:
- BaseCollection as the minimal collection contract
- ChromaBackend/ChromaCollection as the default implementation

It then routes the main runtime collection consumers through that seam:
- palace.py
- searcher.py
- layers.py
- palace_graph.py
- mcp_server.py
- miner.status()

Behavioral constraints kept for stage 1:
- ChromaDB remains the only backend and the default path
- no config/env backend selection yet
- no PostgreSQL code
- no benchmark or research files
- existing tests stay unchanged

Important compatibility details:
- read paths now call the seam with create=False so they still surface
  the existing 'no palace found' behavior instead of silently creating
  empty collections
- write paths keep create=True semantics through palace.get_collection()
- layers/searcher retain a chromadb module attribute so the existing
  mock-based tests can keep patching PersistentClient unchanged
- ChromaBackend only creates palace directories on create=True, which
  preserves mocked read-path tests that use fake read-only paths

Verification:
- python3 -m py_compile mempalace/backends/__init__.py mempalace/backends/base.py mempalace/backends/chroma.py mempalace/palace.py mempalace/searcher.py mempalace/layers.py mempalace/palace_graph.py mempalace/mcp_server.py mempalace/miner.py
- pytest -q  # 529 passed, 106 deselected

* refactor: clean up stage-1 seam compatibility shims

Tighten the stage-1 backend abstraction branch after review.

This follow-up does three small things:
- keep the chromadb compatibility hook in searcher.py and layers.py,
  but express it through the backends.chroma module so it no longer
  reads like an accidental unused import
- fix the palace_graph.py helper alias to avoid the local name collision
  flagged by ruff (imported helper vs local _get_collection wrapper)
- preserve the existing mock-based test patch points unchanged while
  keeping the new backend seam intact

Why this matters:
- the direct  form looked like a
  dead import in review, even though it was intentionally preserving the
  existing test seam ( and
  )
- palace_graph.py had a real lint issue ( redefinition) that was
  small but worth fixing before a public PR

Verification:
- /opt/homebrew/bin/ruff check mempalace/backends/__init__.py mempalace/backends/base.py mempalace/backends/chroma.py mempalace/palace.py mempalace/searcher.py mempalace/layers.py mempalace/palace_graph.py mempalace/mcp_server.py mempalace/miner.py
- pytest -q tests/test_layers.py tests/test_searcher.py
- pytest -q  # 529 passed, 106 deselected

* docs: explain backend shim imports in search paths

Add short code comments in searcher.py and layers.py explaining why the
module-level `chromadb` alias remains after the stage-1 backend seam
refactor.

The alias is intentional: it preserves the existing mock patch points used
by the current test suite (`mempalace.searcher.chromadb.PersistentClient`
and `mempalace.layers.chromadb.PersistentClient`) while the runtime logic
now flows through the backend abstraction.

This keeps the public PR easier to review because the apparent "unused
import" now has an explicit reason next to it.

Verification:
- /opt/homebrew/bin/ruff check mempalace/searcher.py mempalace/layers.py
- pytest -q tests/test_layers.py tests/test_searcher.py

* refactor: reuse a default backend instance in palace helper

Tighten the stage-1 backend seam by promoting the default Chroma backend
adapter to a module-level singleton in `mempalace/palace.py`.

This keeps the stage-1 scope unchanged — Chroma is still the only backend
wired in this branch — but avoids constructing a fresh `ChromaBackend()`
object on every `get_collection()` call. The backend is stateless today,
so this is a readability/cleanup change rather than a behavioral one.

Why this helps:
- makes `palace.get_collection()` read like a real default factory instead
  of an inline constructor call
- keeps the stage-1 branch a little cleaner before opening the public PR
- does not widen the backend surface or change any config/runtime behavior

Verification:
- python3 -m py_compile mempalace/palace.py
- pytest -q tests/test_miner.py tests/test_layers.py tests/test_searcher.py
- pytest -q  # 529 passed, 106 deselected

* fix: harden read-only seam behavior and update seam tests

Preserve the stage-1 backend abstraction while closing the real read-path
regression surfaced in PR review.

What changed:
- make ChromaBackend.get_collection(create=False) fail fast when the palace
  directory does not exist instead of letting PersistentClient create it as a
  side effect
- update miner.status() to call get_collection(..., create=False) so status
  keeps the historical 'No palace found' behavior
- remove the temporary chromadb shim aliases from layers.py and searcher.py
  now that the tests patch the seam directly
- add focused tests for the new backends package, including ChromaCollection
  delegation and ChromaBackend create=True/create=False behavior
- retarget layer/searcher tests to patch the backend seam instead of patching
  chromadb.PersistentClient inside production modules
- add a regression test that status() does not create an empty palace when the
  target path is missing

Verification:
- ruff check .
- uv run pytest -q
- uv run pytest -q tests/test_backends.py tests/test_cli.py tests/test_mcp_server.py tests/test_layers.py tests/test_searcher.py tests/test_miner.py

Notes:
- the separate benchmark/slow/stress layer was started as a soak but not used
  as the merge gate for this PR branch

* refactor: drop duplicate mcp collection cache declaration

Remove a redundant `_collection_cache = None` assignment in
`mempalace/mcp_server.py` left over after the stage-1 backend seam refactor.

This does not change behavior; it only trims review noise in the MCP server
module after the read-path hardening pass.

Verification:
- ruff check mempalace/mcp_server.py
- uv run pytest -q tests/test_mcp_server.py

---------

Co-authored-by: Sergey Kuznetsov <sergey@iterudit.com>
…g, concurrency safety, and WAL fixes (#647)

Addresses findings from security audit (ref #401): inconsistent sanitization across MCP tools,
unfiltered argument dispatch allowing audit trail spoofing, SQLite concurrency issues, WAL
permission race condition, sensitive content in logs, and internal error leakage to MCP callers.

Co-authored-by: Israel Domínguez <isra@MacBook-Pro-de-Israel.local>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…esolves #635) (#667)

* feat: MCP reliability — inode detection, WAL rotation, metadata cache, search limits

Infrastructure hardening for the MCP server:
- Detect palace DB replacement via inode tracking (repair command support)
- WAL rotation to prevent unbounded WAL growth
- _fetch_all_metadata() + _get_cached_metadata() with 60s TTL for taxonomy/status
- _MAX_RESULTS cap (100) with limit clamping [1, _MAX_RESULTS]
- max_distance parameter for similarity threshold in search
- Handle all notifications/* methods, null arguments, method=None
- Remove duplicate _client_cache = None declarations
- searcher.py max_distance parameter passthrough

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: new MCP tools (get/list/update drawer, hook settings, memories filed), export, normalize

New MCP tools:
- mempalace_get_drawer: fetch single drawer by ID with full content
- mempalace_list_drawers: paginated listing with wing/room filter
- mempalace_update_drawer: update content/wing/room on existing drawers
- mempalace_hook_settings: get/set hook behavior (silent_save, desktop_toast)
- mempalace_memories_filed_away: check latest checkpoint status

Also includes:
- exporter.py: export palace as browsable markdown files
- normalize.py: tool_use/tool_result capture for richer transcript mining
- layers.py: updated for new tool integration
- config.py: hook settings properties (hook_silent_save, hook_desktop_toast)

Depends on PR 3 (reliability) for _MAX_RESULTS, _metadata_cache, WAL logging.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: normalize.py handles string messages and Read offset type mismatch

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: params null guard, L2→cosine docs, empty tool_use_map key guard

- Handle explicit null in MCP params (request.get("params") or {})
- Fix search tool description: L2 → cosine distance (collection uses hnsw:space=cosine)
- Guard against empty string key in tool_use_map from malformed JSONL entries

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: rename ambiguous var 'l' to 'line' (E741 lint)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address code review findings (5 issues)

1. min_similarity backwards-compat: convert similarity to distance scale
   (1.0 - similarity) instead of passing raw value as max_distance
2. Restore structured error reporting (error + partial fields) in
   tool_status, tool_list_wings, tool_list_rooms, tool_get_taxonomy
   — reverts silent except:pass that dropped #647 security hardening
3. inode cache: remove falsy-zero short-circuit so missing DB file
   triggers reconnect instead of reusing stale client
4. _fetch_all_metadata: check for empty batch before extending/advancing
   offset to prevent infinite loop on concurrent deletion
5. KG initialization: only override path when --palace is explicit;
   default runs use KnowledgeGraph's built-in default path

Co-authored-by: jphein <jphein@users.noreply.github.com>

---------

Co-authored-by: jp <jp@jphein.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: jphein <jphein@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ORT_DISABLE_COREML is not a recognized ONNX Runtime environment
variable. ONNX Runtime does not expose a global env var to disable
individual execution providers -- providers are selected per session
via the providers argument to InferenceSession. Setting it had zero
effect.

The mitigation was added in df33550 (v3.1.0) with the stated goal of
fixing the #74 ARM64 segfault. Two problems: the env var doesn't
work as described above, and #74 is a null-pointer crash in
chromadb_rust_bindings.abi3.so -- not an ONNX issue, so disabling
CoreML would not have fixed it anyway.

#521 has since traced the actual macOS arm64 crashes (both mine and
search) to the 0.x chromadb hnswlib binding. Filtering
CoreMLExecutionProvider at the ONNX layer leaves the hnswlib C++
crash intact, so the real fix is upgrading chromadb to 1.5.4+,
which #581 proposes. This PR only removes the misleading no-op and
leaves a NOTE pointing at #521 / #581.

Closes #397
Note from code review: (1) silent exception swallow on migration failure means caller proceeds with potentially corrupt DB — consider returning a boolean or re-raising in a follow-up. (2) No blob length validation before int.from_bytes — malformed rows could produce wrong seq_id values. Both are edge cases; the fix is still valuable for the common chromadb 0.6→1.5 migration path.
…176)

The module-level `ssl._create_default_https_context = ssl._create_unverified_context`
disables certificate verification for ALL urllib requests in the process,
not just the benchmark's HuggingFace downloads. This silently exposes
the benchmark runner to MITM attacks.

If a specific environment needs to skip verification (e.g. corporate proxy),
users can set `PYTHONHTTPSVERIFY=0` or pass a custom ssl context per-request
rather than globally patching the ssl module.

Co-authored-by: Tadao <tadao@travisfixes.com>
… mcp_server.py (#449)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Path("~/foo") does not expand tilde on its own, causing
`mempalace split ~/some/dir` to silently find no files.

Fix by calling .expanduser().resolve() in both places the
path is constructed: cmd_split in cli.py (defensive, at the
CLI boundary) and main() in split_mega_files.py (the root cause).

Co-authored-by: Brooke Whatnall <brookewhatnall@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…177)

The `_load_api_key()` function in longmemeval_bench.py and locomo_bench.py
searched for API keys in a fixed path (`~/.config/lu/keys.json`) using
personal key names (`anthropic_milla`, `anthropic_claude_code_main`).

This leaks internal infrastructure details into the public codebase and
trains contributors to store credentials in a non-standard location
rather than using the standard ANTHROPIC_API_KEY env var.

Simplified to: CLI flag > env var > empty string. Updated help text
and HYBRID_MODE.md docs to match.

Co-authored-by: Tadao <tadao@travisfixes.com>
…tor docs (#679)

The repo moved to the MemPalace org but several docs still point at the
old milla-jovovich URLs.  Also, CONTRIBUTING.md tells people to PR
against main while the actual workflow (per ROADMAP.md) targets develop.

Files touched:
- CONTRIBUTING.md: clone URL, issues URL, PR target branch
- examples/gemini_cli_setup.md: clone URL
- integrations/openclaw/SKILL.md: homepage and license URLs
docs: add VitePress documentation site
Copilot AI and others added 13 commits April 12, 2026 22:19
- query_sanitizer: require matching quote pair in _strip_wrapping_quotes
- query_sanitizer: re-check MIN_QUERY_LENGTH after trim in tail_sentence path
- migrate: neutral confirmation message accurate for both migrate and repair
- cli: os.path.normpath instead of rstrip to handle '/' root edge case
…deletion

Harden palace deletion, WAL redaction, and MCP search input handling
Full changelog from git history and merged PRs:
- v3.0.0 (2026-04-06): initial public release
- v3.1.0 (2026-04-09): 80+ commits, security hardening, Windows compat, tests 20→92
- Unreleased/v3.2.0: 50+ commits, i18n, backend seam, migrate command, more security

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: hash full content in tool_add_drawer drawer ID

* style: apply ruff format

* style: fix ruff format for CI ruff 0.4.x
Converted to 7-bit clean us-ascii, which also aligns diagram.
Moved "hall" connection to be inline with Rooms in diagram.
#755)

'(r)roject' had a duplicate 'r', making it read as '(r)roject'
instead of the intended '(r)project'.

Small UX fix — no behavior change.

Co-authored-by: Arnold Wender <arnold.wender@gmail.com>
…757)

When external tools write to the palace database (CLI mining, scripts), the MCP server's cached ChromaDB collection becomes stale — its HNSW index doesn't know about new vectors. Develop already invalidates on inode changes (catches rebuilds) but not on mtime changes (misses in-place writes).

This PR:
- Adds st_mtime tracking alongside st_ino in _get_client; invalidates the cached client on either change.
- Adds the mempalace_reconnect MCP tool for explicit cache flush.

Original author: @jphein (#663). Original approval: @Ari4ka.
Skips test_missing_db_invalidates_cache on Windows (ChromaDB holds chroma.sqlite3 open).
#677) (#685)

* fix: parse Claude.ai privacy export with messages key and sender field (#677)

The privacy-export branch in _try_claude_ai_json only checked for the
"chat_messages" key, missing exports that use "messages" instead.  It
also only read the "role" field while real privacy exports use "sender".
Both gaps caused the file to fall through to plain-text, producing a
single giant drawer.

Changes:
- Accept "messages" alongside "chat_messages" in the conversation-object
  guard and inner extraction.
- Accept "sender" alongside "role" as the author field.
- Fall back to a top-level "text" key when content blocks are empty.
- Produce one transcript per conversation instead of concatenating all
  conversations into a single blob.
- Extract shared logic into _collect_claude_messages helper.
- Add 6 regression tests covering each variant.

* style: apply ruff format to normalize.py

* fix: guard against null text field in Claude.ai export parsing

item.get("text", "").strip() crashes when "text" is explicitly null
in the JSON (legal and observed in some exports). Use
(item.get("text") or "").strip() and add a regression test.

---------

Co-authored-by: Igor Lins e Silva <4753812+igorls@users.noreply.github.com>
PR #761 bumped pyproject.toml to 3.2.0 but missed three other version strings,
causing test_version_consistency to fail on develop CI (macos, linux 3.11, windows).

- mempalace/version.py: 3.1.0 → 3.2.0 (unblocks test_version_consistency)
- README.md: version badge shield 3.1.0 → 3.2.0
- integrations/openclaw/SKILL.md: 3.1.0 → 3.2.0
- CHANGELOG.md: rename [Unreleased] → [3.2.0] — 2026-04-13, add entries
  for #685, #690, #707, #716, #734, #755, #757, #761

Verified locally: 689/689 tests pass, ruff clean.
Copilot AI review requested due to automatic review settings April 13, 2026 06:31
bensig
bensig previously approved these changes Apr 13, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Release cut for v3.2.0 (final v3) that synchronizes version metadata across the repo, refreshes release notes/docs, and includes the accumulated develop-branch functionality/bugfix work (docs site, backend seam, mining/search hardening, i18n, migrations/repairs, etc.).

Changes:

  • Bump/align version strings and release metadata to 3.2.0 (pyproject, mempalace/version.py, README badge, OpenClaw skill, changelog).
  • Add a VitePress documentation site under website/ and a GitHub Pages deployment workflow.
  • Refactor/read-path hardening across core modules (backend seam usage, search/query sanitization updates, migrate/repair confirmations, mining edge-case guards) plus expanded test coverage.

Reviewed changes

Copilot reviewed 95 out of 105 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
website/reference/python-api.md Adds Python API overview page for docs site.
website/reference/modules.md Adds module map / project structure reference.
website/reference/mcp-tools.md Adds MCP tools parameter reference documentation.
website/reference/contributing.md Adds docs-site contributing page.
website/reference/cli.md Adds CLI command reference for docs site.
website/reference/benchmarks.md Adds benchmark summary page for docs site.
website/reference/api-reference.md Adds parameter-level API reference for docs site.
website/public/icons/wrench.svg Adds docs site icon asset.
website/public/icons/shield-check.svg Adds docs site icon asset.
website/public/icons/search.svg Adds docs site icon asset.
website/public/icons/git-merge.svg Adds docs site icon asset.
website/public/icons/file-text.svg Adds docs site icon asset.
website/public/icons/building-2.svg Adds docs site icon asset.
website/package.json Adds docs site Node/bun tooling manifest.
website/index.md Adds VitePress home page content.
website/guide/searching.md Adds docs guide page (searching).
website/guide/openclaw.md Adds docs guide page (OpenClaw).
website/guide/mining.md Adds docs guide page (mining).
website/guide/mcp-integration.md Adds docs guide page (MCP integration).
website/guide/local-models.md Adds docs guide page (local models).
website/guide/hooks.md Adds docs guide page (hooks).
website/guide/getting-started.md Adds docs getting started guide.
website/guide/gemini-cli.md Adds docs guide page (Gemini CLI).
website/guide/configuration.md Adds docs guide page (configuration).
website/guide/claude-code.md Adds docs guide page (Claude Code).
website/concepts/the-palace.md Adds docs concepts page (palace structure).
website/concepts/memory-stack.md Adds docs concepts page (memory stack).
website/concepts/knowledge-graph.md Adds docs concepts page (knowledge graph).
website/concepts/contradiction-detection.md Adds docs concepts page (planned feature).
website/concepts/agents.md Adds docs concepts page (agent diaries).
website/concepts/aaak-dialect.md Adds docs concepts page (AAAK dialect).
website/.vitepress/theme/style.css Adds custom VitePress styling.
website/.vitepress/theme/index.ts Adds VitePress theme entrypoint.
website/.vitepress/config.mts Adds VitePress site configuration + sidebar/nav.
website/.gitignore Adds docs-site ignores.
tests/test_searcher.py Updates mocking strategy for searcher tests.
tests/test_room_detector_local.py Adds regression tests for Windows OSError/reparse points.
tests/test_query_sanitizer.py Expands sanitize_query tests for new behavior/constants.
tests/test_miner.py Adds dry-run regression test + status behavior test.
tests/test_migrate.py Adds safety/confirmation tests for migrate.
tests/test_exporter.py Adds exporter tests for structure/content/empty export.
tests/test_dialect.py Adds coverage for compression_stats() key set.
tests/test_convo_miner.py Adds tests for sentinel registration to prevent reprocessing.
tests/test_convo_miner_unit.py Adds regression test for long AI response preservation.
tests/test_config.py Adds sanitize_name() Unicode + validation tests.
tests/test_cli.py Adds repair confirmation/db-presence tests; updates compress stats expectations.
tests/test_backends.py Adds tests for backend seam + BLOB seq_id repair.
tests/conftest.py Ensures KnowledgeGraph fixture closes connections.
ROADMAP.md Adds roadmap document (v3 stability/v4 plan).
README.md Updates ASCII-ish architecture diagram + version badge.
pyproject.toml Bumps project version to 3.2.0.
MISSION.md Adds mission narrative document.
mempalace/version.py Updates package __version__ to 3.2.0.
mempalace/split_mega_files.py Expands/resolves --source path handling.
mempalace/searcher.py Uses backend seam collection getter; adds filter builder and distance filtering metadata.
mempalace/room_detector_local.py Adds OSError guards/logging around Windows reparse point failures.
mempalace/query_sanitizer.py Tightens MAX_QUERY_LENGTH and improves tail/quote trimming logic.
mempalace/palace.py Routes collection access via backend seam; tweaks mtime epsilon check.
mempalace/palace_graph.py Uses backend seam collection getter (create=False).
mempalace/miner.py Adjusts process_file() return contract; updates status() to use count-based limit.
mempalace/migrate.py Adds db presence check + destructive action confirmation helpers.
mempalace/layers.py Uses backend seam collection getter; adds L1 scan limit and reuses filter builder.
mempalace/knowledge_graph.py Adds threading lock around SQLite operations for safety.
mempalace/instructions/init.md Updates init instructions to include --yes.
mempalace/i18n/zh-TW.json Adds i18n strings/patterns (Traditional Chinese).
mempalace/i18n/zh-CN.json Adds i18n strings/patterns (Simplified Chinese).
mempalace/i18n/test_i18n.py Adds i18n smoke-test script (non-pytest by default).
mempalace/i18n/ko.json Adds i18n strings/patterns (Korean).
mempalace/i18n/ja.json Adds i18n strings/patterns (Japanese).
mempalace/i18n/fr.json Adds i18n strings/patterns (French).
mempalace/i18n/es.json Adds i18n strings/patterns (Spanish).
mempalace/i18n/en.json Adds i18n strings/patterns (English).
mempalace/i18n/de.json Adds i18n strings/patterns (German).
mempalace/i18n/init.py Adds i18n loader/translator utilities.
mempalace/exporter.py Adds markdown export facility for palace drawers.
mempalace/entity_detector.py Fixes prompt typo in interactive entity classification.
mempalace/dialect.py Adds lang support pulling AAAK/regex from i18n dictionaries.
mempalace/convo_miner.py Adds sentinel registration + long-response chunking behavior.
mempalace/config.py Expands sanitize_name() Unicode support; adds hook settings helpers.
mempalace/cli.py Adds destructive confirmation for repair/migrate; improves path handling; updates compress stats printing.
mempalace/backends/chroma.py Adds Chroma backend adapter + BLOB seq_id repair pre-client init.
mempalace/backends/base.py Defines minimal backend collection interface.
mempalace/backends/init.py Exposes backend interfaces/implementations.
mempalace/init.py Removes ineffective CoreML env workaround; updates commentary.
integrations/openclaw/SKILL.md Updates skill version/homepage links for 3.2.0.
examples/gemini_cli_setup.md Updates repo clone URL.
CONTRIBUTING.md Updates repo clone URL and PR base branch guidance.
CLAUDE.md Adds Claude-focused project documentation (principles, structure, commands).
CHANGELOG.md Finalizes 3.2.0 changelog section and release notes.
benchmarks/longmemeval_bench.py Removes hardcoded credential-path fallback for API keys; clarifies messaging.
benchmarks/locomo_bench.py Removes hardcoded credential-path fallback for API keys.
benchmarks/HYBRID_MODE.md Updates benchmark docs to match API key loading behavior.
benchmarks/convomem_bench.py Removes global SSL verification bypass.
.gitignore Ignores Claude/Codex config directories.
.github/workflows/deploy-docs.yml Adds GitHub Pages deployment workflow for docs site.
.github/workflows/ci.yml Runs CI on both main and develop.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread mempalace/exporter.py
Comment on lines +44 to +49
col = get_collection(palace_path)
total = col.count()

if total == 0:
print(" Palace is empty — nothing to export.")
return {"wings": 0, "rooms": 0, "drawers": 0}
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

export_palace() calls get_collection(palace_path) with the default create=True, which will create the palace directory/collection when the path doesn’t exist. Exporting should be read-only; consider calling get_collection(..., create=False) and returning a friendly "No palace found"/empty result without materializing a new palace on disk.

Copilot uses AI. Check for mistakes.
Comment thread mempalace/exporter.py
Comment on lines +82 to +96
for wing, rooms in batch_grouped.items():
safe_wing = _safe_path_component(wing)
wing_dir = os.path.join(output_dir, safe_wing)
os.makedirs(wing_dir, exist_ok=True)

for room, drawers in rooms.items():
safe_room = _safe_path_component(room)
room_path = os.path.join(wing_dir, f"{safe_room}.md")
key = (wing, room)
is_new = key not in opened_rooms

with open(room_path, "a" if not is_new else "w", encoding="utf-8") as f:
if is_new:
f.write(f"# {wing} / {room}\n\n")
opened_rooms.add(key)
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Room files are written under sanitized paths (safe_wing/safe_room), but opened_rooms uses the unsanitized (wing, room) tuple as the key. If two distinct wing/room names sanitize to the same path, this can cause overwrites or incorrect append/overwrite behavior. Consider keying by room_path (or (safe_wing, safe_room)) to match the actual output files.

Copilot uses AI. Check for mistakes.
Comment thread mempalace/exporter.py
"|------|-------|---------|",
]
for wing, room_count, drawer_count in index_rows:
index_lines.append(f"| [{wing}]({wing}/) | {room_count} | {drawer_count} |")
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The export index links use the raw wing value in the URL (([{wing}]({wing}/))), but wing directories are created using the sanitized safe_wing. This can generate broken links (and potentially unsafe paths) for wings with spaces or special characters. Use safe_wing for the link target while keeping the display text as the original wing name.

Suggested change
index_lines.append(f"| [{wing}]({wing}/) | {room_count} | {drawer_count} |")
safe_wing = _safe_path_component(wing)
index_lines.append(f"| [{wing}]({safe_wing}/) | {room_count} | {drawer_count} |")

Copilot uses AI. Check for mistakes.
Comment on lines +9 to +17
```python
from mempalace.searcher import search_memories

results = search_memories(
query="why did we switch to GraphQL",
wing="myapp", # optional filter
room="architecture", # optional filter
n_results=5,
)
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The search_memories() example omits the required palace_path argument. As written, this snippet will raise a TypeError for missing palace_path. Update the example to pass palace_path (positional or keyword) so it matches the actual API.

Copilot uses AI. Check for mistakes.
Comment on lines +23 to +53
### `search_memories(query, palace_path, wing=None, room=None, n_results=5) → dict`

Programmatic search returning a dict. Used by the MCP server.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `query` | `str` | — | Search query text |
| `palace_path` | `str` | — | Path to ChromaDB palace directory |
| `wing` | `str` | `None` | Filter by wing name |
| `room` | `str` | `None` | Filter by room name |
| `n_results` | `int` | `5` | Maximum number of results |

**Returns:**
```python
{
"query": str,
"filters": {"wing": str | None, "room": str | None},
"results": [
{
"text": str, # verbatim drawer content
"wing": str, # wing name
"room": str, # room name
"source_file": str, # original file basename
"similarity": float, # 0.0 to 1.0
}
]
}
```

On error: `{"error": str, "hint": str}`

Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

api-reference.md documents search_memories(query, palace_path, wing=None, room=None, n_results=5) and a return shape that doesn’t include the newer fields (max_distance parameter, distance per hit, total_before_filter on the response). Please update this reference so it matches mempalace.searcher.search_memories()’s current signature and result schema.

Copilot uses AI. Check for mistakes.
Comment on lines +33 to +46
## PR Guidelines

1. Fork the repo and create a feature branch: `git checkout -b feat/my-thing`
2. Write your code
3. Add or update tests if applicable
4. Run `pytest tests/ -v` — everything must pass
5. Commit with clear [conventional commits](https://www.conventionalcommits.org/):
- `feat: add Notion export format`
- `fix: handle empty transcript files`
- `docs: update MCP tool descriptions`
- `bench: add LoCoMo turn-level metrics`
6. Push to your fork and open a PR against `main`

## Code Style
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This guide says to open PRs against main, but the repository contribution workflow targets develop (see the root CONTRIBUTING.md change in this PR). Please update this page to avoid sending contributors to the wrong base branch.

Copilot uses AI. Check for mistakes.
Comment thread README.md
Comment on lines +221 to +247
+------------------------------------------------------------+
¦ WING: Person ¦
¦ ¦
¦ +----------+ +----------+ ¦
¦ ¦ Room A ¦ --hall-- ¦ Room B ¦ ¦
¦ +----------+ +----------+ ¦
¦ ¦ ¦
¦ v ¦
¦ +----------+ +----------+ ¦
¦ ¦ Closet ¦ ---> ¦ Drawer ¦ ¦
¦ +----------+ +----------+ ¦
+---------+--------------------------------------------------+
¦
tunnel
┌─────────┼──────────────────────────────────────────────────┐
WING: Project
┌────┴─────┐ ──hall── ┌──────────┐
Room A │ │ Room C
└────┬─────┘ └──────────┘
┌──────────┐ ┌──────────┐
Closet │ ───▶ │ Drawer
└──────────┘ └──────────┘
└─────────────────────────────────────────────────────────────┘
¦
+---------+--------------------------------------------------+
¦ WING: Project ¦
¦ ¦ ¦
¦ +----------+ +----------+ ¦
¦ ¦ Room A ¦ --hall-- ¦ Room C ¦ ¦
¦ +----------+ +----------+ ¦
¦ ¦ ¦
¦ v ¦
¦ +----------+ +----------+ ¦
¦ ¦ Closet ¦ ---> ¦ Drawer ¦ ¦
¦ +----------+ +----------+ ¦
+------------------------------------------------------------+
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The updated architecture diagram uses the broken-bar character ¦, which is non-ASCII and can render inconsistently across terminals/fonts. If the goal is a portable plain-text diagram (and to avoid encoding issues), consider using standard ASCII | instead.

Copilot uses AI. Check for mistakes.
Comment thread mempalace/convo_miner.py
Comment on lines +35 to +56
def _register_file(collection, source_file: str, wing: str, agent: str):
"""Write a sentinel so file_already_mined() returns True for 0-chunk files.

Without this, files that normalize to nothing or produce zero chunks are
re-read and re-processed on every mine run because nothing was written to
ChromaDB on the first pass.
"""
sentinel_id = f"_reg_{hashlib.sha256(source_file.encode()).hexdigest()[:24]}"
collection.upsert(
documents=[f"[registry] {source_file}"],
ids=[sentinel_id],
metadatas=[
{
"wing": wing,
"room": "_registry",
"source_file": source_file,
"added_by": agent,
"filed_at": datetime.now().isoformat(),
"ingest_mode": "registry",
}
],
)
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The registry sentinel is written into the main mempalace_drawers collection as a normal document (documents=["[registry] ..."], room="_registry"). This will inflate drawer counts (status, exports, etc.) and can surface in unfiltered searches. Consider storing sentinels in a separate collection (or adding an explicit filter in search/status/export to exclude ingest_mode="registry").

Copilot uses AI. Check for mistakes.
* 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.

* fix: remove chromadb <0.7 upper bound — blocks 1.x installs

The current constraint `chromadb>=0.5.0,<0.7` forces pip to install
chromadb 0.6.x, but palaces created with chromadb 1.x (which is what
the mempalace dev environment actually uses — 1.5.7 per uv.lock) have
an incompatible SQLite schema. Specifically, chromadb 0.6.x fails with
`KeyError: '_type'` when opening a collection written by 1.x.

This means a fresh `pip install mempalace` gives users a chromadb
version that cannot read palaces created in the maintainer's own
environment. The fix removes the upper bound so pip can resolve to the
current stable chromadb release.

Reproduction:
  python3 -m venv .venv && source .venv/bin/activate
  pip install mempalace          # installs chromadb 0.6.3
  # Try opening a palace created with chromadb 1.x:
  # -> _get_collection() returns None, tool_status() returns "No palace found"
  pip install chromadb==1.5.7    # force upgrade
  # -> tool_status() returns real data (26k drawers in our case)

---------

Co-authored-by: z3tz3r0 <kittipan.wang@gmail.com>
Co-authored-by: AlyciaBHZ <50111876+AlyciaBHZ@users.noreply.github.com>
Co-authored-by: Ben Sigman <1872138+bensig@users.noreply.github.com>
sync: merge main into release/3.2.0 (preserve history)
@bensig bensig self-requested a review April 13, 2026 06:49
@bensig bensig merged commit 122a5fd into main Apr 13, 2026
6 checks passed
@igorls igorls mentioned this pull request Apr 15, 2026
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.