Skip to main content
mempalace-code

mempalace-code

Your AI's long-term memory. Local. Instant. Private.

Index your codebase once. Your AI can recall architecture decisions, debugging sessions, and API patterns across sessions and projects without re-reading the repo.

No cloud service, no API keys, no subscription. After the one-time embedding model download, indexing and search stay on your machine.


Get Started in 30 seconds · How It Works · All Features · Benchmarks


Language-Aware Mining
AST, regex, and adaptive chunking
matched to each file type
29 MCP Tools
Any MCP-capable agent
search, store, traverse · static profiles
Temporal Knowledge Graph
Facts that change over time
with validity windows
33.8x Token Savings
measured peak · median 14.5x
scales with project size
Cross-Project Tunnels
Search auth in one project
find it everywhere
3,000+ Tests · $0 Cost
Local test suite
offline after model setup

Quick Start

uv tool install mempalace-code        # recommended isolated install
# or
pipx install mempalace-code           # alternative
# or
pip install mempalace-code            # into current environment
# or
uvx --from mempalace-code mempalace-code --help  # try without installing

mempalace-code is the default command name so this fork can coexist with upstream/vanilla mempalace on the same machine. If mempalace is unused on your PATH and you want the shorter alias, run mempalace-code install-alias. The alias is a symlink that the package manager does not record, so remove it before uv tool uninstall mempalace-code or pip uninstall mempalace-code (the command only deletes a mempalace that points at mempalace-code):

alias_path="$(command -v mempalace)" && case "$(readlink "$alias_path")" in mempalace-code|*/mempalace-code) rm "$alias_path" ;; esac

If you already uninstalled, the dangling mempalace symlink is left in the directory that held mempalace-code (uv tool dir --bin prints it for uv); delete it there. Packaged installs use the Python import package mempalace_code, so they can coexist with vanilla MemPalace in the same Python environment. Source checkouts keep a small mempalace.mcp_server shim only so older repo-local MCP configs that run with PYTHONPATH=/path/to/mempalace-code continue to start.

Use docs/AGENT_INSTALL.md for a human-in-the-loop setup sequence. It covers installation, MCP wiring, supported Agent Plugin discovery, the unsupported-client stop boundary, and verification, and asks before choosing install scope, storage path, or model download.

Compatible Agent Plugins 1.0 clients can load the installed portable package. Discover it with:

mempalace-code agent-plugin path --json

Read the JSON path field and use that directory. It contains plugin.json, mcp.json, and skills/mempalace/SKILL.md. Its MCP config runs the installed mempalace-code-mcp --profile=minimal launcher, exposing only mempalace_status, mempalace_search, mempalace_check_duplicate, and mempalace_add_drawer by default. Use direct MCP registration, or edit a copy of mcp.json, when a client needs richer profiles such as --profile=kg, --profile=code, --profile=notes, or --profile=full.

Or do it manually
MEMPALACE_BIN="$(command -v mempalace-code)"
MEMPALACE_MCP="$(dirname "$(realpath "$MEMPALACE_BIN")")/mempalace-code-mcp"
"$MEMPALACE_BIN" init ~/projects/myapp --skip-model-download
# After explicit consent to download/cache the ~80 MB embedding model:
"$MEMPALACE_BIN" fetch-model
"$MEMPALACE_BIN" mine ~/projects/myapp
claude mcp add --scope user mempalace-code -- "$MEMPALACE_MCP"  # Claude user scope
codex mcp add mempalace-code -- "$MEMPALACE_MCP"                # Codex user config

Optional: auto-sync on commit (requires [watch] extra — see Auto-Watch):

"$MEMPALACE_BIN" watch ~/projects/         # re-mines on every commit, zero noise

This registers all 29 tools with the configured MCP client. To expose a reduced subset, add a --profile argv value (for example, -- "$MEMPALACE_MCP" --profile=minimal). Compatible Agent Plugins 1.0 clients should discover the supported instruction bundle with mempalace-code agent-plugin path --json and follow docs/AGENT_INSTALL.md Section 7. For other clients, docs/LLM_USAGE_RULES.md is read-only reference material; instruction-file mutation is unsupported.

Do you need it? Ask your LLM:

Read:
- https://github.com/rergards/mempalace-code/blob/main/README.md
- https://github.com/rergards/mempalace-code/blob/main/docs/AGENT_INSTALL.md
- https://github.com/rergards/mempalace-code/blob/main/docs/LLM_USAGE_RULES.md

Evaluate mempalace-code for this repo specifically, not generic memory tooling.

1. Current context burn: what do I re-explain, and how many tokens/week are spent re-reading files?
2. Highest-ROI surfaces: rank code, docs/prose, design notes, conversation/log exports, manual decisions, KG facts, architecture relationships.
3. Stack support: list supported languages/file types actually indexed; name unsupported extensions that are skipped by default. Distinguish tree-sitter AST, regex structural, Markdown/prose, Kubernetes YAML, and adaptive chunking.
4. Agent workflow fit: which MCP tools should you use proactively (`mempalace_search`, `mempalace_code_search`, `mempalace_file_context`, KG, tunnels, diary, manual drawers)?
5. Ops fit: recommend global MCP, project-scoped MCP, or no MCP; cover 29-tool prompt surface and profile subsetting options (`--profile`, `--tools`, `--exclude`), `mine` vs `mine-all`, `watch` cadence, backup/repair, and offline/model constraints.
6. Decision: install now, try scoped for a week, wait for a named feature, or skip. Give the first 3 commands you would run.
7. If waiting: ask me whether to draft a GitHub feature request.

For repos that already have a hand-curated memory file, evaluate mempalace-code as a complement, not a replacement. A good trial order is:

  1. KG first — move volatile current facts (active branch, phase, owner, deadline, status) into temporal triples; keep curated prose for reasoning and narrative.
  2. Drawers + docs second — mine design docs, specs, long decision notes, and conversation/log exports where semantic retrieval beats manual grep.
  3. Code mining last — start with one high-value subproject, then expand if agents actually use the results.

Cost caveat: a direct MCP registration without selectors defaults to all 29 tools; the portable Agent Plugin defaults to the four-tool minimal profile. Use --profile=minimal or --tools=search,add_drawer with direct registration to reduce the prompt/tool-surface cost. For proactive use, compatible Agent Plugins 1.0 clients load the package discovered by mempalace-code agent-plugin path --json as described in docs/AGENT_INSTALL.md Section 7. Other clients may use docs/LLM_USAGE_RULES.md as read-only reference material; instruction-file mutation is unsupported. Prefer project-scoped MCP for trials, and keep it only if searches, KG lookups, or drawer writes show up in real sessions.

Supported MCP Clients

mempalace-code works with any MCP-compatible client:

  • Claude Code — claude mcp add --scope user mempalace-code -- "$MEMPALACE_MCP"
  • Codex CLI — codex mcp add mempalace-code -- "$MEMPALACE_MCP"
  • Agent Plugins 1.0 clients — parse the JSON path field from mempalace-code agent-plugin path --json and load that directory
  • Claude Desktop — add the JSON entry below to claude_desktop_config.json
  • Cursor — add the JSON entry below to ~/.cursor/mcp.json (or a project's .cursor/mcp.json)
  • Windsurf — add the JSON entry below to ~/.codeium/windsurf/mcp_config.json
  • Any MCP client — point it at the resolved installed mempalace-code-mcp launcher

JSON-configured clients take an mcpServers entry. Merge it into the existing file rather than replacing the file. Print the absolute launcher path with echo "$(dirname "$(realpath "$(command -v mempalace-code)")")/mempalace-code-mcp":

{
  "mcpServers": {
    "mempalace-code": {
      "command": "/absolute/path/to/bin/mempalace-code-mcp",
      "args": ["--profile=minimal"],
      "env": { "MEMPALACE_PALACE_PATH": "/absolute/path/to/palace" }
    }
  }
}

Use absolute paths: clients do not expand ~ or read your shell profile. The server opens MEMPALACE_PALACE_PATH from this env object, else palace_path in ~/.mempalace/config.json, else ~/.mempalace/palace. A relative path in either setting would depend on the directory the client starts the server from, so every command warns about it on stderr. MCP tools never create a missing palace directory except mempalace_mine: with a mistyped path, writes and knowledge-graph tools answer No palace found exactly as mempalace_status and search do, so check the palace_path that mempalace_status reports. Omit env when the palace is the default or is already set in config.json; a variable exported only in your shell does not reach the client. Instead of minimal, choose --profile=kg, code, notes, or full from the MCP Server profile table below.

For local models without MCP support (Llama, Mistral, etc.), use mempalace-code wake-up to pipe context into the system prompt — see Memory Layers.


How It Actually Works

You write code. You make decisions. You debug things. Between sessions, all that context vanishes.

mempalace-code indexes it once into a local vector store, then an MCP-capable AI can retrieve it in milliseconds — using 33.8x fewer tokens than grep + read at measured peak (median 14.5x on the canonical fixture). Think of it as git log for everything that isn't in the code: the why, the discussions, the dead ends, the decisions.

What gets indexed or stored:

  • Code files — structural chunks for Python, TypeScript/JS/TSX/JSX, Go, Rust, Java, Kotlin, C#, F#, VB.NET, Swift, PHP, Scala, Dart, Lua, Terraform/HCL, Markdown, Kubernetes manifests, Helm charts/templates, and Ansible playbooks/roles/inventory; adaptive chunks for C/C++, Ruby, Perl, XAML, XML, shell (including extension-less scripts with a shebang), SQL, HTML/CSS, JSON/YAML/TOML, CSV, Dockerfile, Make, templates, and config files
  • .NET solutions — .sln/.csproj project graphs, cross-project symbol relationships, interface implementations; a solution is filed under the wing named after its root .sln file, which init writes to mempalace.yaml (a wing: you set there, or --wing, overrides it)
  • Architecture facts — pattern, layer, namespace, and project membership facts for .NET and Python projects
  • Conversation/log exports — Claude Code JSONL, OpenAI Codex CLI JSONL, Gemini CLI JSONL, Claude.ai JSON, ChatGPT conversations.json (the whole export array; each conversation's displayed branch), Slack JSON (channel folders; each message keeps its [speaker], export metadata such as users.json is not filed), plain text transcripts. Message text is never spell-corrected or reworded. Left out: Claude Code's injected wrappers and status lines, [Request interrupted by user] notice lines (text typed after one is kept), shell-mode (!) blocks, meta and compaction entries, inline subagent turns (a subagent's own agent-*.jsonl is filed), Slack join/leave-type events, and hidden or non-text ChatGPT parts. Claude Code tool output is abbreviated (long Bash output keeps its head and tail; Read/Edit/Write results are omitted), and an answered slash command is filed as the turn /name args
  • MCP-saved context — manual drawers and diary entries are vector-indexed in the palace (diary entries under wing_<agent> / room diary, one wing per agent however its name is spelled: claude-code and claude_code share one wing, the one already holding that agent's diary (such as wing_claude-code from an earlier release) or else wing_claude_code; also readable with mempalace_diary_read); temporal KG facts live in a separate SQLite graph
  • Architecture notes, decisions, anything else you store manually

Generated helper files such as entities.json are skipped during project mining by default, because they are created by init/entity detection and should not become source-code drawers unless explicitly force-included.

Secrets: mining stores every indexed file verbatim in the palace, and backups and exports copy it. Kubernetes kind: Secret manifests with data/stringData are skipped by default (the mine summary lists them; --include-ignored <path> indexes one anyway), but no other credential file is detected: keep .env files, credential JSON/YAML, and keys out of the palace with .gitignore or scan_skip_globs (in ~/.mempalace/config.json) before mining. Once a mined secret file is excluded, the next whole-project mine removes its drawers; backups and exports made earlier still hold it.

How you use it: An MCP-capable agent can call mempalace tools during a session when its client policy permits the calls. Compatible Agent Plugins 1.0 clients load the supported instruction bundle discovered by mempalace-code agent-plugin path --json; see docs/AGENT_INSTALL.md Section 7. Other clients treat docs/LLM_USAGE_RULES.md as read-only reference material because instruction-file mutation is unsupported. The CLI remains available for direct use.


Features

Language-Aware Code Mining

mempalace-code mine walks your source tree and chooses the best chunker for each file type: AST boundaries where optional tree-sitter grammars are available, regex structural boundaries for supported languages, YAML-aware Kubernetes/Helm/Ansible resource splits, Markdown/prose sections, or adaptive line-count chunks for formats without reliable declarations. The shared catalog currently exposes 45 searchable language labels to code_search(language=...). Leading comments and docstrings stay attached to declarations where structural chunking is active; Markdown drawers keep heading path, section type, and Mermaid/code/table flags in search metadata.

Every drawer is an exact slice of its source file with its line range, so read can return any mined line. Nothing inside a file is dropped: small blocks (imports, short config sections, one-line statements) merge into a neighbouring drawer, and small files are indexed whole. Each named declaration starts its own drawer and names it in symbol_name: every function, class, and type, and the methods of a class, impl, or trait body (a class keeps a drawer of its own for its header, docstring, and attributes), so a symbol_name filter finds each of them. A function defined inside another function stays in the enclosing function's drawer and is found through it. Only unnamed declarations such as constants under 400 characters together may share a drawer, and a file header such as an F# module A.B line, a Rust mod name; or a Lua module table names a drawer only when nothing more specific is in it. A method is typed method (Python, Rust, Go receivers, and TypeScript with tree-sitter). A declaration longer than 4,000 characters is split into several drawers that keep its symbol, and with tree-sitter, code after a declaration (top-level statements, class attributes after the methods) gets drawers of its own. A single line longer than 4,000 characters is stored in several drawers, and read joins them back into the whole line.

Language Strategy AST Support
Python Functions, classes, methods, decorators Optional tree-sitter
TypeScript / JavaScript / TSX / JSX (.ts, .mts, .cts, .js, .mjs, .cjs, .tsx, .jsx) Functions, classes, exports, imports Optional tree-sitter
Go Functions, types, methods, interfaces Optional tree-sitter
Rust Functions, structs, enums, traits, impls Optional tree-sitter
Java Classes, interfaces, methods, annotations Regex
Kotlin Classes, objects, functions, extensions Regex
Scala Classes, case classes, objects, traits, enums, functions, implicits, type aliases, generics Regex
Swift Classes, structs, enums, protocols, functions, properties, extensions, actors, async/await Regex
Dart Classes, mixins, extensions, enums, functions, named/factory constructors, async/await Regex
PHP Classes, interfaces, traits, enums (8.1+), functions, methods, namespaces (Laravel/WP/Symfony aware) Regex
C# Classes, interfaces, records, methods, properties Regex
F# / VB.NET Modules, types, functions Regex
XAML Indexed and searchable with x:Class view metadata; chunked adaptively today. Mining also records KG facts for code-behind links, view-model bindings, named controls, resources, and commands —
Terraform / HCL Terraform/HCL top-level blocks (resource, module, variable, moved, import, check, etc.); symbols use Terraform addresses (aws_instance.web, module.vpc, var.region). .tf/.tfvars files carry the terraform language label and .hcl files the hcl label Regex
Kubernetes manifests Deployments, Services, ConfigMaps, Ingresses, CRDs (indexed by kind/name); kind: Secret manifests with data are skipped unless force-included YAML-aware
Helm charts Chart.yaml, values*.yaml, raw templates with kind/name metadata; no template rendering YAML/Go-template aware
Ansible Playbooks, role tasks/handlers/defaults/vars, inventories; no Jinja evaluation or inventory semantics YAML/Jinja tolerant
Markdown / plain text Heading sections (#-######), heading paths, section metadata, paragraphs —
Lua Functions, local functions, methods (dot/colon), module/table declarations Regex
C / C++ Indexed and searchable with best-effort symbol metadata; chunked adaptively today —
Ruby Indexed and searchable with best-effort static symbol metadata (classes, modules, methods, singleton methods, attrs, constants); chunked adaptively today; Rails DSL/metaprogramming not interpreted —
shell / SQL / Perl / XML Indexed and searchable; chunked adaptively today. Extension-less scripts are indexed when their shebang names a known interpreter (bash/sh/zsh, Python, Node, Ruby, Perl); Perl packages and subs become symbols; .xml, .config, .props, and .targets are labelled xml —
HTML / CSS / CSV Indexed and searchable; chunked adaptively today —
YAML / JSON / TOML Adaptive line-count; Kubernetes YAML auto-detected separately —
Dockerfile / Make / templates / config Dockerfile, Containerfile, Makefile, GNUmakefile, Vagrantfile, Go templates, Jinja2, .conf, .cfg, .ini —

The mempalace_code_search language filter is generated from the same language catalog as the miner. If a file type is mined with a language label, the MCP schema and unsupported-language hints stay aligned with that catalog.

Tree-sitter is optional (pip install "mempalace-code[treesitter]"). When a grammar is missing, Python, TypeScript/JavaScript/TSX/JSX, Go, and Rust fall back to regex structural chunking. Other recognized formats use their regex, YAML-aware, prose, or adaptive chunker as listed above.

Extensions outside the miner catalog are skipped by normal project scans unless you explicitly force-include an exact path with --include-ignored path/to/file (project-relative, or an absolute path inside the project); the mine summary counts the skipped types, and an include path that does not exist is reported.

Files that would pollute search are skipped and named in the mine summary: binary content (NUL bytes or undecodable data, even with a source extension), files larger than max_file_bytes (1,000,000 bytes by default; set max_file_bytes: in mempalace.yaml, 0 for no cap), and minified or generated bundles (*.min.js, *.min.css, or script/stylesheet/JSON/HTML/XML files made of lines over 4,000 characters). An exact --include-ignored path indexes a large or minified file anyway. Files that start with a UTF-16 or UTF-32 byte-order mark are decoded as such. Other files that are not valid UTF-8 are decoded with their declared coding: cookie when present; otherwise the undecodable bytes are stored as U+FFFD and the file is reported. Empty files produce no drawers.

Mining indexes only ordinary readable regular files. Source-shaped FIFO, socket, character device, block device, symlink, and directory entries are rejected or skipped before their source content is opened. An ordinary regular file that cannot be read is reported as a read error; it is not necessarily reported as <path>: not a regular file (<kind>). Mining continues with other ordinary readable regular files, and its diagnostics are bounded and actionable. After replacing or removing the offending entry, run mempalace-code mine <dir> --full.

mempalace-code mine ~/projects/myapp                  # all supported file types
mempalace-code mine ~/projects/myapp --wing myapp     # tag with a specific wing (normalized; see Wing names)
mempalace-code mine ~/chats/ --mode convos            # mine conversation exports
mempalace-code mine-all ~/projects/                   # sync all projects incrementally (one wing per project)
mempalace-code mine-all ~/projects/ --new-only        # skip projects whose wing already exists (first-run only)

Mining is incremental by default — content-hash based, only changed files are re-chunked. Files whose drawers were written by an older chunker version are re-chunked automatically, and editing the rooms or architecture: block in mempalace.yaml re-routes unchanged files and reruns architecture extraction on the next mine. Use --full to force a rebuild. Either way, a mine that walks the whole project (no --limit) removes drawers of files that were deleted, moved, or are now excluded (including every file under a deleted or renamed directory) and reports how many it removed. Mining only replaces drawers it produced: manual (mempalace_add_drawer) and diary drawers are never deleted by a mine, even when their source_file names a mined file. Ctrl-C stops a mine after saving the pending batch (a second Ctrl-C skips that save; a Ctrl-C during a batch's embedding does not embed it again) and exits with status 130; the next mine resumes incrementally.

Conversation mining (--mode convos) first normalizes each transcript into user turns and answers, then keeps every line of that text with its line breaks: a drawer holds one user turn and its answer, a long answer continues in the next drawers, and an exchange too short to stand alone joins its neighbour. A transcript is filed however short it is; one with no text left after normalization is skipped and named. Plain-text transcripts are used as written (a leading UTF-8 byte-order mark is dropped) and mark user turns with > or User:/Assistant: prefixes. Normalizing a structured export (Claude Code, Codex, ChatGPT, Slack, ...) marks only the first line of each user turn with > and keeps the rest of the message as written, keeps each text block or part on its own line, summarizes tool calls and tool results, and drops Claude Code system injections. Text is stored verbatim; --spellcheck/--no-spellcheck are deprecated no-ops kept for compatibility. A transcript that changed (such as a session that keeps growing) is re-chunked, and nothing is backed up or optimized when nothing changed. An incremental mine keeps the drawers of transcripts that are no longer on disk, because tools prune old transcripts (Claude Code deletes sessions after cleanupPeriodDays) and the palace may hold the only copy. It prints how many there are and the command that removes them: a --full mine of the whole directory. Pass one transcript file instead of a directory to mine just that file; without --wing it keeps the wing that already holds that transcript (else the name of its folder). --extract general adds classified excerpts (rooms decisions, preferences, milestones, problems), each taken from within one exchange, next to the verbatim exchange drawers and never replaces them; a transcript with no such excerpt is read again on the next mine. The scan skips directories such as build, dist, env, venv, memory, and tool-results and lists the ones it skipped; mine such a directory directly to include it. A file that is not UTF-8 text or cannot be parsed is reported, counted, and retried on the next mine.

Multi-project wing naming — mine-all assigns one wing per project using this priority:

  1. wing: in the project's mempalace.yaml (explicit override)
  2. Git origin repo name, when the project directory is a repository root (e.g. my-repo.git → my_repo)
  3. Normalized folder name (also used for subdirectories of a monorepo)

init writes the wing from rules 2–3 into a new mempalace.yaml, so the mine-all --dry-run preview, init, and mine agree. mine-all mines only initialized projects; its output names the subdirectories it skipped because they have neither project markers nor mempalace.yaml, and the init command of each uninitialized project. When every project it found is uninitialized it mines nothing and exits 1 with the init command to run first. --force is deprecated and has no effect (it prints a notice). If two projects resolve to the same wing name, mine-all exits with an error before mining anything. Fix this by adding a unique wing: value to each project's mempalace.yaml. Use --new-only to skip projects already present in the palace (useful for first-run batch ingestion).

Wing names — init, mine, mine --wing, and mine-all apply one rule. The name is lowercased, whitespace becomes _, and every character other than a letter or digit (of any script), _, or - is dropped: "Foo Bar (v2)!", "foo bar v2", and FOO_BAR_V2 all name foo_bar_v2, while café and 日本語 stay as written. Names derived from a folder, git origin, or .sln (and every wing of a dotnet_structure project) also turn - into _; a name with no letter or digit falls back to project. A blank --wing, a --wing containing /, \ or a control character, and a blank --agent exit 2 before the palace is touched. A new wing that differs from an existing wing only by case, spacing, or punctuation (other-wing when other_wing exists) exits 2 and prints the mine command that files under the existing wing, as diary write --wing and mempalace_add_drawer do; a --wing that names an existing wing exactly (for example one an older release stored verbatim) keeps filing into it.

Re-running init never replaces an existing mempalace.yaml: it reports the file and keeps it, so hand-edited wings, rooms, and architecture: blocks survive. init <dir> --force regenerates the rooms while keeping the existing wing: and every other key, after copying the old file to mempalace.yaml.bak; follow it with mine <dir> --full to re-file existing drawers into the new rooms. Rooms are proposed only for directories mine traverses (symlinked, generated, scan-excluded, and .gitignored directories are skipped); in a .NET repository, code outside every .csproj project keeps folder-based rooms (a root-level .csproj covers the whole tree, so it gets no folder rooms). With a legacy mempal.yaml, --force writes a new mempalace.yaml and leaves the legacy file in place.

Optional Entity Detection

mempalace-code init <dir> is config-first by default: it detects rooms from the directory structure and does not scan file contents for names. Add --detect-entities only when the directory contains prose where people or project names matter, such as meeting notes, client notes, personal notes, or conversation exports:

mempalace-code init ~/notes --detect-entities        # prompts to confirm detected people/projects
mempalace-code init ~/notes --detect-entities --yes  # auto-accept entity confirmation (no room prompts)

When stdin is not a terminal (agents, CI, < /dev/null), --detect-entities auto-accepts detected people and projects as with --yes and skips uncertain names. --interactive room review needs a terminal: without one, init exits 2 before writing anything; --yes overrides it.

The detector is a lightweight bootstrap step, not the main miner. It samples up to 10 readable files, prefers prose files (.md, .txt, .rst, .csv), reads the first 5 KB of each sampled file, and considers only capitalized names that appear at least 3 times in that sample; its own entities.json and mempalace.yaml are never sampled. It classifies each name from heuristic signals such as Alice said, thanks Bob, Apollo repo, deploy Apollo, or import Apollo. A person needs at least two kinds of person signals (for example Alice said and thanks Alice); a name with fewer or mixed signals is listed as UNCERTAIN. Interactive confirmation lets you keep an uncertain name, while --yes accepts only the confident people and projects. Confirmed results are written to <dir>/entities.json. When a re-run keeps an existing mempalace.yaml, an existing entities.json is kept too; --force replaces it. For example:

{
  "people": ["Alice", "Bob"],
  "projects": ["Apollo"]
}

Use it for human/project context. Leave it off for normal code repos unless their docs contain the entities you want captured. Full-repo scanning would be slower and noisier: class names, packages, examples, and variables often look like people or products to a heuristic pass. Code structure, symbols, languages, and architecture relationships are handled by mempalace-code mine, not by entity detection.

Auto-Watch

Keep your palace in sync automatically. By default, watches git metadata — branch refs plus each checkout's HEAD reflog — and re-mines only when a commit, merge, rebase, reset, or branch checkout moves the checkout; no noise from work-in-progress saves. Normal checkouts, linked worktrees (git worktree add), and reftable repositories are supported. A linked worktree files into the wing its mempalace.yaml names, which is the main checkout's wing when that file is committed. A project without git needs --on-save: the watcher names each such project, and when none has git it exits 1 before any backup or mine with the exact --on-save command.

Requires the watch extra:

uv tool install "mempalace-code[watch]"   # or: pipx install "mempalace-code[watch]"

Already installed without it? Re-run the uv install with the extra. uv replaces the tool's extras, so list every extra you use (for example "mempalace-code[watch,treesitter]"). pipx can add watchfiles to the existing environment instead:

# List every extra you already use; uv drops the ones you leave out.
uv tool install --force "mempalace-code[watch,treesitter]"   # or: pipx inject mempalace-code watchfiles
mempalace-code watch ~/projects/my-app                # watch an initialized project (on commit)
mempalace-code watch ~/projects/                      # watch all initialized projects in a parent directory
mempalace-code watch ~/projects/ --on-save            # watch all file saves instead (noisier)
mempalace-code watch ~/projects/ schedule             # print launchd/cron snippet for daemon

watch accepts either an initialized project directory (has mempalace.yaml) or a parent directory containing immediate initialized project subdirectories. Pointing it at a project root that has project files but no mempalace.yaml exits with the correct mempalace-code init <dir> command, and a path to a file exits with the project directory to watch instead. In a parent directory, uninitialized children are named with their init command and skipped; projects initialized after the watcher starts are picked up when it restarts. One watcher watches a given project into a given palace: a second watcher skips that project, and exits 1 when nothing is left to watch.

Every re-mine cycle is logged, including one that only swept deleted files; in every mode the line names the sources the cycle swept (deleted source(s) swept: src/old.py). In --on-save mode, renaming a directory or moving one into or out of a project starts a cycle that indexes the new paths and sweeps the old ones. A failed cycle is reported with what mine printed, and the watcher keeps running. A watched project that is removed, moved, or loses its mempalace.yaml is named and no longer watched (WATCH_RUN ... state=project-dropped); its drawers stay in the palace, and when no project remains the watcher exits 1 (state=stopped reason=no-projects). A storage error caused by another process writing the same palace (a missing fragment on a stale handle, a commit conflict) is retried on a fresh handle while the palace head stays readable. The watcher never rolls a shared palace back on its own; see Degraded Startup Recovery.

Startup resolves and validates each watcher source root as a directory before creating a pre-watch backup. Nested non-regular or unreadable source entries follow the mining contract above: other ordinary readable regular files continue through the mine, and such an entry does not by itself abort or restart the watcher. A watcher run also reuses one warmed store and embedding-model lifecycle across remine cycles; regression tests bound post-warm-up RSS, file descriptors, archive retention, disk growth, and SIGINT shutdown.

Install as persistent daemon (macOS):

mempalace-code watch ~/projects/ schedule    # prints the plist; stderr prints its exact save and load commands
# For example (the label comes from the root's name and path):
mempalace-code watch ~/projects/ schedule > ~/Library/LaunchAgents/com.mempalace.watch.projects-1a2b3c4d.plist
launchctl load ~/Library/LaunchAgents/com.mempalace.watch.projects-1a2b3c4d.plist

Starts at login, restarts if crashed. Each watch root gets its own job, com.mempalace.watch.<root-name>-<hash>, so scheduling a second root adds a job instead of replacing the first, and re-rendering a root replaces only its own job. The daemon command keeps --palace, --on-save, --agent, and --no-gitignore. schedule exits 1 instead of printing a job that would crash-loop under KeepAlive: a missing path, a file, an uninitialized root, a commit-mode root without git, or an installation without the watch extra. The plist carries EnvironmentVariables for HF_HOME and every MEMPALACE_* setting of the rendering shell, so re-render after changing them. The daemon runs offline (HF_HUB_OFFLINE=1 unless you set it) and never downloads the model; run mempalace-code fetch-model first (schedule warns when the model is not cached). It gets 120 seconds (ExitTimeOut) to finish an in-flight re-mine after SIGTERM, and logs to the per-user file ~/Library/Logs/<label>.log, which launchd appends to without rotation. On Linux the cron @reboot line carries the same settings as leading NAME=value assignments.

A job rendered by an earlier release keeps the fixed label com.mempalace.watch and logs to /tmp/mempalace-watch.log; watch <root> status still finds it when it watches that root. To move it to a per-root job, unload and remove it, then save and load the newly rendered plist. schedule warns, with the unload command, when a saved job already watches the same root, a parent, or a child into the same palace: a second watcher of a project refuses to start. watch <root> status reads the loaded job's arguments, or else the saved plist, or else its own options, and uses that job's palace and mode: a commit-mode root without git is reported Runnable: no with the --on-save command. Its re-render hint keeps --palace, --on-save, --agent, and --no-gitignore and writes to a temporary file first (... schedule > <plist>.tmp && mv <plist>.tmp <plist>), so a refused render never empties the saved plist.

Disk-budget guard: the daemon automatically skips mine/optimize cycles when free disk space falls below the configured floor (default 1 GiB). Use watch status to check the current state:

mempalace-code watch ~/projects/ status      # disk budget, root validity, and this root's job (label, state, log, stop command)

To pause or stop the daemon when disk is low (use the label watch status prints):

launchctl unload ~/Library/LaunchAgents/<label>.plist   # stop until next login
launchctl load   ~/Library/LaunchAgents/<label>.plist   # re-enable after freeing space

Diagnosing and stopping a crash-looping job:

A job whose root was later removed or de-initialized, or one rendered by an earlier release without the render-time checks, can crash-loop under KeepAlive. Confirm with:

mempalace-code watch ~/projects/ status   # shows state, runs count, and last exit code

Stop and optionally remove it:

# macOS 10.11+ preferred — unregisters the job immediately (status prints this line):
launchctl bootout gui/$(id -u)/<label>

# Older macOS / alternative:
launchctl unload ~/Library/LaunchAgents/<label>.plist

# Remove permanently (re-install after fixing the watch root):
rm ~/Library/LaunchAgents/<label>.plist

Daemon health check:

The daemon emits WATCH_RUN lines at each startup transition, and when a cycle fails, a project is dropped, or the watcher stops, so the job's appended log (watch status prints its path) can be searched to confirm the latest startup reached the watch loop. The full state list is in Startup State Markers. Use this sequence to diagnose daemon state:

# 1. Process state, log path, and stop command for this root's job
mempalace-code watch ~/projects/ status
# Or query launchd directly with the label status printed:
launchctl print gui/$(id -u)/<label>

# 2. Palace storage health
mempalace-code --palace ~/.mempalace/palace health

# 3. Find the latest startup that reached watch-ready (LOG is the path status printed)
grep -a 'state=watch-ready' "$LOG" | tail -1
# Example output: WATCH_RUN run_id=20260616T120102Z-p12345 state=watch-ready

# 4. Filter that run's context (replace the run_id from step 3):
grep -a 'run_id=20260616T120102Z-p12345' "$LOG"

A log file may contain WATCH_RUN lines from older runs that exited with disk-budget or backup failures. The run_id on the latest state=watch-ready line identifies the current healthy startup — lines from prior runs with different run_id values are stale and can be ignored. If no watch-ready line appears, check the most recent run-started line and the state that followed it (for example state=preflight-failed, state=pre-watch-backup-failed, or state=initial-mine-skipped reason=disk-budget).

Configure the threshold via environment variable or ~/.mempalace/config.json:

# Environment variable (bytes or human suffix)
export MEMPALACE_WATCH_DISK_MIN_FREE_BYTES=2GiB    # watcher-specific floor
export MEMPALACE_DISK_MIN_FREE_BYTES=1GiB          # global floor (watcher + backup)

Or set byte values in ~/.mempalace/config.json. This example keeps the 1 GiB global default and raises the watcher and backup floors to 2 GiB each. The file must be a plain JSON object without comments; a file that fails to parse, or whose top level is not an object, is ignored as a whole with a one-line warning. A single value that cannot be parsed (for example "lots") is reported on stderr and the next setting in precedence order applies instead.

{
  "disk_min_free_bytes": 1073741824,
  "watch_disk_min_free_bytes": 2147483648,
  "backup_disk_min_free_bytes": 2147483648
}

MCP read/search behavior is not affected by a paused watcher — agents can still search the palace while the daemon waits for disk space.


The Palace

mempalace-code organizes memories into a navigable structure — the same mental model ancient Greek orators used to memorize speeches.

  ┌─────────────────────────────────────────────────────────────┐
  │  WING: myapp                                               │
  │    ┌──────────┐            ┌──────────┐                    │
  │    │  backend │            │  frontend│                    │
  │    └────┬─────┘            └──────────┘                    │
  │         ▼                                                  │
  │    ┌──────────┐      ┌──────────┐                          │
  │    │  Closet  │ ───▶ │  Drawer  │  (verbatim content)     │
  │    └──────────┘      └──────────┘                          │
  └─────────┼──────────────────────────────────────────────────┘
            │ tunnel (auto-created when room names match)
  ┌─────────┼──────────────────────────────────────────────────┐
  │  WING: otherapp                                            │
  │    ┌────┴─────┐            ┌──────────┐                    │
  │    │  backend │            │  infra   │                    │
  │    └──────────┘            └──────────┘                    │
  └─────────────────────────────────────────────────────────────┘
Concept What it is
Wing A project, person, or domain. As many as you need.
Room A topic within a wing: backend, auth, deploy, decisions.
Drawer Verbatim content. Never summarized, never rewritten.
Hall Optional hall label on a drawer. Only diary entries set one today (hall_diary); mined drawers leave it empty, so graph tools list halls only for diary rooms.
Tunnel Auto-connection between wings when the same room name appears.

MCP Server — 29 Tools

Agent Plugins-compatible clients can discover the portable package with:

mempalace-code agent-plugin path --json

That package declares the installed stdio launcher mempalace-code-mcp --profile=minimal. This is the portable default for low tool-schema cost. Direct MCP registrations below remain supported for the full surface or richer startup profiles.

MEMPALACE_BIN="$(command -v mempalace-code)"
MEMPALACE_MCP="$(dirname "$(realpath "$MEMPALACE_BIN")")/mempalace-code-mcp"
claude mcp add --scope user mempalace-code -- "$MEMPALACE_MCP"

The MCP server registration name defaults to mempalace-code. The MCP tool identifiers remain mempalace_* for compatibility with existing agents and usage rules.

Protocol compatibility: the server speaks both the stable 2026-07-28 protocol revision (via server/discover and per-request metadata) and the legacy initialize-based handshake, negotiated automatically per request — existing python -m mempalace_code.mcp_server registrations (and the source-checkout mempalace.mcp_server shim) keep working unchanged; there is nothing to reconfigure.

Direct registration without a selector exposes all 29 tools. The portable Agent Plugin above selects minimal and exposes four. Use startup flags to reduce the direct tool surface (GitHub issue #6 — static profiles lower prompt cost while preserving stable named-tool trigger patterns in usage rules):

# Named profiles — select a pre-defined subset at server startup
claude mcp add --scope user mempalace-code -- "$MEMPALACE_MCP" --profile=minimal
claude mcp add --scope user mempalace-code -- "$MEMPALACE_MCP" --profile=kg
claude mcp add --scope user mempalace-code -- "$MEMPALACE_MCP" --profile=code
claude mcp add --scope user mempalace-code -- "$MEMPALACE_MCP" --profile=notes

# Explicit tool list (replaces profile base set)
claude mcp add --scope user mempalace-code -- "$MEMPALACE_MCP" --tools=search,add_drawer,diary_*

# Add or remove tools from a profile
claude mcp add --scope user mempalace-code -- "$MEMPALACE_MCP" --profile=minimal --include=kg_query
claude mcp add --scope user mempalace-code -- "$MEMPALACE_MCP" --profile=full --exclude=delete_wing,delete_drawer
Profile Tools Best for
full (default) all 29 Full capability; no surface reduction
minimal 4 Search + store only
kg 8 Minimal + temporal knowledge graph
code 11 Code archaeology; no drawer-write/diary tools (mine included)
notes 12 Knowledge management + diary; no code-search

Selector rules for --tools, --include, --exclude:

  • Accept full names (mempalace_search), short names (search), or wildcards (diary_*).
  • --tools replaces the profile base set; cannot be combined with --include.
  • --include adds to the profile base set; --exclude removes last (wins over everything).
  • Invalid profile name, unknown selector, a selector flag given an empty value (--tools=, --exclude=""), or empty result → process exits with nonzero status and a stderr message.

An explicit wing/room filter passed to CLI search/read or the MCP search/read/architecture tools is validated against the palace taxonomy before retrieval runs (when the palace has a readable, non-empty taxonomy to validate against). A valid scope with zero matches is still a normal success (results: [], CLI exit status 0); an unknown wing, room, or wing/room pair returns a structured {error, filter, value, suggestions} validation error instead (CLI exit status 2), with up to 3 advisory suggestions that are never auto-applied. See docs/HOW_SEARCH_WORKS.md for the full contract, including when validation is skipped.

A malformed MCP call is bounded, not fatal. Arguments that are not an object, undeclared argument names, type mismatches, missing or blank required arguments, numbers outside a schema minimum/maximum, text longer than a schema maxLength (a search query over 10,000 characters, drawer or diary text over 100,000), a tools/call without a tool name, text with an unpaired surrogate escape (\ud800), and values a tool cannot use (a malformed date, an inverted validity window, a path-like wing) are each rejected with JSON-RPC -32602 naming exactly what was wrong — correct those arguments and retry the same tool. Numeric arguments also accept a numeric string ("3"); an integer argument accepts only integral values (3, 3.0, "3", not 2.5). A line that is not valid JSON — including invalid UTF-8, a number with more digits than Python parses, or nesting deeper than its recursion limit — returns -32700 with a null id, and an unknown tool name returns -32601. None of these end the session: the server answers and keeps reading, so the next valid request is served normally and no restart is needed. A message without an id is a JSON-RPC notification: it is never answered, and anything but notifications/* is ignored with a stderr warning rather than run. ping returns an empty result. A tool that reports a failure in its payload ({"error": ...} or {"success": false, ...}) returns it with isError: true; -32000 means an internal server fault. mempalace-code-mcp --version prints the launcher version.

Palace — Read
Tool What
mempalace_status Palace overview — total drawers, wings, rooms
mempalace_list_wings All wings with drawer counts
mempalace_list_rooms Rooms within a wing
mempalace_get_taxonomy Full wing → room → count tree
mempalace_search Semantic search with optional wing/room filters (limit 1–50), in descending similarity; every hit carries its drawer id, and Markdown hits include heading path and section metadata
mempalace_code_search Filter by language, symbol name/type, file glob (all applied before ranking); optional rerank="hybrid"
mempalace_file_context Indexed chunks for a source file, ordered by chunk_index and paged with limit/offset; resolves paths like mempalace_read
mempalace_read Surgical read of stored source lines in a range for a file; use after a search/file_context hit to avoid reading the whole file; reports gaps, out_of_range, and no_line_metadata (conversation drawers)
mempalace_check_duplicate Similarity check before filing (duplicate at cosine similarity ≥ 0.9 by default; threshold must be -1 to 1)
Palace — Write
Tool What
mempalace_add_drawer File verbatim content into a wing/room; refuses a near-identical drawer (similarity ≥ 0.9 and no new number or identifier) and a new wing/room name that only re-spells an existing one (names are trimmed and case-sensitive)
mempalace_delete_drawer Remove a drawer by ID
mempalace_delete_wing Delete all drawers in a wing
mempalace_mine Trigger re-mining of a project directory (incremental or full)
Knowledge Graph
Tool What
mempalace_kg_query Entity relationships with time filtering (paged: limit/offset, total, truncated, next_offset)
mempalace_kg_add Add a fact with optional validity window; reports created and other current values
mempalace_kg_invalidate Mark a fact as no longer true; success: false when no open fact matched
mempalace_kg_timeline Chronological story of an entity (paged: limit/offset, total, truncated)
mempalace_kg_stats Graph overview
Architecture Retrieval
Tool What
mempalace_find_implementations Find all types implementing a given interface
mempalace_find_references Find all usages of a type (implementors, subclasses, deps)
mempalace_show_project_graph .NET project-level dependency graph, optionally filtered by solution (an unknown solution returns unknown_solution with suggestions; paged per predicate: limit/offset, totals, next_offset)
mempalace_show_type_dependencies Inheritance/implementation chain (ancestors + descendants)
mempalace_explain_subsystem Explain how a subsystem works: semantic search + KG expansion
mempalace_extract_reusable Classify deps as core/platform/glue; identify extraction boundary
mempalace_kg_query (entity="Service", direction="incoming") Show all services in the project
mempalace_kg_query (entity="Data", direction="incoming") Show all types in the data layer
Navigation & Diary
Tool What
mempalace_traverse Walk the graph from a room across wings (the catch-all general room is not a node)
mempalace_find_tunnels Find rooms bridging two wings (unknown wings return unknown_wing)
mempalace_graph_stats Graph connectivity overview
mempalace_diary_write Write a session journal entry (agent_name defaults to the server's MEMPALACE_AGENT_NAME)
mempalace_diary_read Read an agent's recent diary entries from any wing, including CLI diary write --wing entries; identity ignores case, spaces, hyphens and underscores

MCP-capable clients discover the registered tools. Compatible Agent Plugins 1.0 clients get the supported instruction bundle from mempalace-code agent-plugin path --json; follow docs/AGENT_INSTALL.md Section 7. Other clients may consult docs/LLM_USAGE_RULES.md as read-only reference material, including its profile-scoped routing guidance. Instruction-file and system-prompt mutation are unsupported.


Knowledge Graph

Temporal entity-relationship triples — local SQLite, no Neo4j, no cloud.

from mempalace_code.config import MempalaceConfig
from mempalace_code.knowledge_graph import open_palace_kg

palace_path = MempalaceConfig().palace_path  # or any palace directory
kg = open_palace_kg(palace_path)  # <palace>/knowledge_graph.sqlite3, shared with MCP and the CLI
kg.add_triple("myapp", "uses", "Postgres", valid_from="2025-11-03")
kg.add_triple("myapp", "uses", "Redis",    valid_from="2026-01-15")

kg.query_entity("myapp")                    # → Postgres (current), Redis (current)
kg.query_entity("myapp", as_of="2025-12-01")  # → Postgres only

kg.invalidate("myapp", "uses", "Postgres", ended="2026-03-01")  # fact expired

Entity names are case-insensitive: LanceDB and lancedb are one entity, shown with the spelling first stored (add_entity sets it). Predicates are stored lowercase with underscores. A dated ended is an inclusive end-of-day bound, so to replace a value that changes today, invalidate without ended (or pass the day before the new valid_from) before adding the new value.

Good candidates: version numbers, team assignments, tech stack choices, deployment states, deadlines.

Architecture extraction — mempalace-code mine automatically emits higher-level KG facts for .NET and Python projects after each mine:

Predicate Example Query
is_pattern UserService → is_pattern → Service kg_query(entity="Service", direction="incoming")
is_layer UserRepository → is_layer → Data kg_query(entity="Data", direction="incoming")
in_namespace UserService → in_namespace → Company.App kg_query(entity="UserService")
in_project UserService → in_project → myapp kg_query(entity="myapp", direction="incoming")

Default patterns: Service, Repository, Controller, ViewModel, Factory. Default layers: UI (*.UI, *.Web, *.Presentation), Business (*.Application, *.Domain), Data (*.Data, *.Persistence), Infrastructure (*.Infrastructure). A namespace glob also matches sub-namespaces (*.Infrastructure matches Shop.Infrastructure.Email); a glob matching the whole namespace wins first, and namespace placement beats the type-suffix fallback.

Re-mining a project refreshes architecture facts for that project's wing only, so a multi-project palace can update one repo without expiring facts from another. Unchanged facts keep their rows; only facts that are no longer derived expire. Python depends_on facts name modules by their dotted import path (shop.web.models); run mempalace-code mine <dir> --full once after upgrading to replace older facts that used the bare file name.

Customize the rules with the architecture: block in mempalace.yaml. A patterns: or layers: list replaces the whole default list, so re-list every default rule you want to keep:

architecture:
  enabled: true
  patterns:
    - name: Service
      suffixes: [Service]
      type_names: [AuditHandler]   # explicit names bypass suffix matching
    - name: Repository
      suffixes: [Repository]
    - name: Controller
      suffixes: [Controller]
    - name: ViewModel
      suffixes: [ViewModel, VM]
    - name: Factory
      suffixes: [Factory]
  layers:                          # omit to keep the default layers
    - name: UI
      namespace_globs: ["*.UI", "*.Web", "*.Presentation"]
      type_suffixes: [Controller, ViewModel]
      priority: 1
    - name: Business
      namespace_globs: ["*.Application", "*.Domain", "*.Audit"]
      type_suffixes: [Service]
      priority: 2
    - name: Data
      namespace_globs: ["*.Data", "*.Persistence"]
      type_suffixes: [Repository]
      priority: 3
    - name: Infrastructure
      namespace_globs: ["*.Infrastructure"]
      priority: 4

Set enabled: false to disable the pass entirely.


Memory Layers

Layer What When
L0 Identity — the text you write in ~/.mempalace/identity.txt (project, persona) Always loaded (aim for ~100 tokens; capped at 800 characters)
L1 Essential story — up to 15 drawer snippets: recent diary entries and manual notes first, then decision rooms, conversations, and project files Always loaded (~500–800 tokens)
L2 Room recall — current topic On demand (search --wing/--room, MCP tools)
L3 Deep search — full semantic query On demand (search)
mempalace-code wake-up --wing myapp    # emit L0 + L1 context (~600–900 tokens)

L1 ranks drawers by kind and then by filing time, most recent first; raw JSON/XML fragments are skipped and snippets are verbatim drawer text. It does not include knowledge-graph facts — query those with mempalace_kg_query. wake-up writes only the context to stdout and the token estimate (plus any warning) to stderr, so mempalace-code wake-up --wing myapp > context.txt or a pipe carries nothing else. A missing palace exits 1 and a blank --wing exits 2.

For local models (Llama, Mistral) that don't speak MCP, pipe wake-up into the system prompt.

AAAK Summaries (compress)

mempalace-code compress [--wing W] prints a lossy AAAK summary — entity codes, topic words, one key quote, flags — for each drawer, next to the drawer id it summarizes. It is read-only: drawers keep their verbatim text, nothing is stored, and read, search, export, and wake-up never return AAAK. Use the printed summaries wherever you want compact context. Drawers whose summary would not be shorter are skipped and counted, and Total uses the same token estimates as each row.

--config <file> loads entity codes from the entities.json that init --detect-entities writes ({"people": [...], "projects": [...]}) or from {"entities": {"Alice": "ALC"}, "skip_names": [...]}; a missing or invalid file exits 2. No entities.json is loaded unless you pass --config.

Releases before 1.15.0 overwrote drawers with AAAK in place. compress reports such drawers; restore their verbatim text from the backup archive that the old command created before writing (mempalace-code backup list shows it):

mempalace-code compress --recover-from <archive> --dry-run   # show what the archive can restore
mempalace-code compress --recover-from <archive>             # restore those drawers' text

Recovery replaces only drawers that still hold AAAK text and leaves later writes alone. Drawers the archive cannot restore are listed and the command exits 1, with or without --dry-run: retry with an older archive, or rebuild the drawers from their sources, with mempalace-code mine <project-dir> --full for project files and mempalace-code mine <conversations-dir> --mode convos for conversations.


Concurrent Writers and Long-Running Servers

One palace can serve an MCP server, a watcher, CLI mines, and the hooks' background conversation mines at the same time:

  • Writers take turns. Each mine (project or --mode convos), each mine-all project, each watcher re-mine cycle, and each optimize or cleanup holds a per-palace write lease (~/.mempalace/locks/palace-<id>.lock). A second writer of the same palace prints Waiting for another MemPalace writer to finish with <palace> (operation=..., pid=...) and then runs, so overlapping mines never file a drawer twice. The CLI waits up to 30 minutes, then exits 75 and prints the exact command to rerun; mempalace_mine waits up to 60 seconds, and mempalace_add_drawer, mempalace_diary_write, and the delete tools wait up to 30 seconds (they load the embedding model before taking the lease, so the lease covers only the write). Past that budget the call returns success: false, retryable: true, and a hint; nothing was written, so retry the same call once the named writer finishes (a mine can take minutes). Writers also hold the shared side of the install lease, so update never replaces the package mid-mine. Each palace ever written keeps three small files in ~/.mempalace/locks (palace-<id>.lock, .lock.metadata.lock, .lock.owners.json); they are not removed automatically, and an owner entry left by a killed process is cleared the next time that palace is written. When no MemPalace process is running (no MCP server, watcher, mine, or scheduled job), the whole ~/.mempalace/locks directory can be deleted; it is recreated on the next write.
  • Long-running readers stay current. Every store handle reads the latest committed table version at the start of each operation, so an MCP server or watcher sees drawers that other processes wrote, without a restart. The MCP server reopens the palace when another process restored or rebuilt it. A mine (including --full) replaces a changed file's drawers in the same commit that writes the new ones, so a concurrent search, file_context, or read sees the file's old drawers or its new ones, never neither.
  • Cleanup spares live readers. Post-optimize cleanup and cleanup --older-than-days N never remove a table version that was current within the last minute. Until those versions go, the palace takes more disk than its compacted size: about 2x after a typical mine, about 3x after mine --full, and more after a burst of mines within one minute. The next mine that starts more than a minute later prunes them before it writes (unless drawers were written since the last optimize or optimize_after_mine is off); mempalace-code cleanup --older-than-days 0 reclaims them at any time (a plain cleanup keeps every version younger than 7 days). Only cleanup --unsafe-now also removes versions from the last minute, so stop every MemPalace process before using it.

Backup & Restore

Create and inspect both recovery artifacts before rebuilding. The import preview must target the inspected, already-existing palace.

set -euo pipefail

PALACE="${HOME}/.mempalace/palace"
EXPORT_JSONL="${HOME}/.mempalace/recovery-manual.jsonl"
BACKUP_TAR="${HOME}/.mempalace/recovery-full.tar.gz"

: "${PALACE:?set PALACE to the inspected existing palace}"
: "${EXPORT_JSONL:?set EXPORT_JSONL to a new JSONL path}"
: "${BACKUP_TAR:?set BACKUP_TAR to a new tar path}"
test -d "$PALACE/lance"
test ! -e "$EXPORT_JSONL"
test ! -e "$BACKUP_TAR"

mempalace-code --palace "$PALACE" export --only-manual --with-kg --out "$EXPORT_JSONL"
mempalace-code --palace "$PALACE" import "$EXPORT_JSONL" --dry-run
mempalace-code --palace "$PALACE" backup create --out "$BACKUP_TAR"
tar -tzf "$BACKUP_TAR"

The preview validates the JSONL and opens existing palace state read-only when present. It does not write palace or KG state. When the selected palace and KG are absent, it does not create them or initialize temporary, embedding-model, or cache state. It previews record import without applying records. Without --force, tar restore refuses state found at the selected palace or KG during its checks, claims the exact lance/ name exclusively, and publishes the KG with an atomic no-replace operation. An existing real empty palace directory is the only reusable initial state. Unsupported no-replace KG publication fails closed. This boundary is not a transaction for concurrent replacement of the palace root or its ancestors, or for arbitrary edits elsewhere in the palace. Back up every reported destination before an intentional --force restore.

KG boundary: JSONL --with-kg, tar backup/restore, mining, MCP, and the watcher all use the selected palace's <palace>/knowledge_graph.sqlite3 (--palace, or the configured palace); tar backup omits it from the archive when absent. Facts that only the separate global KG at ~/.mempalace/knowledge_graph.sqlite3 holds (written by earlier releases and by the Python KnowledgeGraph() default) are copied into the configured palace's KG once, and again only after that file changes or after import replays an export written before 1.15.0; its contents are never modified. --kg-path selects another restore destination. The complete quarantine, rebuild, restore, force-restore, and failure-recovery procedure is in docs/BACKUP_RESTORE.md.

Backup kinds: Each archive has a kind that controls its filename prefix and per-kind retention:

Kind Prefix Created by
manual mempalace_backup_ backup create (default)
scheduled scheduled_ backup create --kind scheduled / cron
pre_optimize pre_optimize_ Auto-backup before optimize
pre_watch pre_watch_ Auto-backup before the watcher's initial mine

Scheduled backups:

# Print a scheduler snippet (does NOT install — owner action required)
mempalace-code backup schedule --freq daily    # daily at 03:00
mempalace-code backup schedule --freq weekly   # weekly on Sunday at 03:00
mempalace-code backup schedule --freq hourly   # every hour

# macOS: save and load the launchd plist; stderr prints the exact save and load commands.
# For example (the label comes from the palace directory's name and path):
mempalace-code backup schedule --freq daily > ~/Library/LaunchAgents/com.mempalace.backup.palace-1a2b3c4d.plist
launchctl load ~/Library/LaunchAgents/com.mempalace.backup.palace-1a2b3c4d.plist

# Linux: paste the printed cron line into crontab -e
mempalace-code backup schedule --freq daily
# → 0 3 * * * /usr/local/bin/mempalace-code --palace /path/to/palace backup create --kind scheduled

Each palace gets its own launchd job, com.mempalace.backup.<palace-dir-name>-<hash>, so scheduling a second palace adds a job instead of replacing the first. A job saved by an earlier release keeps the fixed label com.mempalace.backup; unload and remove it before loading the newly rendered plist for the same palace, or both run. The plist logs every run to ~/Library/Logs/<label>.log (stderr prints the path), so a refused run (disk budget, degraded palace, busy lease) leaves a trace. Like the watch job, the plist (and the cron line, as leading NAME=value assignments) carries HF_HOME and every MEMPALACE_* setting of the rendering shell, such as MEMPALACE_BACKUP_RETAIN_COUNT; re-render after changing them. schedule warns when no palace exists at the target path yet.

Retention (automatic pruning):

pre_optimize and pre_watch archives are bounded by default to the newest 5 per kind (implicit safe default for repeated mine/optimize cycles and watcher restarts). scheduled archives are bounded by default to the newest 14 (implicit safe default for cron and launchd jobs). manual archives are unbounded by default (retain all, no pruning).

export MEMPALACE_BACKUP_RETAIN_COUNT=5   # explicit limit for all kinds; overrides implicit pre_optimize, pre_watch, and scheduled bounds
# Deliberate keep-all opt-out (including pre_optimize, pre_watch, and scheduled):
export MEMPALACE_BACKUP_RETAIN_COUNT=0   # 0 disables pruning for every kind

Or in ~/.mempalace/config.json: {"backup_retain_count": 5}. Each palace has its own managed directory, <palace_parent>/backups/<palace_name>/, and retention only affects archives there, so palaces that share a parent directory never prune each other's backups. Explicit --out archives are never pruned, and backup create --out refuses to overwrite an existing file.

backup list (--json for scripts) prints stale in its FLAGS column for archives that would be pruned at the current retain count, oversized for archives larger than MEMPALACE_BACKUP_WARN_SIZE_BYTES, degraded for a snapshot of a damaged palace, and shared for archives an older release wrote into the shared <palace_parent>/backups/ directory (they may belong to a sibling palace; check their wings before restoring, and retention never deletes them).

backup create fails with exit 1 instead of writing an empty archive when the path holds no palace (missing directory, empty directory, or a legacy ChromaDB palace). For a degraded palace (unreadable drawers, or a knowledge graph that fails SQLite's quick_check) it still writes a forensic *_DEGRADED.tar.gz copy, warns, and exits 1; restore refuses such an archive, and degraded archives never displace healthy ones in retention.

After a successful optimize and readability check, MemPalace also runs best-effort verified Lance cleanup (cleanup_stale_fragments with unsafe_now=false) so future backups do not keep archiving stale table versions. That cleanup keeps every version that was current within the last minute, so another process still reading the palace keeps its data files. The next mine that starts more than a minute later prunes them before it writes, so its pre-optimize backup does not carry them. That step only prunes: it never compacts, it is skipped when drawers were added or deleted since the last optimize (the backup then comes first), and it does nothing when optimize_after_mine is off. mempalace-code cleanup --older-than-days 0 reclaims them at any time; a plain cleanup keeps every version younger than 7 days. Optimize and cleanup verification re-opens the Lance table, so it checks the same fresh-handle path the next CLI, MCP server, or watcher process will use. Use the manual cleanup command for older installations that already accumulated stale versions or for emergency recovery.

Disk-budget guard (1 GiB default):

export MEMPALACE_BACKUP_DISK_MIN_FREE_BYTES=2GiB    # require 2 GiB projected free after backup
# Legacy alias still accepted:
export MEMPALACE_BACKUP_MIN_FREE_BYTES=2GiB

When projected free space after the archive would fall below the configured floor, backup raises a disk budget error before writing the archive and optimize is skipped (fail-closed). The backup floor falls back to backup_disk_min_free_bytes → disk_min_free_bytes → 1 GiB default. Set the backup-specific floor to 0 to disable the backup guard.

Auto-backup before optimize (on by default):

backup_before_optimize is true by default. A backup is created under <palace_parent>/backups/<palace_name>/pre_optimize_*.tar.gz before every optimize() call (runs after mining).

To opt out, add to ~/.mempalace/config.json:

{
  "auto_backup_before_optimize": false
}

Or set env var: MEMPALACE_AUTO_BACKUP_BEFORE_OPTIMIZE=0 (preferred) or MEMPALACE_BACKUP_BEFORE_OPTIMIZE=0.

Disable auto-optimize (paranoid mode):

{
  "optimize_after_mine": false
}

Skips compaction entirely. Storage will grow with more fragments but avoids any compaction-related corruption risk.

Why backup matters: Manual drawer additions (via mempalace_add_drawer) are not recoverable from source code. If LanceDB storage gets corrupted, only backups preserve this data. Code-mined drawers can be restored by re-running mempalace-code mine.

Also available: mempalace-code export --only-manual --out <backup.jsonl> for JSONL export of manually-stored drawers.

Remote mirror risk — backups vs file mirroring:

Managed backups and Lance cleanup protect local palace state. They do not protect against delete-mode rsync between independent hosts. rsync --delete syncing a whole MemPalace state directory removes remote-owned drawers, diary entries, and KG triples that were never synced back to the source — even when local backups are healthy.

Use these recommended excludes for any delete-mode state-directory mirror:

rsync -a --delete \
  --exclude=palace/ \
  --exclude='palace.backup-*/' \
  --exclude='palace.quarantine-*/' \
  --exclude=knowledge_graph.sqlite3 \
  --exclude=knowledge_graph.sqlite3.adopted \
  --exclude=config.json \
  --exclude=backups/ \
  ~/.mempalace/ user@host:.mempalace/

palace.backup-<UTC>/ (left by repair) and palace.quarantine-<UTC>/ (left by the rebuild workflow) are full palace copies. Run a preflight check before installing a mirror job (the command is never executed). It also blocks --del, --remove-source-files, mirrors of a palace configured outside ~/.mempalace, and mirrors of a parent such as ~/ that contains ~/.mempalace without excluding the same families under it (for example --exclude=/.mempalace/palace/), and reports UNVERIFIED when excludes come from files or an earlier include:

mempalace-code preflight mirror --command \
  "rsync -a --delete --exclude=palace/ --exclude='palace.backup-*/' \
   --exclude='palace.quarantine-*/' --exclude=knowledge_graph.sqlite3 \
   --exclude=knowledge_graph.sqlite3.adopted --exclude=config.json --exclude=backups/ \
   ~/.mempalace/ user@host:.mempalace/"
# OK
#   warning: advisory: no --exclude for 'logs' (add --exclude='*.log' if you route MemPalace logs into the state directory)

See docs/BACKUP_RESTORE.md for the full mirror-risk guidance and the safer export/import alternative for cross-host transfer of non-regenerable content.


Scan Excludes

By default mempalace-code mine already skips common generated directories (node_modules, __pycache__, .git, etc.). For project-specific noise — generated LSP state, build artifacts, IDE files — configure app-level excludes in ~/.mempalace/config.json:

{
  "scan_skip_dirs":  [".kotlin-lsp"],
  "scan_skip_files": ["workspace.json"],
  "scan_skip_globs": ["generated/**/*.js", "build/**"]
}
Key Match rule Default
scan_skip_dirs directory basename — prunes the whole subtree [".kotlin-lsp"]
scan_skip_files file basename — skips matching files anywhere []
scan_skip_globs project-relative POSIX glob — skips matching file paths []

workspace.json as opt-in example: a root workspace.json can be a legitimate monorepo config file, so it is not excluded by default. Add it to scan_skip_files only if your LSP generates it as noise inside generated directories.

These rules apply to both mempalace-code mine and the auto-watcher (mempalace-code mine --watch and mempalace-code watch). Force-include paths (--include-ignored) always win over app-level excludes.

Watcher loops reload these app-level rules between scan cycles, so edits to ~/.mempalace/config.json apply to subsequent re-mines without restarting mempalace-code watch.

Removing previously indexed noise: scan excludes prevent future scans from indexing the excluded paths. To remove content that was indexed before adding the exclusion, mine the project again:

mempalace-code mine <dir>

Every mine that walks the whole project (no --limit) ends with a stale-file sweep that removes drawers of files that are no longer discovered by the scanner — deleted files and previously indexed files that now fall under an exclusion rule. --full runs the same sweep and also re-chunks and re-embeds every remaining file.


Health & Repair

mempalace-code health              # probe palace for fragment corruption
mempalace-code health --json       # machine-readable report (JSON on errors too)
mempalace-code cleanup --older-than-days 7 --dry-run  # preview which versions cleanup removes
mempalace-code cleanup --older-than-days 7  # reclaim stale Lance versions
mempalace-code cleanup --unsafe-now         # emergency only; stop MemPalace processes first

mempalace-code repair --rollback --dry-run  # show what rollback would recover
mempalace-code repair --rollback   # roll back to last working version
mempalace-code repair              # rebuild the drawer table from stored data

What health checks:

  1. Manifest read (count_rows)
  2. Data fragment read (head)
  3. Metadata scan (count_by_pair) - catches the silent-failure surface
  4. Knowledge graph integrity (SQLite quick_check on <palace>/knowledge_graph.sqlite3)

A path without a drawer table (a project directory, or a palace whose first mine failed) reports No palace found and exits 1. Every Next: hint repeats --palace <path>, so following it never acts on the default palace.

What repair --rollback does:

  1. Probes the current version first; a healthy palace is left unchanged
  2. Otherwise walks LanceDB version history from newest to oldest
  3. Finds the most recent version where all probes pass
  4. Restores to that version (loses data added after corruption)

Use --dry-run first to see how many rows would be lost. Rollback never falls back to a full rebuild.

What repair (full rebuild) does: it reads every stored drawer first and stops with exit 1, changing nothing, at the first unreadable drawer or when another process writes to the palace meanwhile. Then choose: restore an archive (backup list), or repair --salvage to rebuild from the readable drawers only. It rebuilds just lance/ from the stored text, metadata, and vectors (no model needed), keeps knowledge_graph.sqlite3 and .mempalace/ in place, and saves the pre-repair palace to a new <palace>.backup-<UTC timestamp> directory; it never deletes an earlier backup.

A degraded palace fails loudly. When stored drawers cannot be read, search, read, status, wake-up, and export exit 1 with the palace path and the health / repair --rollback --dry-run / repair / backup list commands, and MCP search returns that error, instead of reporting an empty palace.

Running processes: repair, repair --rollback, cleanup, and restore --force take the installation's exclusive operation lease. They refuse, naming the process, while an MCP server, watcher, or other MemPalace process holds a lease; stop those first. The lease covers the whole installation, not one palace: a holder working on a different palace still blocks them, and the refusal names that holder's palace. Previews (--dry-run) need no lease.

Normal optimize runs already prune verified stale Lance versions after a successful compaction and fresh-handle verification, keeping versions that were current within the last minute for readers in other processes. Manual cleanup applies the same reader grace and is still useful after older installs or large historical accumulations (--older-than-days 0 also reclaims versions younger than the 7-day default); cleanup --unsafe-now skips the grace and is only for emergency disk recovery after stopping watchers, miners, maintenance commands, and MCP servers.


Version Check (opt-in)

mempalace-code can notify you when a newer release is available on PyPI. This feature is strictly opt-in — no network calls are made by default.

# Check your current status (local-only, no network)
mempalace-code version-check

# Opt in to periodic checks (contacts PyPI for package metadata only)
mempalace-code version-check --enable

# Opt out (suppresses future first-run prompts)
mempalace-code version-check --disable

# Check right now regardless of the interval or persisted preference
mempalace-code version-check --check-now

How it works:

  • On the first interactive command after a fresh install, the CLI prompts once: "Enable periodic new-version checks?" — answering n records the opt-out permanently. Non-interactive (piped, CI, non-TTY) invocations never prompt.
  • When opted in, a background check runs at most once per interval (default: 168 hours / 1 week). Any update hint appears on stderr only — stdout remains machine-parseable.
  • Explicit --check-now ignores the interval and persisted preference, then contacts PyPI and prints current/latest/error to stdout. A successful explicit check updates Last checked. MEMPALACE_VERSION_CHECK=0 and invalid values still block the request; run unset MEMPALACE_VERSION_CHECK (or set it to 1) before retrying.
  • Only https://pypi.org/pypi/mempalace-code/json is contacted. No telemetry, no user IDs, no installed-package inventory.

Environment overrides:

Variable Effect
MEMPALACE_VERSION_CHECK=1 Force-enable (overrides config and state); true, yes, on are accepted too
MEMPALACE_VERSION_CHECK=0 Force-disable (overrides config and state); false, no, off are accepted too
MEMPALACE_VERSION_CHECK_INTERVAL_HOURS=N Override interval in whole hours, at least 1 (default: 168); an invalid value is reported and ignored

Setting MEMPALACE_VERSION_CHECK=0 in a CI pipeline guarantees no version-check network calls, including explicit --check-now, regardless of any saved preference. Invalid values fail closed in the same way, and version-check --status says the value is unrecognized. Run unset MEMPALACE_VERSION_CHECK (or set it to 1) before an explicit check.


This Fork vs Upstream

This is a code-first fork of the upstream mempalace project. The canonical upstream repository is MemPalace/mempalace; the older milla-jovovich/mempalace URL redirects there.

Snapshot reviewed on 2026-10-03 against upstream develop at commit 43122aefb8ed58e9fa4e1953e15ce70026339ec2 (upstream release 3.11.0), using pinned upstream public sources. Nothing below is a performance, quality, or adoption comparison — no upstream build or benchmark was run.

Area Upstream, as advertised at that commit This fork
Focus General-purpose AI memory Code-first: repository mining, code_search, symbol/type/project-graph tools
Default storage ChromaDB LanceDB
Other backends offered sqlite_exact, native rust_exact, Milvus, Qdrant, pgvector LanceDB-only current package; ChromaDB support is retired; no server-backed backends
Embedding model all-MiniLM-L6-v2 by default; onboarding recommends the multilingual embeddinggemma-300m; optional OpenAI-compatible embedding server all-MiniLM-L6-v2; no supported multilingual configuration or migration flow
Retrieval Hybrid retrieval Vector search, plus a local deterministic code_search(rerank="hybrid")
Reranking Optional LLM reranking None — no LLM reranker; this direction is explicitly rejected
MCP tools 47 in the full server registry (its README still says 45), plus a 3-tool lightweight server 29 in the full profile, plus a 4-tool minimal profile for agent plugins

This fork has not adopted upstream's multilingual embedding, broad hybrid-retrieval, or alternative-backend paths. LLM reranking is explicitly rejected: it would put an API key, a per-query network call, and non-deterministic results into a tool that stays local after the one-time model download.

Reviewed comparison with source links, evidence limits, and the reasoning behind each decision: docs/UPSTREAM_COMPARISON.md.

Historical community criticism of upstream from April 2026, and how this fork responded at the time, is archived in docs/UPSTREAM_HARDENING.md. Those findings describe April 2026 and are not claims about upstream today.


Benchmarks

Token savings vs grep + read (full methodology)

Measured 2026-08-09 on the canonical fixture, a snapshot of this repository (378 tracked files, 2,024 mined chunks, 20 queries):

Median Mean Peak
14.5x 15.8x 33.8x

These are read from the committed benchmarks/token_delta_fixture_facts.json; scripts/docs_drift_guard.py fails CI if the Median or Peak columns drift from that file (Mean comes from the same file but is not individually guard-checked). Treat the figures as benchmark results for this fixture's query set and corpus size; re-run the benchmark before applying them to a different corpus.

Retrieval quality

Benchmark Score
Code retrieval R@5 (MiniLM, 469 chunks, 2026-04-09) 95.0%
Code retrieval R@10 100%
.NET retrieval R@5 (CleanArchitecture pinned corpus, vector, 2026-05-03) 90.0%
.NET retrieval R@5 (same corpus, rerank="hybrid") 100%

These are read from the committed benchmarks/retrieval_quality_facts.json; scripts/docs_drift_guard.py fails CI if these values drift from that file. The code rows are the 2026-04-09 embedding A/B run (20 known-answer queries over a 469-chunk snapshot of this repository); the current benchmarks/code_retrieval_bench.py uses a different 33-query dataset and does not reproduce them. The .NET rows were measured 2026-05-03 on the pinned CleanArchitecture commit.

Upstream LongMemEval result (96.6% R@5 on conversations) retained with methodology caveats.


Installation Details
pip install mempalace-code
# or
uv pip install mempalace-code

Bootstrap script (explicit remote-script option for servers/CI):

[[ "${BOOTSTRAP_REF:-}" =~ ^[0-9a-fA-F]{40}$ ]] || { echo "BOOTSTRAP_REF must be a reviewed 40-hex commit" >&2; exit 2; }
BOOTSTRAP_FILE="$(mktemp -t mempalace-bootstrap.XXXXXX)" || exit 1
(
  trap 'rm -f -- "$BOOTSTRAP_FILE"' EXIT
  curl -fL "https://raw.githubusercontent.com/rergards/mempalace-code/$BOOTSTRAP_REF/scripts/bootstrap.sh" -o "$BOOTSTRAP_FILE" || exit 1
  less "$BOOTSTRAP_FILE" || exit 1
  bash "$BOOTSTRAP_FILE" || exit 1
) || exit 1

Optional extras:

# mempalace-code[custom-models]            # CPU-only Linux: docs/OFFLINE_USAGE.md
pip install "mempalace-code[treesitter]"  # AST parsing
pip install "mempalace-code[spellcheck]"  # autocorrect helper API; never applied to mined text
pip install "mempalace-code[watch]"       # optional watcher (auto-mine on file changes)
pip install "mempalace-code[dev]"         # pytest + ruff + pyright

CPU-only Linux custom models require the ordered CPU PyTorch contour and bounded recovery in Using a Custom Model Offline. That guide also covers arbitrary SentenceTransformer names and local paths, and how a palace records its model so the CLI and MCP server use it.

Requirements: Python 3.11+. Use mempalace-code init <dir> --skip-model-download for an offline-safe init. Run mempalace-code fetch-model later only after explicit consent to cache the ~80 MB embedding model.

The default all-MiniLM-L6-v2 runtime is CPU FastEmbed/ONNX and stores its immutable MemPalace provenance under $HF_HOME/mempalace-fastembed/all-MiniLM-L6-v2-v1/. Canonical aliases never enable trusted remote code. Explicit custom models require [custom-models]; that path retains SentenceTransformer's trust_remote_code=True compatibility boundary. Recovery: run mempalace-code fetch-model while online, then retry with HF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1. MemPalace sets ORT_DISABLE_TELEMETRY=1 before ONNX Runtime loads (an explicit value you set is kept), so ONNX Runtime does not create its device-usage event store under your home directory (on macOS ~/Library/Application Support/Microsoft/DeveloperTools/.onnxruntime/).

All CLI Commands
mempalace-code help                                    # show top-level help message and exit
mempalace-code help <command>                          # show one command's help (same as <command> --help)
mempalace-code --version                               # print the installed package version and exit

# Setup
mempalace-code init <dir>                              # initialize rooms (keeps an existing mempalace.yaml)
mempalace-code init <dir> --force                      # regenerate rooms; keeps wing/settings, backs up the old file
mempalace-code init <dir> --detect-entities            # optional prose entity bootstrap
mempalace-code onboarding <dir>                        # record people/projects (entity registry, AAAK codes) + reference notes; <dir> is scanned only on request
mempalace-code split <dir>                             # split Claude Code transcript mega-files (top-level .txt) before mining
mempalace-code split <file>                            # split one mega-file; every line is kept, original → .mega_backup
mempalace-code install-alias                           # create optional 'mempalace' alias next to mempalace-code
mempalace-code install-alias --target-dir <dir>        # create it in an existing <dir> instead; only <dir> is inspected
mempalace-code-alias --yes                             # the same as install-alias, as its own console script (bare: help only)

# Mining
mempalace-code mine <dir>                              # mine code project
mempalace-code mine <dir> --wing myapp                 # tag with wing
mempalace-code mine <dir> --mode convos                # mine conversations
mempalace-code mine <dir> --full                       # force full rebuild
mempalace-code mine <dir> --watch                      # auto-incremental on file changes
mempalace-code mine-all <parent-dir>                   # sync all projects incrementally (one wing per project)
mempalace-code mine-all <parent-dir> --new-only        # only mine projects not yet in the palace

# Watch (project auto-sync)
mempalace-code watch <initialized-project>             # watch a single initialized project
mempalace-code watch <parent-dir>                      # watch all initialized projects in a parent directory
mempalace-code watch <parent-dir> schedule             # print this root's launchd/cron daemon snippet
mempalace-code watch <parent-dir> status               # disk budget, root validity, this root's launchd job

# Search
mempalace-code search "query"                          # search everything
mempalace-code search "query" --wing myapp             # scoped to wing
mempalace-code search "query" --room auth              # scoped to room
mempalace-code search "query" --json                   # one JSON object: hits with id, source_file, line_range
mempalace-code read <file> --start N --end N           # print stored source lines for a file/range
mempalace-code compress --wing myapp                   # print lossy AAAK Dialect summaries (read-only; drawers stay verbatim)
mempalace-code compress --recover-from <archive>       # restore drawers that compress rewrote before 1.15.0

# Diary
mempalace-code diary write --agent <name> --entry "<text>"  # write a diary entry

A successful direct diary write prints Diary entry stored., stable ID, Wing, Room, and Topic poststate, then a bounded Verify before retry search command. If that output is retained after an ambiguous result, run the printed search (it prints JSON hits) before any retry. An exact hit means success — a hit whose id equals the printed ID, not merely similar text: do not repeat the write. If the response or printed command is unavailable, do not retry; inspect recent same-agent entries with exposed mempalace_diary_read (it also returns entries written under --wing), or stop for owner reconciliation. A blank --agent, --entry, --topic, or --wing, or a name containing /, \ or control characters, exits 2 before the palace is touched. A new --wing that only re-spells an existing wing (Shop when shop exists) also exits 2 and names the existing wing, as mempalace_add_drawer does.

# Backup & Recovery
mempalace-code backup create                           # create backup (default: <palace_parent>/backups/<palace_name>/)
mempalace-code backup list                             # list existing backups
mempalace-code backup schedule --freq daily            # print daily scheduler snippet
mempalace-code restore <archive>                       # restore from backup
mempalace-code export --only-manual --out backup.jsonl # JSONL export
mempalace-code import <file>                           # JSONL import
mempalace-code health                                  # probe for fragment corruption
mempalace-code cleanup                                 # reclaim stale Lance versions
mempalace-code repair --rollback                       # roll back to last working version

# Context
mempalace-code wake-up                                 # L0 + L1 context on stdout
mempalace-code wake-up --wing myapp                    # project-scoped
mempalace-code status                                  # detailed overview; grows with wing/room count
mempalace-code status --summary                        # bounded agent-facing status (drawer/wing/room-pair counts + storage)
mempalace-code health --json                           # compact integrity report

# Model
mempalace-code fetch-model                             # cache or verify model for offline use

# Advanced / Ops
# Legacy Chroma palace recovery: back up SRC before upgrading, then run the last bridge in isolation.
# The bridge installs a pre-1.0 chromadb with server-side advisories: run it once, locally, no Chroma server.
mempalace-code migrate-storage SRC DST --verify         # retired; exits before mutation and points here
uvx --python 3.12 --from 'mempalace-code[chroma]==1.13.4' mempalace-code migrate-storage SRC DST --verify
mempalace-code preflight mirror --command "<cmd>"      # inspect an rsync command for state-dir risks
mempalace-code version-check                           # show version-check status (opt-in PyPI checks)
mempalace-code version-check --check-now               # check PyPI now; prints a pip fallback for unmanaged installs
mempalace-code update status                            # inspect upgrade eligibility (supported installs)
mempalace-code agent-plugin path --json                # locate the installed Agent Plugins package directory
mempalace-code wing-migration qualify --mode synthetic # qualify the receipt-bound copy migration operator

Plain status prints a full wing/room breakdown, so its output grows with palace size. Do not use it as a routine agent bootstrap or machine-readable health check; use status --summary for bounded shell-based CLI discovery, task-specific MCP retrieval, or mempalace-code health --json for a compact CLI integrity report.

Saving Conversation Context

Code mining is automatic via mempalace-code watch. For conversation context (decisions, discussions, debugging notes), an MCP-capable client can expose the storage tools:

  1. Wire the MCP server (see install docs)
  2. For compatible Agent Plugins 1.0 clients, load the package reported by mempalace-code agent-plugin path --json; follow Section 7
  3. For other clients, stop after MCP wiring. docs/LLM_USAGE_RULES.md remains read-only reference material; instruction-file and system-prompt mutation are unsupported
  4. Subject to client policy, call mempalace_add_drawer and mempalace_diary_write during sessions

Legacy: Claude Code also supports optional auto-save hooks that remind the AI to save at fixed intervals. They are repository scripts, not part of the installed package; the hooks README shows how to download them. They are independent of the Agent Plugin instruction-loading boundary.

Project Structure
mempalace-code/
├── mempalace_code/
│   ├── cli.py              ← CLI entry point
│   ├── cli_commands/       ← CLI command handlers
│   ├── mcp/                ← MCP server implementation (29 tools, profiled at startup)
│   ├── mcp_server.py       ← MCP entrypoint compatibility facade
│   ├── storage.py          ← LanceDB vector storage
│   ├── mining/             ← language-aware project mining and chunking
│   ├── miner.py            ← mining compatibility facade
│   ├── convo_miner.py      ← conversation ingest
│   ├── searcher.py         ← semantic search
│   ├── knowledge_graph.py  ← temporal entity graph (SQLite)
│   ├── palace_graph.py     ← room navigation graph
│   └── layers.py           ← 4-layer memory stack
├── mempalace/              ← source-only MCP compatibility shim
├── benchmarks/             ← reproducible benchmark runners
├── hooks/                  ← Claude Code auto-save hooks (legacy, optional)
├── examples/               ← usage examples
└── tests/                  ← 3,000+ tests

Opt-in updates

Supported isolated installs can inspect and explicitly apply upgrades. The command never runs from ordinary MemPalace startup, and its systemd-user scheduler is disabled until installed by the operator.

mempalace-code update status                 # read-only: installer, extras, provenance, service, timer
mempalace-code update check                  # read-only canonical PyPI provenance refresh
mempalace-code update apply --yes            # explicit package and managed-watcher transaction
mempalace-code update scheduler render       # inspect systemd-user units without writing them
mempalace-code update scheduler install --yes # explicit daily scheduler opt-in (Linux systemd-user)

Omitting --yes from update apply, update scheduler install, or update scheduler remove exits 2 before mutation. Human output prints Recovery: <command>; JSON output supplies the exact guarded command in recovery_command. Review current mutation authority before running that emitted command. See the update runbook for the complete refusal contract.

The first slice supports uv tool, pipx, and the documented ~/.mempalace/venv bootstrap install. It refuses system Python, distro-managed, editable/source, and ambiguous environments before stopping a watcher or changing package state. If you installed with plain pip, upgrade yourself — mempalace-code version-check --check-now prints the exact pinned python -m pip install command for the interpreter that is running mempalace-code. Stable, compatible-major releases are selected from canonical PyPI provenance; prereleases, yanked files, and releases without a wheel are refused. Detected extras are retained. A configured watcher missing its required watch extra is also a preflight refusal.

An update serializes with watchers, records its stage and bounded log under ~/.mempalace/updates/, validates the new console and palace, then restores a previously active managed watcher. Any failure after version recording invokes the same installer to roll back the prior version and restores the prior watcher state. See the update runbook for recovery.

Contributing

PRs welcome. See CONTRIBUTING.md. Maintainers should follow docs/RELEASING.md before tagging or publishing.

python -m pytest tests/ -x -q    # full suite, all local, no network
python -m pyright --pythonpath "$(python -c 'import sys; print(sys.executable)')"  # type-check baseline

License

Apache 2.0 — see LICENSE and NOTICE.

Metadata

Release files for mempalace-code 1.15.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mempalace-code 1.15.1
File Size Uploaded
mempalace_code-1.15.1.tar.gz 2.4 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for mempalace-code 1.15.1
File Interpreter ABI Platform
mempalace_code-1.15.1-py3-none-any.whl Python 3 none any Details

Total release size: 3.0 MB

Release files / mempalace_code-1.15.1.tar.gz

Download URL mempalace_code-1.15.1.tar.gz
Size 2.4 MB
Tags Source
SHA-256 checksum
How to use checksums
468c9ffe2ebe4a3eac21d12567dbe4468d0028f6e1cde1134fdfbf750f3e372f
BLAKE2b-256 checksum
How to use checksums
502214439032e937c465b781eaaeb3d859dee676cde4287d173c8dcb3616206e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 5, 2026.

Transparency log

Release files / mempalace_code-1.15.1-py3-none-any.whl

Download URL mempalace_code-1.15.1-py3-none-any.whl
Size 570.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
bfa4902403102be364aaaa2fcf83f968eb51576b6e50e1f123cab818af12f983
BLAKE2b-256 checksum
How to use checksums
7dcae443c7196805c904e4409fdfd16e2a1471239db512718344b80db4edcb37
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 5, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.15.1 This release

2 release files

1.14.2

2 release files

1.14.0

2 release files

1.13.4

2 release files

1.13.3

2 release files

1.13.1

2 release files

1.13.0

2 release files

1.12.1

2 release files

1.12.0

2 release files

1.11.0

2 release files

1.10.1

2 release files

1.10.0

2 release files

1.9.0

2 release files

1.8.1

2 release files

1.8.0

2 release files

1.7.0

2 release files

1.6.2

2 release files

1.6.1

2 release files

1.6.0

2 release files

1.5.0

2 release files

1.4.1

2 release files

1.4.0

2 release files

1.3.0

2 release files

1.2.0

2 release files

1.1.1

2 release files

1.1.0

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page