A long-lived MCP server imports mempalace and chromadb once and serves from
those in-memory modules for the life of the process, so an upgrade on disk
mid-session never reaches it and it keeps accepting writes produced by code
the user no longer has installed.
Refuse mutating tools with JSON-RPC -32005 once a watched distribution's
installed version differs from the snapshot taken at import, or once it is
gone entirely. Reads stay available, mempalace_status reports
library_versions, and MEMPALACE_MCP_ALLOW_STALE_LIBRARY=1 opts out.
Both sides of the comparison come from installed metadata rather than a live
module.__version__. A distribution whose metadata cannot be read, or whose
search root will not open, is reported and left uncompared rather than
treated as removed: importlib.metadata suppresses the failure at both of
those levels, so either one would otherwise look exactly like an uninstall
and refuse every write on a healthy install.
The same module also memoizes each search root's listing against that root's
mtime, read in seconds where this fingerprint compares nanoseconds. An
upgrade whose removal and creation both land inside one timestamp tick would
then be answered from the listing taken before it, naming a dist-info that is
already gone; its version reads as empty, the distribution is left
uncompared, and nothing moves that mtime afterwards, so the gate would stay
off for it for the rest of the process. Drop the memo before each reading.
Watch chromadb only when chromadb is the backend serving. It is a hard
dependency rather than an extra, so it is installed even for a palace kept
in Postgres, and watching it there would refuse that user's writes whenever
chromadb alone was upgraded, over a library that writes nothing they own. A
backend that cannot be resolved keeps it watched.
Skip a sys.path entry carrying an embedded NUL. os.stat and os.listdir refuse
it during argument conversion, raising ValueError rather than the OSError
those callers hold; POSIX never gets there because realpath rejects it first,
but Windows resolves it and one such entry would end the whole reading.
The gate sits ahead of the diverged-index refusal added since (-32004, which
is why this one takes -32005). That gate's remedy is `mempalace repair
rebuild-index`, which would run the installed code against a palace this
process is still writing with the superseded one, so the restart instruction
has to be the one that reaches the client; the index probe re-runs per call
and surfaces immediately after a restart. Ordering it this way also skips
that gate's segment probe on a call already refused. Both directions of the
precedence are pinned by tests.
Co-Authored-By: messelink <274674234+messelink@users.noreply.github.com>
The default (legacy) repair path ignored --dry-run and ran the real
rebuild: it deleted any existing <palace>.backup, copied the live palace
over it, re-filed the drawers collection through a staged temp copy, then
rebuilt FTS5 and VACUUMed. #2095 and #2133 fixed this for
--mode from-sqlite only.
The preview now returns before ChromaBackend() is constructed. Opening a
chromadb client is itself a write to chroma.sqlite3, so a preview that
reached one could not be inert; the row count comes from
sqlite_drawer_count instead, the read-only SQLite ground truth
check_extraction_safety already trusts. Staying off the chromadb layer
also keeps a dry run clear of the layer repair is separately reported to
segfault in on a large palace (#2113).
resolve_repair_preflight_errors() decides what a dry run does about the
FTS5 autoheal. The autoheal is a write, so a preview must not run it, but
skipping it routed an isolated inverted-index error into the abort banner
and exit 1 - telling the operator to run offline sqlite3 .recover on a
palace a real run heals by itself (#1596). The dry run now classifies the
errors with the same _errors_are_isolated_fts5 predicate the real path
gates on and continues; broader corruption still aborts in both modes.
It is worded as an attempt rather than a promise, because the real heal
still gives up when another process holds the mine lock, when the rebuild
raises, or when quick_check is still dirty afterwards.
The plan describes the real run in execution order. It names the
live-collection delete the rebuild performs, since "re-file via a staged
temp copy" alone reads as additive; it warns when an existing backup
would be deleted; and it reports a no-op instead of a rebuild when the
collection holds no rows. It states the #1208 truncation guard that can
abort the run, and reports that guard as disabled when
--confirm-truncation-ok is set, because check_extraction_safety returns
immediately then and the promised abort would not happen. An unreadable
count fails closed with a non-zero exit, for parity with the from-sqlite
preview.
Narrows the 3.7.0 slice to the feature users actually get: RFC 003 agent
coordination (events, artifacts, patch handoffs) plus the multi-master
logstream sync that makes it work across machines.
Removed, deferred with the rest of RFC 004:
- replica_sync.py / vector_cache.py and the `mempalace replica` CLI
- the /snapshot/* hub endpoints they backed
- website/concepts/replicated-palace.md
Memory read replicas were a leaf on the dependency graph (nothing in the
logstream path imports them), and shipping them half-done meant documenting
a mesh whose memory does not actually converge. Dropping them lets the docs
say one true thing instead of two hedged ones: coordination syncs, memory
stays local, point every agent at one hub if you want shared recall.
Fixes a bug found by running it: _start_peer_sync_thread() read peers.json
once at startup and returned early when absent, so a hub started before
peers.json was written never synced — silently, forever. That is the order
the guide tells users to follow. Membership is now re-read every round.
Verified on two live hubs: delegation loop end-to-end, verbatim patch
round-trip by sha256, bidirectional sync, CLI sync alongside a live hub,
and automatic convergence 15s after writing peers.json with no restart.
The website pages arrived from the full RFC 004 branch and documented the
memory op-log, `mempalace oplog`, and automatic bidirectional memory
convergence. None of that is in this slice: the CLI has logstream, artifact,
and replica, and memory moves by one-way `replica pull`.
- cli.md: drop the `mempalace oplog` section (the command does not exist);
note that full convergence is staged.
- replicated-palace.md: split the sync table into shipping (coordination,
multi-master) vs next (memory ops, bidirectional); say plainly that two
machines capturing independently do not merge yet.
- shared-brain.md: mark step 3 a pull you re-run, not a converging loop.
- RFC 004: record which steps landed.
The rebase resolution had taken the branch version of both files wholesale,
dropping develop's backend table, version badge, mempalace_kg_supersede
section, and the checkpoint added_by row. Rebuilt as develop's content plus
only the RFC 003 logstream additions; tool count is 43 (36 + 7 logstream).
Every hub now advertises a self-derived node profile: roles
(replica/agents/compute), resolved accelerator + embedder, live drawer
count, hardware string, advertised_at. Every field is derivation from
what the daemon observes about itself — never configuration, so mesh
dashboards render what each machine reported, not what a UI guessed.
Profiles ride the existing sync surfaces: /sync/version_vector gains
additive profile/profiles fields, each round's stats carry them, and
carriers relay profiles for origins they only know transitively —
/sync/peers now serves per-peer profiles plus origin_profiles keyed by
replica id. Unreachable peers keep their last advertised profile.
New MCP tool mempalace_mesh_peers returns exactly the /sync/peers
payload from the same producer function, so the desktop app consumes
the estate through the existing bridge — one integration, one token
path, and the tool and endpoint can never drift. Tokens are never in
the payload, asserted in tests.
(cherry picked from commit f31d0904167f5463285ae78de335bcea304ed71a)
The docs ended at the single-hub logstream era; the mesh has since gone
from RFC to production. This brings the site current:
- NEW concepts/replicated-palace.md: one logical palace across N
machines — the availability invariant, the three layers, ops/HLC/
version vectors, gossip convergence, snapshot bootstrap + peers.json,
distributed embedding (sync the facts, derive the senses), the
/sync/peers estate surface, and the trust story (bearer tokens today,
mesh identity next). Status-honest: the memory op-log is documented
AS the dual-write shadow it currently is, with oplog verify named as
the cutover gate.
- reference/cli.md: mempalace replica (pull/embed-cache), mempalace
oplog (status/sync/fold/verify), logstream sync — with the
operational rules (hub-stopped pulls, quiescent origins,
MEMPALACE_SYNC_HTTP_TIMEOUT for big bootstraps).
- guide/shared-brain.md: new 'From hub to mesh' section — the
three-step join recipe and why the mesh removes the hub as a single
point of failure; See-also links updated.
- Sidebar: Replicated Palace under Concepts.
Example hostnames throughout; vitepress build clean.
(cherry picked from commit 63c2c3c20fcd350b8aca6b3249dd1a08ba5ecc22)
Reported by windows-codex through the logstream itself: wait rejected
'limit' with -32602 while list accepts it, though the filter sets are
otherwise identical. The core already threaded limit through; expose it
on the MCP schema and the CLI (wait --limit).
(cherry picked from commit bc18ccaac6ec709dbb0821f65c3a8e39f68733d1)
- website/concepts/agent-logstream.md: event/artifact model, delegation
loop, coordination-vs-memory table; added to the sidebar.
- website/reference/mcp-tools.md: schemas for the seven logstream tools;
tool count 35 -> 42 (README updated to match).
- website/reference/cli.md: mempalace logstream / mempalace artifact.
- website/guide/remote-server.md: hub doubles as agent coordination bus.
- integrations/shared/coordination-protocol.md: canonical shared-brain
protocol (identity, delegation loop, hard rules) with a copy-paste
system-prompt snippet; linked from recall-protocol.md and the
mempalace-recall skill so recall and coordination never blur.
(cherry picked from commit 1ff312511aef39abbe4638bdc23301266f87dbe6)
mempalace_checkpoint hard-coded added_by="checkpoint" for every drawer,
dropping the filing agent's identity even though it arrives in the same
call via diary.agent_name. Add an optional top-level added_by parameter
and resolve attribution as explicit > diary agent_name > "checkpoint";
blank/whitespace/non-string values defer to the next source. The value
is declared in the tool schema so tools/call admits it on both stdio and
HTTP transports.
Fixes#2023
Co-Authored-By: epinethrone <172391900+epinethrone@users.noreply.github.com>
Bump version to 3.5.0 across version.py, pyproject.toml, the Claude/Codex
plugin manifests, the README badge, and uv.lock. Refresh the "N MCP tools"
prose from 34 to 35 (delete_by_source #1729 and checkpoint #1851 each added a
tool). Add the 3.5.0 CHANGELOG entry.
Collapse the Cursor auto-save sequence (check_duplicate Nx + add_drawer
Nx + diary_write 1x) into a single mempalace_checkpoint MCP call so the
host UI renders one tool-call card and keeps its spinner up for the whole
save. The new tool reuses the existing single-item handlers, so semantic
dedup, idempotency, and verbatim guarantees are unchanged.
- mcp_server.py: add tool_checkpoint + register mempalace_checkpoint
- service.py: classify mempalace_checkpoint as a write tool
- cursor save hook: followup now drives one mempalace_checkpoint call
- docs: new mcp-tools.md section, help.md entry, 33 -> 34 tool count sweep
- tests: checkpoint add/dedup/malformed/registry + classify_tool
Co-authored-by: Cursor <cursoragent@cursor.com>
Adds an MCP tool to remove every drawer mined from a given source_file
exact match, for cleaning up benchmark/test data accidentally mined into
a user wing (ShareGPT dumps, results_mempal_*.jsonl, language config
JSON) that drowns out real memories in semantic search.
Matching is pushed to the backend via delete(where={"source_file": ...})
the same idiom the miner and diary-ingest paths already use so it is not
subject to the SQLite variable limit regardless of how many drawers share
the source. Defaults to a dry run reporting match count and a sample;
dry_run=false commits. Absent source is an idempotent no-op, not an error.
Address review feedback surfaced on the 3.4.1 release promotion (#1810).
Bug fix — `date -r FILE` is GNU-only. On BSD/macOS `date -r` expects
epoch seconds, not a path, so the staleness/throttle checks in the new
Cursor and Antigravity hooks silently failed on macOS: the state GC
swept on every fire and the pending-save guard was skipped. Replace
with a portable `os.path.getmtime` one-liner via the already-resolved
$MEMPAL_PYTHON_BIN (cursor/lib, antigravity/lib, antigravity save hook).
This restores the "bash 3.2.57 / macOS default" compatibility the
Antigravity changelog claims.
Docs:
- Correct the MCP tool count to 33 (was 19/29/31 in 21 places across
plugin manifests, READMEs, and website docs — all drifted from the
TOOLS dict / mcp-tools.md reference, which both have 33).
- Fix broken CHANGELOG link to the Cursor skill (skills/, not
.cursor-plugin/skills/).
- Fix one-too-many `../` in skills/mempalace/SKILL.md's cursor-hooks
link (resolved above the repo root).
- Add the required `mcpServers` wrapper to the mcp.json example in
.cursor-plugin/README.md so copy-paste yields a valid Cursor config.
Left intentionally unchanged: the os.dup2 fd-1 redirect in
mcp_server.py is deliberate (#225 keeps JSON-RPC off fd 1).
CI's test_no_undocumented_tools enforces that every tool registered
in the TOOLS dict has a corresponding section in mcp-tools.md.
The two hallway tools from b866f41 were missing — adding them here.
Sections mirror the format of the existing list_tunnels and
delete_tunnel entries directly above.
Expose mining as an MCP tool so clients that cannot shell out (Claude
Desktop, LM Studio, Aionui, Desktop Commander) can index projects,
conversations, or documents in-conversation, not only through the
`mempalace mine` CLI.
tool_mine is a synchronous wrapper over the existing miners (miner.mine,
convo_miner.mine_convos, format_miner.mine_formats) that cmd_mine already
calls, so it adds no new ingestion logic and no backend coupling. Miner
stdout is captured at the Python and file-descriptor level so it cannot
corrupt the JSON-RPC channel (#225); no Unix-only calls, so it works on
Windows. The miners keep the palace write lock, so a concurrent mine
returns a structured already-running error.
Add `mempalace sync` CLI command and `mempalace_sync` MCP tool that
prune drawers whose source files are gitignored, deleted, or moved
out of the project. Reuses the existing GitignoreMatcher
infrastructure in mempalace/miner.py so the same gitignore rules
that block ingest also drive the corresponding cleanup.
Closes#1252.
End-user installs now lead with `uv tool install mempalace`, with
`pip install mempalace` kept as a fallback. Dev/contributor docs lead
with `uv sync --extra dev` and `uv run` for tests/benchmarks/lint, with
the equivalent pip recipe kept inline. The shipped `/mempalace:init`
skill instructions (mempalace/instructions/init.md) try `uv tool install`
first when uv is on PATH, then fall back through the pip variants.
Adds a .python-version pin at 3.12 because the lockfile's
onnxruntime==1.24.3 only ships wheels for Python >=3.11; without the
pin, `uv sync` on a host where uv prefers 3.10 fails with no source
distribution available, which would make the documented command a
footgun. pyproject's `requires-python = ">=3.9"` is unchanged — pip
users on 3.9/3.10 are unaffected.
Files updated: README.md, CONTRIBUTING.md, CLAUDE.md, the gemini-cli
guide and example, the .claude-plugin / .codex-plugin READMEs, the
mempalace SKILL, the openclaw SKILL, tools/save.md, the three
benchmarks docs, and the corresponding website mirrors.
The MCP `mempalace_get_drawer` tool returned the entire raw drawer
metadata blob to any connected client, and the `source_file` field
in that blob is the absolute filesystem path written by the miners
(`miner.py`, `convo_miner.py` — `source_file = str(filepath)`). On
a single-user local deployment this is self-disclosure, but in
nested-agent or multi-server MCP topologies the client is a separate
trust domain and the host's directory layout has no documented
client-side use.
Mirror the mitigation that `searcher.search_memories()` already applies
on its own return path: reduce `source_file` to its basename via
`Path(source_file).name` before handing the metadata to the client.
Citations still work — the directory layout does not leak.
Companion to #1 (omit palace_path from tool_status). Same threat class,
different surface:
- mempalace_status — palace dir path → fixed in #1
- mempalace_get_drawer — per-drawer source_file path → this PR
Other read tools were audited and do not leak host paths:
- mempalace_search — already basenames source_file
- mempalace_list_drawers — returns wing/room/preview only
- mempalace_diary_read — date/timestamp/topic/content only
- mempalace_reconnect — success/message/drawers only
- mempalace_kg_* — entity/predicate strings, counts
- mempalace_check_duplicate — wing/room/preview only
Changes:
- mempalace/mcp_server.py: tool_get_drawer() now basenames metadata.source_file
- tests/test_mcp_server.py: regression test asserting the absolute path
and its parent directory do not appear anywhere in the response
- website/reference/mcp-tools.md: clarify the documented return shape
The MCP `mempalace_status` tool was returning the server's absolute
`_config.palace_path` to any connected client on both the main
(ChromaDB-backed) path and the sqlite fallback path that runs when
HNSW divergence is detected (#1222). On a single-user local deployment
this is self-disclosure, but in nested-agent or multi-server MCP
topologies the client is a separate trust domain and the absolute
path has no documented client-side use.
Clients that legitimately need the palace path continue to have three
documented channels: the `MEMPALACE_PALACE_PATH` env var (primary) or
its legacy `MEMPAL_PALACE_PATH` alias, the `~/.mempalace/config.json`
file, and the `--palace` CLI flag on most subcommands.
Also corrects stale docs that claimed `mempalace_reconnect` returned a
`palace_path` field; the code returns `{success, message, drawers,
vector_disabled[, vector_disabled_reason]}` on success, plus a no-palace
shape and an exception shape.
- mempalace/mcp_server.py: drop palace_path from tool_status() and
_tool_status_via_sqlite() result dicts
- website/reference/mcp-tools.md: update documented return shapes for
mempalace_status (fix) and mempalace_reconnect (stale-docs correction)
Authored-by: Aaron Salsitz (ICCI LLC, @icciaaron). Claude Code was used
as an authoring and review-orchestration tool, with human-in-the-loop
oversight at every step: Aaron wrote the prompts, reviewed each draft,
called for three independent review passes (drafting / post-rebase
technical / CISA-aligned disclosure-leak), and verified the final patch
behavior before commit.
- Landing: replace nonexistent `mempalace remember` CLI demo with real
`mempalace mine ./notes`
- Landing: soften unverifiable absolutes ("forever available",
"100% recall by design", "<50 ms", "90%+ compression",
"two-thousand-year-old", "tens of thousands of entries")
- MCP tool count: 19 → 29 across mcp-integration, claude-code, openclaw,
and modules; expand tool overview with Drawers, Tunnels, and System
categories to match mcp_server.py
- Wake-up token range: ~170–900 → ~600–900 in cli/api-reference/python-api
to match cli.py help text and concept docs
- Gemini CLI: move `--scope user` before target name and add `--`
separator so `-m mempalace.mcp_server` isn't parsed as Gemini flags
Fixes#210.
The CLI requires a positional <dir> argument. Previous docs emphasized
that init 'sets up ~/.mempalace/' which misled users into expecting
no arguments. Now the docs show <dir> is required, offer '.' as the
usage for the current directory, and reword the description so the
project-directory scan is listed first.
The regression-guard tests added in #835 were pinned to the old
README shape (tool table + file-reference table). When #897 slimmed
the README and moved that content to the website, three tests
started failing:
TestReadmeToolsExistInCode.test_every_readme_tool_exists_in_tools_dict
TestNoUnlistedTools.test_no_undocumented_tools
TestReadmeDialectNotLossless.test_readme_dialect_line_not_lossless
Changes in this commit:
1. Update the 3 tests to track the new canonical docs surfaces
- Tool list -> website/reference/mcp-tools.md
(tests parse `### \`mempalace_xxx\`` headings instead of
markdown table rows).
- dialect.py lossless disclaimer -> website/reference/modules.md
(any line mentioning dialect.py must not also say "lossless").
2. Fix the website to make "no undocumented tools" true
Add the 10 tools that existed in TOOLS but were missing from
website/reference/mcp-tools.md (create_tunnel, delete_tunnel,
follow_tunnels, list_tunnels, get_drawer, list_drawers,
update_drawer, hook_settings, memories_filed_away, reconnect).
Page header now correctly says "all 29 MCP tools".
3. Align pre-commit ruff pin to match CI (0.4.x)
.pre-commit-config.yaml was pinning ruff v0.9.0, while
.github/workflows/ci.yml installs ruff>=0.4.0,<0.5. The two
formatters produce incompatible output (e.g. v0.9.0 reformats
`assert (x), msg` -> `assert x, (msg)` in a way v0.4.x rejects),
which would cause the pre-commit hook to modify files that CI
then flags as unformatted. Pinning the hook to v0.4.10 keeps
the dev loop and CI in lock-step.
Full suite: 887 passed, 0 failed.
Part of #875. Bring the VitePress site into line with the new README
and the reproducibility scorecard: drop category-error comparisons,
drop retracted claims, retain only metrics and caveats that survive
audit.
website/index.md
- New tagline matches README (local-first, verbatim, pluggable backend,
96.6% R@5 raw, zero API calls).
- Replace the "MemPalace hybrid 100% / Supermemory ~99% / Mastra
94.87% / Mem0 ~85%" comparison table with a single honest table
showing MemPalace's own retrieval-recall numbers (raw 96.6%,
hybrid v4 held-out 98.4%). Add an explicit sentence explaining why
we no longer publish a cross-system table on the landing page
(retrieval recall vs QA accuracy are different metrics).
- Soften the "ChromaDB-powered vector search" feature blurb to be
backend-agnostic, since the retrieval layer is pluggable.
website/reference/benchmarks.md
- Full rewrite of the retrieval-recall tables. No more "100%"
headline; honest held-out 98.4% R@5 replaces it. Added the
model-agnostic rerank result (99.2% R@5 / 100% R@10 with
minimax-m2.7 via Ollama) to show the pipeline is not Haiku-specific.
- Drop the LoCoMo "Hybrid v5 + Sonnet rerank (top-50) 100%" row.
With per-conversation session counts of 19-32 and top_k=50, the
retrieval stage returns every session by construction — the number
measures an LLM's reading comprehension, not retrieval.
- Drop the cross-system comparison tables. Link out to each project's
own research page (Mastra, Mem0, Supermemory) for their published
numbers and metric definitions.
- Rewrite reproduction commands to use the correct repository and
demonstrate the new --llm-backend ollama flag.
website/concepts/the-palace.md
- Remove the "+34%" row / paragraph. Wing/room filtering is standard
metadata filtering in the vector store, not a novel retrieval
mechanism — the April-7 note already retracted that framing; this
finishes the retraction on the website where it had remained.
website/guide/searching.md
- Same treatment for "34% retrieval improvement". Reframe as
operational scoping, not a novel boost.
website/reference/contributing.md
- Update the "palace structure matters" bullet to reflect the same
framing: scoping-not-magic.
website/concepts/knowledge-graph.md
- Replace the MemPalace-vs-Zep feature matrix with a short "related
work" note that links to Zep's own documentation for authoritative
details on their deployment model. Avoids claims we cannot verify
at source.
Follow-up to #766 which covers version.py, pyproject.toml, README,
CHANGELOG, and CONTRIBUTING. These 11 files still had the old org
name in URLs:
- website/ (VitePress config + 6 docs pages)
- .claude-plugin/ (plugin.json repository, README marketplace command)
- .codex-plugin/ (plugin.json URLs, README links)
Author name fields are intentionally unchanged.