v3.3.5

MemPalace/mempalacev3.3.5May 10, 2026by igorls

AI Summary

A fixes release focusing on data integrity, recovery from corrupt HNSW indices, and cross-process correctness on Windows and multi-tenant hosts.

Key Highlights

  • `mempalace repair --mode from-sqlite` recovers palaces stuck on corrupt HNSW.
  • Knowledge graph rejects inverted temporal intervals.
  • Entity registry saves atomically to prevent crash corruption.
  • MCP `tool_search` retries on transient HNSW flush windows.
  • Cross-process correctness on Windows and multi-tenant hosts.

Breaking Changes

  • Partial-date inputs that worked in 3.3.4 (e.g., '2026', '2026-05') now error; full date or UTC datetime required.

New Features

  • Repair from-sqlite mode
  • Knowledge graph validation
  • Atomic registry save
  • Retry logic for transient failures
  • Cross-process correctness fixes

Full Release Notes

**3.3.5 is a fixes release.** No new user-facing features — the cycle's energy went into data integrity, recovery, and cross-process correctness. If your palace ever hit `Internal error: Error finding id` after a big mine, or you had a knowledge-graph triple silently disappear into the void, or you're running on Windows / a multi-tenant host — this release is for you.

The headline fixes:

- **`mempalace repair --mode from-sqlite` recovers palaces stuck on `apply_logs`.** When ChromaDB's HNSW segment writer wedges (the corruption class reported upstream and in #1308), `--mode legacy` and the inline repair both call `Collection.count()` as their first read — which is exactly the call that raises. The old failure mode was a repair printing `Cannot recover — palace may need to be re-mined from source files` even though the SQLite tables were fully intact. The new mode reads `(id, document, metadata)` directly from `chroma.sqlite3` via the metadata segment join, never opens a chromadb client against the corrupt palace, and re-upserts everything into a fresh palace under your configured embedding function. Verified end-to-end on a 52,300-row real-world corrupt palace. (#1308)

- **Two silent-loss classes in the knowledge graph are now write-time errors.** Triples with `valid_to < valid_from` used to be durably stored but unreachable (the temporal filter matched no `as_of`); `add_triple()` now rejects inverted intervals at write time. And date strings forwarded to SQLite are validated up front via `sanitize_iso_temporal()` (alias `sanitize_iso_date()`), which accepts `YYYY-MM-DD` and canonical UTC datetimes (`YYYY-MM-DDTHH:MM:SSZ` / `+00:00`) and rejects everything else — natural-language inputs like `"yesterday"` no longer silently produce empty result sets. **Behavior change**: partial-date inputs that worked in 3.3.4 (`"2026"`, `"2026-05"`) now error; pass a full date or canonical UTC datetime instead. (#1214, #1164, #1167, #1374, #1417)

- **`EntityRegistry.save()` is now crash-safe.** The people/projects map is months of mining data, and the old code did a non-atomic `Path.write_text()` — a power loss, OOM, or `kill -9` between truncate and full-flush would silently wipe it (the registry's `load()` swallows `JSONDecodeError`). Save now writes to a sibling temp file, `fsync`s, and `os.replace()`s into place. The previous registry stays intact on any crash before the rename returns. (#1215)

- **MCP `tool_search` rides through the post-bulk-mine HNSW flush window.** After a CLI mine, ChromaDB's HNSW segment metadata can be unflushed for ~30-60s; wing-scoped MCP search was hitting `Internal error: Error finding id` for that entire window. `tool_search` now detects this specific transient, drops both the MCP-local and shared-backend caches for the palace, sleeps 2s, and retries once. Successful retries are tagged with `index_recovered: true` so callers can observe when it fired. Partial fix for the broader #1315 cluster — `tool_check_duplicate` and other index-touching tools still need the same wrapper. (#1396)

- **Cross-process correctness on Windows and multi-tenant hosts.** `ChromaBackend.close_palace()` now properly releases ChromaDB's rust-side SQLite file lock — reopening the same palace path after `shutil.rmtree` no longer fails with `SQLITE_READONLY_DBMOVED`. And the MCP server's `_kg` is no longer a module-level singleton: multi-tenant hosts rotating `MEMPALACE_PALACE_PATH` between tool calls now hit the right SQLite file via a lazy per-path cache. (#1067, #1105, #1136, #1160)

- **Windows stdio is UTF-8 across the board.** `mempalace search > out.txt` and piped fact_checker invocations no longer mojibake Cyrillic / CJK drawer text at the process boundary. `mcp_server`, `hooks_cli`, and `cli`/`fact_checker` all reconfigure identically. (#1282)

- **`miner.detect_room` routing fix for monorepos.** The substring check was bidirectional, so any token that was a substring of a room name or vice versa matched — `views/billing-page/src/Foo.test.tsx` routed to an `interviews` room because `"views" in "interviews"` matched first. Now uses separator-bounded token matching (`-`, `_`, `.`, `/`). (#1004, closes #1002)

- **`mempalace compress` no longer crashes on palaces above ~32k drawers.** Drawer fetch is now paginated, mirroring the #851 fix in `miner.py`. (#1073, #1107)

Internal: test reliability got a pass too — `tests/test_palace_locks.py` and `tests/test_chroma_collection_lock.py` switched from `fork` to `spawn` for subprocess spawning (Python 3.13 + multi-threaded parent = deadlock under `fork`, and macOS forbids fork-without-exec via CoreFoundation), and several test files now wrap `sqlite3.connect(...)` in `contextlib.closing()` so connections release on the failure path too. (#1430, #1431)

The promises haven't moved. Every word stored exactly. Everything stays on your machine. No telemetry. No fallbacks to cloud APIs we didn't tell you about. Each release sharpens what's already there.

## What's Changed
* fix(mcp_server): pass embedding_function= on collection reopen (#1299) by @igorls in https://github.com/MemPalace/mempalace/pull/1303
* fix(hooks): quote plugin-root paths with spaces (#1076) by @mvalentsev in https://github.com/MemPalace/mempalace/pull/1077
* fix(cli): cmd_compress writes to mempalace_closets (#1244) by @igorls in https://github.com/MemPalace/mempalace/pull/1319
* fix(mcp): case-insensitive agent name in diary read/write (#1243) by @igorls in https://github.com/MemPalace/mempalace/pull/1323
* fix(backends/chroma): wire quarantine_stale_hnsw into _client() (#1121 #1132 #1263) by @igorls in https://github.com/MemPalace/mempalace/pull/1322
* fix(mcp): omit absolute filesystem paths from MCP tool responses by @igorls in https://github.com/MemPalace/mempalace/pull/1325
* feat(searcher): candidate_strategy="union" — BM25 candidates joined with vector pool before hybrid rerank by @igorls in https://github.com/MemPalace/mempalace/pull/1306
* fix(mcp): forward valid_to and source params in kg_add/kg_invalidate (#1314) by @igorls in https://github.com/MemPalace/mempalace/pull/1320
* fix(cli): honor --palace flag in cmd_init (#1313) by @igorls in https://github.com/MemPalace/mempalace/pull/1321
* fix(kg): reject inverted intervals in add_triple (valid_to < valid_from) by @arnoldwender in https://github.com/MemPalace/mempalace/pull/1214
* fix(backends/chroma): release SQLite file lock on close_palace/close (#1067) by @mvalentsev in https://github.com/MemPalace/mempalace/pull/1105
* fix(entity_registry): atomic write to prevent partial corruption on crash by @arnoldwender in https://github.com/MemPalace/mempalace/pull/1215
* fix: paginate closet_llm col.get (#1073) by @sha2fiddy in https://github.com/MemPalace/mempalace/pull/1107
* fix(cli, fact-checker): reconfigure stdio to UTF-8 on Windows by @mvalentsev in https://github.com/MemPalace/mempalace/pull/1282
* fix(kg): validate ISO-8601 date formats at MCP boundary by @arnoldwender in https://github.com/MemPalace/mempalace/pull/1167
* fix(mcp): lazy per-path KnowledgeGraph cache (#1136) by @mvalentsev in https://github.com/MemPalace/mempalace/pull/1160
* docs(changelog): batch entries for 7 v3.3.5 fixes by @igorls in https://github.com/MemPalace/mempalace/pull/1370
* fix: serialize ChromaCollection writes through palace lock by @imtylervo in https://github.com/MemPalace/mempalace/pull/1162
* fix(hooks): treat absent ~/.mempalace as auto-save off by @lcatlett in https://github.com/MemPalace/mempalace/pull/1305
* fix: add total count to tool_list_drawers pagination response by @Sathvik-1007 in https://github.com/MemPalace/mempalace/pull/1114
* fix(mcp): force UTF-8 on stdio to fix -32000 on non-ASCII payloads (Windows) by @alonehobo in https://github.com/MemPalace/mempalace/pull/1060
* fix: guard None metadata/doc in tool_check_duplicate and Layer1/Layer2 by @eldar702 in https://github.com/MemPalace/mempalace/pull/1030
* fix(lint): hoist hooks_cli_mod import to top of test_hooks_cli (E402) by @igorls in https://github.com/MemPalace/mempalace/pull/1375
* fix(searcher): clamp effective_distance to valid cosine range [0, 2] by @eldar702 in https://github.com/MemPalace/mempalace/pull/1029
* fix(mcp): handle null JSON-RPC request payloads safely by @alpiua in https://github.com/MemPalace/mempalace/pull/987
* fix: MCP server JSON output ensure_ascii=False for non-ASCII support by @hzx945627450-eng in https://github.com/MemPalace/mempalace/pull/1293
* fix: clamp similarity scores to [0,1] to prevent negative values by @bobo-xxx in https://github.com/MemPalace/mempalace/pull/988
* fix(searcher): guard against None metadata/doc in search result loops by @cantenesse in https://github.com/MemPalace/mempalace/pull/1019
* fix: reject non-http(s) LLM endpoints + clear ruff bugbear/silent-except findings by @anthonyonazure in https://github.com/MemPalace/mempalace/pull/1138
* fix(mcp): retry _get_collection once on transient failure (#1286) by @igorls in https://github.com/MemPalace/mempalace/pull/1377
* docs: add 30-day expiry callout + ship 4 auto-save tools by @milla-jovovich in https://github.com/MemPalace/mempalace/pull/1391
* fix(repair): add --mode from-sqlite to recover palaces with corrupt HNSW (#1308) by @potterdigital in https://github.com/MemPalace/mempalace/pull/1310
* fix: harden Chroma repair preflight and rollback recovery by @mjc in https://github.com/MemPalace/mempalace/pull/1285
* fix(miner): harden Windows mine against ONNX bad_alloc + silent partial exits (#1296) by @igorls in https://github.com/MemPalace/mempalace/pull/1402
* fix: use configured collection in repair recovery paths by @mjc in https://github.com/MemPalace/mempalace/pull/1312
* fix(migrate): verify write roundtrip before bailout by @fatkobra in https://github.com/MemPalace/mempalace/pull/1359
* fix(storage): quarantine bloated HNSW link payloads by @fatkobra in https://github.com/MemPalace/mempalace/pull/1339
* fix(repair): preflight poisoned max_seq_id before rebuild by @fatkobra in https://github.com/MemPalace/mempalace/pull/1357
* fix(storage): quarantine partial HNSW flush without metadata by @fatkobra in https://github.com/MemPalace/mempalace/pull/1342
* fix(repair): preflight SQLite integrity before rebuild by @fatkobra in https://github.com/MemPalace/mempalace/pull/1364
* fix(repair): run SQLite integrity preflight before chromadb open (follow-up to #1364) by @igorls in https://github.com/MemPalace/mempalace/pull/1403
* fix(closet_llm): retry _call_llm on JSONDecodeError (#1155) by @igorls in https://github.com/MemPalace/mempalace/pull/1404
* fix(exporter): refuse symlinks at export targets (#1156) by @igorls in https://github.com/MemPalace/mempalace/pull/1405
* fix(diary): detect same-size edits via content hash (#925) by @igorls in https://github.com/MemPalace/mempalace/pull/1406
* fix(hooks): detach Popen children so hook exits cleanly on Windows (#1268) by @igorls in https://github.com/MemPalace/mempalace/pull/1412
* fix(mine): identify lock holder + exit non-zero on contention (#1264) by @igorls in https://github.com/MemPalace/mempalace/pull/1413
* fix(hooks): per-target PID guard with atomic claim (#1212, #1206) by @igorls in https://github.com/MemPalace/mempalace/pull/1415
* chore: housekeeping — sync 3.3.4 version + recommend uv for installs by @igorls in https://github.com/MemPalace/mempalace/pull/1414
* chore(deps): bump actions/configure-pages from 5 to 6 by @dependabot[bot] in https://github.com/MemPalace/mempalace/pull/1292
* feat(sync): add gitignore-aware drawer prune (#1252) by @mvalentsev in https://github.com/MemPalace/mempalace/pull/1421
* docs: clarify contributor git identity setup by @sjhddh in https://github.com/MemPalace/mempalace/pull/1385
* fix(miner): use token-boundary matching in detect_room by @coogie in https://github.com/MemPalace/mempalace/pull/1004
* fix(mcp): retry tool_search once on Chroma "Error finding   id" transient (#1315) by @JPdeB61 in https://github.com/MemPalace/mempalace/pull/1396
* fix(tests): use spawn instead of fork for lock-test subprocesses by @igorls in https://github.com/MemPalace/mempalace/pull/1431
* chore(tests): wrap sqlite3 connections in contextlib.closing by @igorls in https://github.com/MemPalace/mempalace/pull/1430
* fix(kg): accept ISO datetimes for temporal inputs by @fatkobra in https://github.com/MemPalace/mempalace/pull/1417
* chore(release): 3.3.5 by @igorls in https://github.com/MemPalace/mempalace/pull/1432
* chore(release): 3.3.5 by @igorls in https://github.com/MemPalace/mempalace/pull/1434

## New Contributors
* @imtylervo made their first contribution in https://github.com/MemPalace/mempalace/pull/1162
* @lcatlett made their first contribution in https://github.com/MemPalace/mempalace/pull/1305
* @Sathvik-1007 made their first contribution in https://github.com/MemPalace/mempalace/pull/1114
* @alonehobo made their first contribution in https://github.com/MemPalace/mempalace/pull/1060
* @eldar702 made their first contribution in https://github.com/MemPalace/mempalace/pull/1030
* @alpiua made their first contribution in https://github.com/MemPalace/mempalace/pull/987
* @hzx945627450-eng made their first contribution in https://github.com/MemPalace/mempalace/pull/1293
* @bobo-xxx made their first contribution in https://github.com/MemPalace/mempalace/pull/988
* @cantenesse made their first contribution in https://github.com/MemPalace/mempalace/pull/1019
* @anthonyonazure made their first contribution in https://github.com/MemPalace/mempalace/pull/1138
* @potterdigital made their first contribution in https://github.com/MemPalace/mempalace/pull/1310
* @mjc made their first contribution in https://github.com/MemPalace/mempalace/pull/1285
* @coogie made their first contribution in https://github.com/MemPalace/mempalace/pull/1004
* @JPdeB61 made their first contribution in https://github.com/MemPalace/mempalace/pull/1396

**Full Changelog**: https://github.com/MemPalace/mempalace/compare/v3.3.4...v3.3.5