* fix: add file-level locking to prevent multi-agent duplicate drawers
Root cause: when multiple agents mine simultaneously, both pass
file_already_mined() check, both delete+insert the same file's
drawers, creating duplicates or losing data.
Fix: mine_lock() in palace.py — cross-platform file lock (fcntl on
Unix, msvcrt on Windows). Both miner.py and convo_miner.py now lock
per-file during the delete+insert cycle and re-check after acquiring
the lock.
Tested:
- Lock acquires and releases correctly
- Second agent blocks until first releases (0.25s wait)
- 33/33 existing tests pass
- Cross-platform: fcntl (macOS/Linux), msvcrt (Windows)
Based on v3.2.0 tag.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: strip system tags, hook output, and Claude UI chrome from drawers
normalize.py now strips before filing:
- <system-reminder>, <command-message>, <command-name> tags
- <task-notification>, <user-prompt-submit-hook>, <hook_output> tags
- Hook status messages (CURRENT TIME, Checking verified facts, etc.)
- Claude Code UI chrome (ctrl+o to expand, progress bars, etc.)
- Collapsed runs of blank lines
This noise was going straight into drawers, wasting storage space
and polluting search results. strip_noise() runs on all normalized
output regardless of input format (JSONL, JSON, plain text).
689/689 tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add closet layer — searchable index pointing to drawers
The closet architecture was always part of MemPalace's design but
never shipped in the public codebase. This adds it.
Palace now has TWO collections:
- mempalace_drawers — full verbatim content (unchanged)
- mempalace_closets — compact AAAK-style index entries
How it works:
- When mining, each file gets a closet alongside its drawers
- Closet contains extracted topics, entities, quotes as pointers
- Closets pack up to 1500 chars, topics never split mid-entry
- Search hits closets first (fast, small), then hydrates the
full drawer content for matching files
- Falls back to direct drawer search if no closets exist yet
Files changed:
- palace.py: get_closets_collection(), build_closet_text(),
upsert_closet(), CLOSET_CHAR_LIMIT
- miner.py: process_file() now creates closets after drawers
- searcher.py: search_memories() tries closet-first search,
hydrates drawers, falls back to direct search
Backwards compatible — existing palaces without closets continue
to work via the fallback path. Closets are created on next mine.
689/689 tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: enforce atomic topics in closets, extract richer pointers
- upsert_closet replaced by upsert_closet_lines: checks each topic
line individually against CLOSET_CHAR_LIMIT. If adding one line
WHOLE would exceed the limit, starts a new closet. Never splits
mid-topic.
- build_closet_lines returns a list of atomic lines (not joined text)
- Richer extraction: section headers, more action verbs, up to 3
quotes, up to 12 topics per file
- Each line is complete: topic|entities|→drawer_refs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: add CLOSETS.md — closet layer overview
Cherry-picked the docs portion of 67e4ac6 to accompany the closet
feature. Test coverage for closets is omnibus with tests for entity
metadata and BM25 (see PR targeting those features) and will land
together in a follow-up.
Co-Authored-By: MSL <232237854+milla-jovovich@users.noreply.github.com>
* feat: entity metadata + diary ingest + BM25 hybrid search
Three features that close the gap between the architecture docs
and the actual codebase:
1. Entity metadata on drawers and closets
- _extract_entities_for_metadata() pulls names from known_entities.json
+ proper nouns appearing 2+ times
- Stamped as "entities" field in ChromaDB metadata
- Enables filterable search by person/project name
2. Day-based diary ingest (diary_ingest.py)
- ONE drawer per day, upserted as the day grows
- Closets pack topics atomically, never split mid-topic
- Tracks entry count in state file, only processes new entries
- Usage: python -m mempalace.diary_ingest --dir ~/summaries
3. BM25 hybrid search in searcher.py
- _bm25_score() keyword matching complements vector similarity
- _hybrid_rank() combines both signals (60% vector, 40% BM25)
- Catches exact name/term matches that embeddings miss
- Applied to both closet-first and direct drawer search paths
689/689 tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add tests for mine_lock, closets, entity metadata, BM25, diary
Trimmed version of Milla's omnibus test_closets.py to only cover
features present in this PR stack (#784 lock, #788 closets, this
PR's entity/BM25/diary). Strip-noise tests will land with #785;
tunnel tests will land with the tunnels PR.
16/16 pass.
Co-Authored-By: MSL <232237854+milla-jovovich@users.noreply.github.com>
* feat: explicit cross-wing tunnels for multi-project agents
Adds active tunnel creation alongside passive tunnel discovery.
Passive tunnels (existing): rooms with the same name across wings.
Explicit tunnels (new): agent-created links between specific
locations. "This API design in project_api relates to the database
schema in project_database."
New functions in palace_graph.py:
- create_tunnel() — link two wing/room pairs with a label
- list_tunnels() — list all explicit tunnels, filter by wing
- delete_tunnel() — remove a tunnel by ID
- follow_tunnels() — from a room, find all connected rooms in
other wings with drawer content previews
New MCP tools:
- mempalace_create_tunnel
- mempalace_list_tunnels
- mempalace_delete_tunnel
- mempalace_follow_tunnels
Tunnels stored in ~/.mempalace/tunnels.json (persists across
palace rebuilds). Deduplicated by endpoint pair.
689/689 tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add TestTunnels for cross-wing tunnel operations
Appended from Milla's omnibus test_closets.py — covers create,
list, delete, dedup, and follow_tunnels behavior. 21/21 pass.
Co-Authored-By: MSL <232237854+milla-jovovich@users.noreply.github.com>
* feat(search): drawer-grep returns best-matching chunk + neighbors
When a closet hit leads to a source file with many drawers, grep each
chunk for query terms and return the BEST-MATCHING chunk + 1 neighbor
on each side, instead of dumping the whole file truncated at
MAX_HYDRATION_CHARS. Result now includes drawer_index and
total_drawers so callers can request adjacent drawers explicitly.
Extracted from Milla's commit 935f657 which bundled drawer-grep with
closet_llm (deferred pending LLM_ENDPOINT refactor) and fact_checker
(separate PR). Ported only the searcher.py change.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: offline fact checker against entity registry + knowledge graph
fact_checker.py verifies text for contradictions against locally stored
entities and KG facts. Catches similar-name confusion (Bob vs Bobby),
relationship mismatches (KG says husband, text says brother), and
stale facts (KG valid_from/valid_to).
No hardcoded facts. No network calls. Reads:
- ~/.mempalace/known_entities.json
- KnowledgeGraph SQLite
Usage:
from mempalace.fact_checker import check_text
issues = check_text("Bob is Alice's brother", palace_path)
# CLI
python -m mempalace.fact_checker "text" --palace ~/.mempalace/palace
Extracted from Milla's commit 935f657 which bundled this with
closet_llm (deferred) and drawer-grep (PR #791). Ported only
fact_checker.py — verified no network / API imports.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: optional LLM-based closet regeneration — bring-your-own endpoint
Adds mempalace/closet_llm.py as an OPTIONAL path for richer closet
generation. Regex closets remain the default and cover the local-first
promise; users who want LLM-quality topics can bring their own endpoint.
Configuration (env or CLI flag):
LLM_ENDPOINT — OpenAI-compatible base URL (required)
LLM_KEY — bearer token (optional; local inference skips this)
LLM_MODEL — model name (required)
Works with Ollama, vLLM, llama.cpp servers, OpenAI, OpenRouter, and any
other provider that speaks OpenAI-compatible /chat/completions. Zero new
dependencies — uses stdlib urllib.
Replaces the original Anthropic-SDK-hardcoded version of this module
from Milla's branch (commit 935f657). Same prompt, same parsing, same
regenerate_closets flow; only the transport was generalised so the
feature doesn't lock users into a specific vendor or require API keys
for core memory operations (CLAUDE.md, "Local-first, zero API").
Includes 13 unit tests covering config resolution, request shape,
auth-header omission when no key is set, code-fence stripping, and
missing-config error path. All mocked — zero network calls in tests.
Co-Authored-By: MSL <232237854+milla-jovovich@users.noreply.github.com>
* fix(search): hybrid closet+drawer retrieval — closets boost, never gate (#795)
* Fix: set cosine distance metadata on all collection creation sites
ChromaDB defaults HNSW index to L2 (Euclidean) distance, but
MemPalace scoring uses 1-distance which requires cosine (range 0-2).
Add metadata={"hnsw:space": "cosine"} to the 4 production and 3 test
call sites that were missing it.
Closes #218
* fix: sync version.py to 3.2.0
Commit 6614b9b bumped pyproject.toml to 3.2.0 but missed
mempalace/version.py, breaking test_version_consistency on
every PR's CI. This syncs them.
* refactor: extract locked filing block to keep mine_convos under C901
Adding the per-file lock + double-checked file_already_mined() in the
previous commit pushed mine_convos cyclomatic complexity from 25 to 26,
just over ruff's max-complexity threshold. Hoist the locked critical
section into _file_chunks_locked() so the outer loop stays within
budget. No behavior change.
* style: ruff format mempalace/palace.py
Add blank lines after inline imports in mine_lock. Pure formatting.
* fix(normalize): make strip_noise verbatim-safe and scope it to Claude Code JSONL
The initial strip_noise() regressed on three fronts when audited against
adversarial user content — each verified with executable repros against
the cherry-picked code:
1. `<tag>.*?</tag>` with re.DOTALL span-ate across messages: one
stray unclosed <system-reminder> anywhere in a session merged with
the next closing tag, silently deleting everything between them
(including full assistant replies).
2. `.*\(ctrl\+o to expand\).*\n?` nuked entire lines of user prose
whenever a user happened to document the TUI shortcut.
3. `Ran \d+ (?:stop|pre|post)\s*hook.*` with IGNORECASE ate the
second sentence from "our CI has a stop hook ... Ran 2 stop hooks
last week" — legitimate user commentary.
These are unambiguous violations of the project's "Verbatim always"
design principle.
Fixes:
- All tag patterns are now line-anchored (`(?m)^(?:> )?<tag>`) and their
body forbids crossing a blank line (`(?:(?!\n\s*\n)[\s\S])*?`), so a
dangling open tag cannot eat neighboring messages.
- `_NOISE_LINE_PREFIXES` are line-anchored and case-sensitive — user
prose mentioning "CURRENT TIME:" mid-sentence is preserved.
- Hook-run chrome requires `(?m)^`, explicit hook names (Stop,
PreCompact, PreToolUse, etc.), and no IGNORECASE.
- "… +N lines" is line-anchored.
- "(ctrl+o to expand)" only matches Claude Code's actual collapsed-
output chrome shape `[N tokens] (ctrl+o to expand)`; a bare
parenthetical in user prose stays intact.
Scope:
- `strip_noise()` is no longer called on every normalization path.
Only `_try_claude_code_jsonl` invokes it, per-extracted-message — so
Claude.ai exports, ChatGPT exports, Slack JSON, Codex JSONL, and
plain text with `>` markers pass through fully verbatim. Per-message
application also makes span-eating structurally impossible.
Tests:
- 15 new tests in test_normalize.py pin the boundary: 6 guard user
content that must survive (each of the adversarial repros), 9 assert
real system chrome is still stripped. All pass; full suite 702 pass
(2 failures are the unrelated pre-existing version.py bug, cleared
by #820).
Known limitation (not fixed here): convo_miner.py does not delete
drawers on re-mine, so transcripts mined before this PR keep noise-
filled drawers until the user manually erases + re-mines. Proper fix
needs a schema-version field on drawer metadata + re-mine trigger —
out of scope for this PR.
* feat(normalize): auto-rebuild stale drawers via NORMALIZE_VERSION schema gate
Without this, the strip_noise improvement only helps new mines. Every
user who had already mined Claude Code JSONL sessions would keep their
noise-polluted drawers forever, because convo_miner's file_already_mined
skip short-circuits before re-processing.
Adds a versioned schema gate so upgrades propagate silently:
- palace.NORMALIZE_VERSION=2 — bumped when the normalization pipeline
changes shape (this PR's strip_noise is the v1→v2 bump).
- file_already_mined now returns False if the stored normalize_version
is missing or less than current, triggering a rebuild on next mine.
- Both miners stamp drawers with the current normalize_version.
- convo_miner now purges stale drawers before inserting fresh chunks
(mirrors miner.py's existing delete+insert), extracted into
_file_convo_chunks helper to keep mine_convos under ruff's C901 limit.
User experience: upgrade mempalace, run `mempalace mine` as usual, old
noisy drawers get silently replaced with clean ones. No erase needed,
no "you need to rebuild" changelog footgun.
Tests:
- test_file_already_mined_returns_false_for_stale_normalize_version —
pins the version gate contract for missing/v1/current.
- test_add_drawer_stamps_normalize_version — fresh project-miner drawers
carry the field.
- test_mine_convos_rebuilds_stale_drawers_after_schema_bump — end-to-end
proof that a pre-v2 palace gets silently cleaned on next mine, with
orphan drawers purged and NOT skipped.
Existing test_file_already_mined_check_mtime updated to include the
new field; all other tests unaffected.
* fix: stop hooks from making agents write in chat — save tokens
The save hook and precompact hook were telling the agent to write
diary entries, add drawers, and add KG triples IN THE CHAT WINDOW.
Every line written stays in conversation history and retransmits on
every subsequent turn — ~$1/session in wasted tokens.
Fix: hooks now say "saved in background, no action needed" and use
decision: allow instead of block. The agent continues working without
interruption. All filing happens via the background pipeline.
Also updated hooks README with:
- Known limitation: hooks require session restart after install
- Updated cost section: zero tokens, background-only
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use microsecond timestamp and full content hash in diary entry ID (#819)
* fix: remove unused import 'main' from mempalace/__init__.py
Removed the 'main' import from `mempalace/__init__.py` and updated
`pyproject.toml` to point the script entry point directly to
`mempalace.cli:main`. This ensures the CLI remains functional while
improving code hygiene.
Co-authored-by: igorls <4753812+igorls@users.noreply.github.com>
* merge: full hardened stack + rewrite fact_checker around actual KG API
Merges the full hardened stack (up through #791 drawer-grep) and turns
fact_checker from "dead code hidden behind bare except" into an
actually-working offline contradiction detector with tests.
## Dead paths the PR body advertised but the code never executed
Both buried by a single outer ``except Exception: pass``:
* ``kg.query(subject)`` — ``KnowledgeGraph`` has no ``query()`` method;
it has ``query_entity()``. The attribute error was silently swallowed
and the entire KG branch always returned ``[]``. Now using
``kg.query_entity(subject, direction="outgoing")`` with proper
handling of the ``predicate``/``object``/``current``/``valid_to``
fields the real API returns.
* ``KnowledgeGraph(palace_path=palace_path)`` — the constructor's only
kwarg is ``db_path``. Passing ``palace_path`` raised TypeError,
silently swallowed. Now computing the db_path correctly from
``<palace>/knowledge_graph.sqlite3``, matching the convention the
MCP server already uses.
## Contradiction logic rewritten
The previous ``if kg_pred in claim and fact.object not in claim`` only
fired when text used the SAME predicate word as the KG fact — the exact
opposite of the stated use case ("Bob is Alice's brother" when KG says
husband" would NOT have fired). Replaced with a proper parse → lookup
→ compare pipeline:
* ``_extract_claims`` parses two surface forms ("X is Y's Z" and
"X's Z is Y") into ``(subject, predicate, object)`` triples.
* ``_check_kg_contradictions`` pulls the subject's outgoing facts
and flags two classes:
- ``relationship_mismatch`` when a current KG fact matches the
same ``(subject, object)`` pair but with a different predicate.
- ``stale_fact`` when the exact triple exists but is
``valid_to``-closed in the past.
* Stale-fact detection is now implemented (the PR body claimed it;
the old code silently didn't implement it).
## Performance fix — O(n²) → O(mentioned × n)
``_check_entity_confusion`` previously computed Levenshtein for every
pair of registered names on every ``check_text`` call. For 1,000
registered names that's ~500K edit-distance calls per hook invocation.
Now we first identify which registry names actually appear in the text
(single regex scan), then only compute edit distance between mentioned
and unmentioned names. Pinned by a test that asserts <200ms on a 500-
name registry with zero mentions.
Also: when *both* similar names are mentioned in the text, we no
longer flag them — the user clearly knows they're different people.
## Shared entity-registry loader
``mempalace/miner.py`` already had an mtime-cached loader for
``~/.mempalace/known_entities.json``. fact_checker had a duplicate
implementation that leaked file handles and ignored caching. Extended
miner's cache to expose both the flat set (``_load_known_entities``)
and the raw category dict (``_load_known_entities_raw``); fact_checker
now imports the latter. No more double disk reads, no more handle leak.
## Tests — 24 cases in tests/test_fact_checker.py
All three detection paths + both dead-code regressions:
* ``test_kg_init_uses_db_path_not_palace_path_kwarg`` — pins the
correct KG constructor signature so the ``palace_path=`` bug can't
come back.
* ``test_relationship_mismatch_detected`` — the headline example from
the PR body now actually fires.
* ``test_stale_fact_detected`` — valid_to-closed triple is flagged.
* ``test_current_fact_same_triple_is_not_flagged`` — no false positive
on a still-valid match.
* ``test_performance_bounded_by_mentioned_names`` — 500-name registry,
zero mentions, <200ms. Regression for the O(n²) blowup.
* ``test_no_false_positive_when_both_names_mentioned`` — Mila and
Milla in the same text is fine.
* Plus claim extraction, flatten_names shapes, CLI exit code, empty
text handling, missing-palace graceful fallback, registry-dict
shape support.
785/785 suite pass. ruff + format clean on CI-pinned 0.4.x.
* Optimize entity detection with regex caching and pre-compilation
- Use functools.lru_cache to cache compiled patterns for entity names.
- Pre-compile static pronoun patterns into a single regex.
- Remove redundant .lower() calls in score_entity loop.
Co-authored-by: igorls <4753812+igorls@users.noreply.github.com>
* docs: fix stale milla-jovovich org URLs in website and plugin manifests (#787)
Follow-up to #766 which covers version.py, pyproject.toml, README,
CHANGELOG, and CONTRIBUTING. These 11 files still had the old org
name in URLs:
- website/ (VitePress config + 6 docs pages)
- .claude-plugin/ (plugin.json repository, README marketplace command)
- .codex-plugin/ (plugin.json URLs, README links)
Author name fields are intentionally unchanged.
* test: make diary state path assertion platform-neutral
The Windows CI job failed on:
assert '/.mempalace/state/' in str(state_path)
because Windows uses ``\`` as the path separator, so the substring
never matches. The behavior under test (state file lives outside the
diary dir, under ``~/.mempalace/state/``) is already correct on both
platforms — only the assertion was Unix-only.
Switch to ``state_path.parent`` comparisons that work on any OS.
* test: serialize mine_lock concurrency test with multiprocessing
The macOS CI job failed ``test_lock_blocks_concurrent_access`` because
``fcntl.flock`` on BSD/macOS is per-*process*, not per-FD: two threads
in the same process both acquire even when they open their own file
descriptors. The test passed on Linux (per-FD flock) and Windows
(per-FD ``msvcrt.locking``) but was never actually exercising the
lock's real contract.
``mine_lock`` is designed to serialize multi-*agent* access — i.e.,
separate processes, not threads. Switch the test to
``multiprocessing.get_context('spawn')`` with a module-level worker
(so the spawn pickles cleanly) so it:
1. reflects the actual use case (one lock per mining process);
2. passes on all three OSes without flock-semantics branching;
3. catches real regressions (a broken lock would now let both
processes through, exactly what we care about).
Hold time bumped to 0.3s and the "wait until p1 acquires" delay to
0.2s to tolerate spawn's higher startup latency on macOS/Windows.
* test: verify mine_lock via disjoint critical-section intervals
The previous revision used multiprocessing but still relied on timing
("second process waited at least N seconds") which flakes on CI where
spawn overhead eats into the hold window. Linux CI observed the second
process report a 0.088s wait — below the 0.1s threshold — even though
the lock behavior was correct; spawn was just slow enough that the
first process had nearly finished holding when the second got past
its own spawn.
Switch to effect-based verification: each worker logs its
[enter_time, exit_time] inside the critical section, and the test
asserts the two intervals are disjoint after sorting. A broken lock
would produce overlapping intervals regardless of spawn latency; a
working lock cannot.
Also removed the mp.Queue since we no longer pass timing data back.
* Fix: ruff format with CI-pinned version (0.4.x)
* fix: README audit — 42 TDD tests + hall detection + 7 claim fixes (#835)
* fix: README audit — match every claim to shipped code + add hall detection
TDD audit: wrote 42 tests verifying README claims against codebase.
Fixed all 7 failures:
1. Tool count: 19 → 29 (10 tools were undocumented)
2. Added tool table rows for tunnels, drawer management, system tools
3. Version badge: 3.1.0 → 3.2.0
4. dialect.py file reference: "30x lossless" → "AAAK index format for closet pointers"
5. Wake-up token cost: "~170 tokens" → "~600-900 tokens" (matches layers.py)
6. pyproject.toml version in project structure: v3.0.0 → v3.2.0
7. Hall detection: added detect_hall() to miner.py — drawers now tagged
with hall metadata so palace_graph.py can build hall connections
New code:
- miner.py: detect_hall() — keyword scoring against config hall_keywords,
writes hall field to every drawer's metadata
- tests/test_hall_detection.py — 12 TDD tests (written before code)
- tests/test_readme_claims.py — 42 TDD tests verifying README accuracy
859/859 tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: resolve ruff lint — unused imports and variables
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style: ruff format with CI-pinned 0.4.x
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use conftest fixtures in hall tests for Windows compat
Windows CI fails with NotADirectoryError when ChromaDB tries to
write HNSW files in short-lived TemporaryDirectory. Use conftest
palace_path and tmp_dir fixtures instead — same pattern as all
other tests that touch ChromaDB.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address Igor's review — convo_miner halls, cached config, markdown typo
TDD: wrote tests for convo_miner hall metadata and config caching
BEFORE verifying the code changes.
1. README markdown typo: extra ** in wake-up token row (line 195)
2. convo_miner.py: added _detect_hall_cached() — conversation
drawers now get hall metadata (was missing, Igor caught it)
3. miner.py + convo_miner.py: cached hall_keywords at module level
so config.json isn't re-read per drawer during bulk mine
4. New tests: TestConvoMinerWritesHalls, TestDetectHallCaching
861/861 tests pass. ruff clean.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(website): update vitepress base url for custom domain
* chore(release): bump version strings to 3.3.0 and curate CHANGELOG
Prepare develop for the 3.3.0 release cycle.
Version bumps:
- mempalace/version.py: 3.2.0 -> 3.3.0
- pyproject.toml: 3.2.0 -> 3.3.0
- README.md: pyproject.toml label and shields.io badge
- uv.lock: mempalace 3.0.0 -> 3.3.0 (also fills in resolved dev/extras)
CHANGELOG.md:
- Close out the stale [Unreleased] section as [3.2.0] - 2026-04-12
(v3.2.0 was tagged on that date but the release flip was never made)
- Add a fresh [Unreleased] - v3.3.0 section covering the 49 commits
since v3.2.0: closet layer, BM25 hybrid search, entity metadata,
diary ingest, cross-wing tunnels, drawer-grep, offline fact checker,
LLM-based closet regen, hall detection, cosine-distance fix,
multi-agent locking, README audit, etc.
- Adopt Keep a Changelog + SemVer framing
- Add version compare reference links at the bottom
- Fix stale milla-jovovich/mempalace preamble URL to MemPalace/mempalace
---------
Co-authored-by: MSL <232237854+milla-jovovich@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: eblander <eblander@foundrydigital.com>
Co-authored-by: shafdev <96260000+shafdev@users.noreply.github.com>
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Co-authored-by: mvalentsev <michael@valentsev.ru>
Co-authored-by: Dominique Deschatre <43499065+domiscd@users.noreply.github.com>
33 KiB
MemPalace
The highest-scoring AI memory system ever benchmarked. And it's free.
Every conversation you have with an AI — every decision, every debugging session, every architecture debate — disappears when the session ends. Six months of work, gone. You start over every time.
Other memory systems try to fix this by letting AI decide what's worth remembering. It extracts "user prefers Postgres" and throws away the conversation where you explained why. MemPalace takes a different approach: store everything, then make it findable.
The Palace — Ancient Greek orators memorized entire speeches by placing ideas in rooms of an imaginary building. Walk through the building, find the idea. MemPalace applies the same principle to AI memory: your conversations are organized into wings (people and projects), halls (types of memory), and rooms (specific ideas). No AI decides what matters — you keep every word, and the structure gives you a navigable map instead of a flat search index.
Raw verbatim storage — MemPalace stores your actual exchanges in ChromaDB without summarization or extraction. The 96.6% LongMemEval result comes from this raw mode. We don't burn an LLM to decide what's "worth remembering" — we keep everything and let semantic search find it.
AAAK (experimental) — A lossy abbreviation dialect for packing repeated entities into fewer tokens at scale. Readable by any LLM that reads text — Claude, GPT, Gemini, Llama, Mistral — no decoder needed. AAAK is a separate compression layer, not the storage default, and on the LongMemEval benchmark it currently regresses vs raw mode (84.2% vs 96.6%). We're iterating. See the note above for the honest status.
Local, open, adaptable — MemPalace runs entirely on your machine, on any data you have locally, without using any external API or services. It has been tested on conversations — but it can be adapted for different types of datastores. This is why we're open-sourcing it.
Quick Start · The Palace · AAAK Dialect · Benchmarks · MCP Tools
Highest LongMemEval score ever published — free or paid.
| 96.6% LongMemEval R@5 raw mode, zero API calls |
500/500 questions tested independently reproduced |
$0 No subscription No cloud. Local only. |
Reproducible — runners in benchmarks/. Full results. The 96.6% is from raw verbatim mode, not AAAK or rooms mode (those score lower — see note above).
A Note from Milla & Ben — April 7, 2026
The community caught real problems in this README within hours of launch and we want to address them directly.
What we got wrong:
The AAAK token example was incorrect. We used a rough heuristic (
len(text)//3) for token counts instead of an actual tokenizer. Real counts via OpenAI's tokenizer: the English example is 66 tokens, the AAAK example is 73. AAAK does not save tokens at small scales — it's designed for repeated entities at scale, and the README example was a bad demonstration of that. We're rewriting it."30x lossless compression" was overstated. AAAK is a lossy abbreviation system (entity codes, sentence truncation). Independent benchmarks show AAAK mode scores 84.2% R@5 vs raw mode's 96.6% on LongMemEval — a 12.4 point regression. The honest framing is: AAAK is an experimental compression layer that trades fidelity for token density, and the 96.6% headline number is from RAW mode, not AAAK.
"+34% palace boost" was misleading. That number compares unfiltered search to wing+room metadata filtering. Metadata filtering is a standard ChromaDB feature, not a novel retrieval mechanism. Real and useful, but not a moat.
"Contradiction detection" exists as a separate utility (
fact_checker.py) but is not currently wired into the knowledge graph operations as the README implied."100% with Haiku rerank" is real (we have the result files) but the rerank pipeline is not in the public benchmark scripts. We're adding it.
What's still true and reproducible:
- 96.6% R@5 on LongMemEval in raw mode, on 500 questions, zero API calls — independently reproduced on M2 Ultra in under 5 minutes by @gizmax.
- Local, free, no subscription, no cloud, no data leaving your machine.
- The architecture (wings, rooms, closets, drawers) is real and useful, even if it's not a magical retrieval boost.
What we're doing:
- Rewriting the AAAK example with real tokenizer counts and a scenario where AAAK actually demonstrates compression
- Adding
mode raw / aaak / roomsclearly to the benchmark documentation so the trade-offs are visible- Wiring
fact_checker.pyinto the KG ops so the contradiction detection claim becomes true- Pinning ChromaDB to a tested range (Issue #100), fixing the shell injection in hooks (#110), and addressing the macOS ARM64 segfault (#74)
Thank you to everyone who poked holes in this. Brutal honest criticism is exactly what makes open source work, and it's what we asked for. Special thanks to @panuhorsmalahti, @lhl, @gizmax, and everyone who filed an issue or a PR in the first 48 hours. We're listening, we're fixing, and we'd rather be right than impressive.
— Milla Jovovich & Ben Sigman
An important follow up note regarding fake MemPalace websites - April 11, 2026
Several Community Members (#267, #326, #506) have pointed out there are fake MemPalace websites popping up, including ones with Malware.
To be super clear, MemPalace has no website (at least for now), so anything claiming to be one is false.
Thanks to our Community Members for letting us know about the problem.
Stay safe out there.
Quick Start
pip install mempalace
# Set up your world — who you work with, what your projects are
mempalace init ~/projects/myapp
# Mine your data
mempalace mine ~/projects/myapp # projects — code, docs, notes
mempalace mine ~/chats/ --mode convos # convos — Claude, ChatGPT, Slack exports
mempalace mine ~/chats/ --mode convos --extract general # general — classifies into decisions, milestones, problems
# Search anything you've ever discussed
mempalace search "why did we switch to GraphQL"
# Your AI remembers
mempalace status
Three mining modes: projects (code and docs), convos (conversation exports), and general (auto-classifies into decisions, preferences, milestones, problems, and emotional context). Everything stays on your machine.
How You Actually Use It
After the one-time setup (install → init → mine), you don't run MemPalace commands manually. Your AI uses it for you. There are two ways, depending on which AI you use.
With Claude Code (recommended)
Native marketplace install:
claude plugin marketplace add milla-jovovich/mempalace
claude plugin install --scope user mempalace
Restart Claude Code, then type /skills to verify "mempalace" appears.
With Claude, ChatGPT, Cursor, Gemini (MCP-compatible tools)
# Connect MemPalace once
claude mcp add mempalace -- python -m mempalace.mcp_server
Now your AI has 29 tools available through MCP. Ask it anything:
"What did we decide about auth last month?"
Claude calls mempalace_search automatically, gets verbatim results, and answers you. You never type mempalace search again. The AI handles it.
MemPalace also works natively with Gemini CLI (which handles the server and save hooks automatically) — see the Gemini CLI Integration Guide.
With local models (Llama, Mistral, or any offline LLM)
Local models generally don't speak MCP yet. Two approaches:
1. Wake-up command — load your world into the model's context:
mempalace wake-up > context.txt
# Paste context.txt into your local model's system prompt
This gives your local model ~600-900 tokens of critical facts (in AAAK if you prefer) before you ask a single question.
2. CLI search — query on demand, feed results into your prompt:
mempalace search "auth decisions" > results.txt
# Include results.txt in your prompt
Or use the Python API:
from mempalace.searcher import search_memories
results = search_memories("auth decisions", palace_path="~/.mempalace/palace")
# Inject into your local model's context
Either way — your entire memory stack runs offline. ChromaDB on your machine, Llama on your machine, AAAK for compression, zero cloud calls.
The Problem
Decisions happen in conversations now. Not in docs. Not in Jira. In conversations with Claude, ChatGPT, Copilot. The reasoning, the tradeoffs, the "we tried X and it failed because Y" — all trapped in chat windows that evaporate when the session ends.
Six months of daily AI use = 19.5 million tokens. That's every decision, every debugging session, every architecture debate. Gone.
| Approach | Tokens loaded | Annual cost |
|---|---|---|
| Paste everything | 19.5M — doesn't fit any context window | Impossible |
| LLM summaries | ~650K | ~$507/yr |
| MemPalace wake-up | ~600-900 tokens | ~$0.70/yr |
| MemPalace + 5 searches | ~13,500 tokens | ~$10/yr |
MemPalace loads ~600-900 tokens of critical facts on wake-up — your team, your projects, your preferences. Then searches only when needed. $10/year to remember everything vs $507/year for summaries that lose context.
How It Works
The Palace
The layout is fairly simple, though it took a long time to get there.
It starts with a wing. Every project, person, or topic you're filing gets its own wing in the palace.
Each wing has rooms connected to it, where information is divided into subjects that relate to that wing — so every room is a different element of what your project contains. Project ideas could be one room, employees could be another, financial statements another. There can be an endless number of rooms that split the wing into sections. The MemPalace install detects these for you automatically, and of course you can personalize it any way you feel is right.
Every room has a closet connected to it, and here's where things get interesting. We've developed an AI language called AAAK. Don't ask — it's a whole story of its own. Your agent learns the AAAK shorthand every time it wakes up. Because AAAK is essentially English, but a very truncated version, your agent understands how to use it in seconds. It comes as part of the install, built into the MemPalace code. In our next update, we'll add AAAK directly to the closets, which will be a real game changer — the amount of info in the closets will be much bigger, but it will take up far less space and far less reading time for your agent.
Inside those closets are drawers, and those drawers are where your original files live. In this first version, we haven't used AAAK as a closet tool, but even so, the summaries have shown 96.6% recall in all the benchmarks we've done across multiple benchmarking platforms. Once the closets use AAAK, searches will be even faster while keeping every word exact. But even now, the closet approach has been a huge boon to how much info is stored in a small space — it's used to easily point your AI agent to the drawer where your original file lives. You never lose anything, and all this happens in seconds.
There are also halls, which connect rooms within a wing, and tunnels, which connect rooms from different wings to one another. So finding things becomes truly effortless — we've given the AI a clean and organized way to know where to start searching, without having to look through every keyword in huge folders.
You say what you're looking for and boom, it already knows which wing to go to. Just that in itself would have made a big difference. But this is beautiful, elegant, organic, and most importantly, efficient.
+------------------------------------------------------------+
¦ WING: Person ¦
¦ ¦
¦ +----------+ +----------+ ¦
¦ ¦ Room A ¦ --hall-- ¦ Room B ¦ ¦
¦ +----------+ +----------+ ¦
¦ ¦ ¦
¦ v ¦
¦ +----------+ +----------+ ¦
¦ ¦ Closet ¦ ---> ¦ Drawer ¦ ¦
¦ +----------+ +----------+ ¦
+---------+--------------------------------------------------+
¦
tunnel
¦
+---------+--------------------------------------------------+
¦ WING: Project ¦
¦ ¦ ¦
¦ +----------+ +----------+ ¦
¦ ¦ Room A ¦ --hall-- ¦ Room C ¦ ¦
¦ +----------+ +----------+ ¦
¦ ¦ ¦
¦ v ¦
¦ +----------+ +----------+ ¦
¦ ¦ Closet ¦ ---> ¦ Drawer ¦ ¦
¦ +----------+ +----------+ ¦
+------------------------------------------------------------+
Wings — a person or project. As many as you need. Rooms — specific topics within a wing. Auth, billing, deploy — endless rooms. Halls — connections between related rooms within the same wing. If Room A (auth) and Room B (security) are related, a hall links them. Tunnels — connections between wings. When Person A and a Project both have a room about "auth," a tunnel cross-references them automatically. Closets — summaries that point to the original content. (In v3.0.0 these are plain-text summaries; AAAK-encoded closets are coming in a future update — see Task #30.) Drawers — the original verbatim files. The exact words, never summarized.
Halls are memory types — the same in every wing, acting as corridors:
hall_facts— decisions made, choices locked inhall_events— sessions, milestones, debugginghall_discoveries— breakthroughs, new insightshall_preferences— habits, likes, opinionshall_advice— recommendations and solutions
Rooms are named ideas — auth-migration, graphql-switch, ci-pipeline. When the same room appears in different wings, it creates a tunnel — connecting the same topic across domains:
wing_kai / hall_events / auth-migration → "Kai debugged the OAuth token refresh"
wing_driftwood / hall_facts / auth-migration → "team decided to migrate auth to Clerk"
wing_priya / hall_advice / auth-migration → "Priya approved Clerk over Auth0"
Same room. Three wings. The tunnel connects them.
Why Structure Matters
Tested on 22,000+ real conversation memories:
Search all closets: 60.9% R@10
Search within wing: 73.1% (+12%)
Search wing + hall: 84.8% (+24%)
Search wing + room: 94.8% (+34%)
Wings and rooms aren't cosmetic. They're a 34% retrieval improvement. The palace structure is the product.
The Memory Stack
| Layer | What | Size | When |
|---|---|---|---|
| L0 | Identity — who is this AI? | ~50 tokens | Always loaded |
| L1 | Critical facts — team, projects, preferences | ~120 tokens (AAAK) | Always loaded |
| L2 | Room recall — recent sessions, current project | On demand | When topic comes up |
| L3 | Deep search — semantic query across all closets | On demand | When explicitly asked |
Your AI wakes up with L0 + L1 (~600-900 tokens) and knows your world. Searches only fire when needed.
AAAK Dialect (experimental)
AAAK is a lossy abbreviation system — entity codes, structural markers, and sentence truncation — designed to pack repeated entities and relationships into fewer tokens at scale. It is readable by any LLM that reads text (Claude, GPT, Gemini, Llama, Mistral) without a decoder, so a local model can use it without any cloud dependency.
Honest status (April 2026):
- AAAK is lossy, not lossless. It uses regex-based abbreviation, not reversible compression.
- It does not save tokens at small scales. Short text already tokenizes efficiently. AAAK overhead (codes, separators) costs more than it saves on a few sentences.
- It can save tokens at scale — in scenarios with many repeated entities (a team mentioned hundreds of times, the same project across thousands of sessions), the entity codes amortize.
- AAAK currently regresses LongMemEval vs raw verbatim retrieval (84.2% R@5 vs 96.6%). The 96.6% headline number is from raw mode, not AAAK mode.
- The MemPalace storage default is raw verbatim text in ChromaDB — that's where the benchmark wins come from. AAAK is a separate compression layer for context loading, not the storage format.
We're iterating on the dialect spec, adding a real tokenizer for stats, and exploring better break points for when to use it. Track progress in Issue #43 and #27.
Contradiction Detection (experimental, not yet wired into KG)
A separate utility (fact_checker.py) can check assertions against entity facts. It's not currently called automatically by the knowledge graph operations — this is being fixed (track in Issue #27). When enabled it catches things like:
Input: "Soren finished the auth migration"
Output: 🔴 AUTH-MIGRATION: attribution conflict — Maya was assigned, not Soren
Input: "Kai has been here 2 years"
Output: 🟡 KAI: wrong_tenure — records show 3 years (started 2023-04)
Input: "The sprint ends Friday"
Output: 🟡 SPRINT: stale_date — current sprint ends Thursday (updated 2 days ago)
Facts checked against the knowledge graph. Ages, dates, and tenures calculated dynamically — not hardcoded.
Real-World Examples
Solo developer across multiple projects
# Mine each project's conversations
mempalace mine ~/chats/orion/ --mode convos --wing orion
mempalace mine ~/chats/nova/ --mode convos --wing nova
mempalace mine ~/chats/helios/ --mode convos --wing helios
# Six months later: "why did I use Postgres here?"
mempalace search "database decision" --wing orion
# → "Chose Postgres over SQLite because Orion needs concurrent writes
# and the dataset will exceed 10GB. Decided 2025-11-03."
# Cross-project search
mempalace search "rate limiting approach"
# → finds your approach in Orion AND Nova, shows the differences
Team lead managing a product
# Mine Slack exports and AI conversations
mempalace mine ~/exports/slack/ --mode convos --wing driftwood
mempalace mine ~/.claude/projects/ --mode convos
# "What did Soren work on last sprint?"
mempalace search "Soren sprint" --wing driftwood
# → 14 closets: OAuth refactor, dark mode, component library migration
# "Who decided to use Clerk?"
mempalace search "Clerk decision" --wing driftwood
# → "Kai recommended Clerk over Auth0 — pricing + developer experience.
# Team agreed 2026-01-15. Maya handling the migration."
Before mining: split mega-files
Some transcript exports concatenate multiple sessions into one huge file:
mempalace split ~/chats/ # split into per-session files
mempalace split ~/chats/ --dry-run # preview first
mempalace split ~/chats/ --min-sessions 3 # only split files with 3+ sessions
Knowledge Graph
Temporal entity-relationship triples — like Zep's Graphiti, but SQLite instead of Neo4j. Local and free.
from mempalace.knowledge_graph import KnowledgeGraph
kg = KnowledgeGraph()
kg.add_triple("Kai", "works_on", "Orion", valid_from="2025-06-01")
kg.add_triple("Maya", "assigned_to", "auth-migration", valid_from="2026-01-15")
kg.add_triple("Maya", "completed", "auth-migration", valid_from="2026-02-01")
# What's Kai working on?
kg.query_entity("Kai")
# → [Kai → works_on → Orion (current), Kai → recommended → Clerk (2026-01)]
# What was true in January?
kg.query_entity("Maya", as_of="2026-01-20")
# → [Maya → assigned_to → auth-migration (active)]
# Timeline
kg.timeline("Orion")
# → chronological story of the project
Facts have validity windows. When something stops being true, invalidate it:
kg.invalidate("Kai", "works_on", "Orion", ended="2026-03-01")
Now queries for Kai's current work won't return Orion. Historical queries still will.
| Feature | MemPalace | Zep (Graphiti) |
|---|---|---|
| Storage | SQLite (local) | Neo4j (cloud) |
| Cost | Free | $25/mo+ |
| Temporal validity | Yes | Yes |
| Self-hosted | Always | Enterprise only |
| Privacy | Everything local | SOC 2, HIPAA |
Specialist Agents
Create agents that focus on specific areas. Each agent gets its own wing and diary in the palace — not in your CLAUDE.md. Add 50 agents, your config stays the same size.
~/.mempalace/agents/
├── reviewer.json # code quality, patterns, bugs
├── architect.json # design decisions, tradeoffs
└── ops.json # deploys, incidents, infra
Your CLAUDE.md just needs one line:
You have MemPalace agents. Run mempalace_list_agents to see them.
The AI discovers its agents from the palace at runtime. Each agent:
- Has a focus — what it pays attention to
- Keeps a diary — written in AAAK, persists across sessions
- Builds expertise — reads its own history to stay sharp in its domain
# Agent writes to its diary after a code review
mempalace_diary_write("reviewer",
"PR#42|auth.bypass.found|missing.middleware.check|pattern:3rd.time.this.quarter|★★★★")
# Agent reads back its history
mempalace_diary_read("reviewer", last_n=10)
# → last 10 findings, compressed in AAAK
Each agent is a specialist lens on your data. The reviewer remembers every bug pattern it's seen. The architect remembers every design decision. The ops agent remembers every incident. They don't share a scratchpad — they each maintain their own memory.
Letta charges $20–200/mo for agent-managed memory. MemPalace does it with a wing.
MCP Server
# Via plugin (recommended)
claude plugin marketplace add milla-jovovich/mempalace
claude plugin install --scope user mempalace
# Or manually
claude mcp add mempalace -- python -m mempalace.mcp_server
29 Tools
Palace (read)
| Tool | What |
|---|---|
mempalace_status |
Palace overview + AAAK spec + memory protocol |
mempalace_list_wings |
Wings with counts |
mempalace_list_rooms |
Rooms within a wing |
mempalace_get_taxonomy |
Full wing → room → count tree |
mempalace_search |
Semantic search with wing/room filters |
mempalace_check_duplicate |
Check before filing |
mempalace_get_aaak_spec |
AAAK dialect reference |
Palace (write)
| Tool | What |
|---|---|
mempalace_add_drawer |
File verbatim content |
mempalace_delete_drawer |
Remove by ID |
Knowledge Graph
| Tool | What |
|---|---|
mempalace_kg_query |
Entity relationships with time filtering |
mempalace_kg_add |
Add facts |
mempalace_kg_invalidate |
Mark facts as ended |
mempalace_kg_timeline |
Chronological entity story |
mempalace_kg_stats |
Graph overview |
Navigation
| Tool | What |
|---|---|
mempalace_traverse |
Walk the graph from a room across wings |
mempalace_find_tunnels |
Find rooms bridging two wings |
mempalace_graph_stats |
Graph connectivity overview |
mempalace_create_tunnel |
Create explicit cross-wing link between two rooms |
mempalace_list_tunnels |
List all explicit tunnels, filter by wing |
mempalace_delete_tunnel |
Remove a tunnel by ID |
mempalace_follow_tunnels |
Follow tunnels from a room to connected rooms in other wings |
Drawer Management
| Tool | What |
|---|---|
mempalace_get_drawer |
Fetch a single drawer by ID |
mempalace_list_drawers |
Paginated drawer listing |
mempalace_update_drawer |
Update drawer content or metadata |
Agent Diary
| Tool | What |
|---|---|
mempalace_diary_write |
Write AAAK diary entry |
mempalace_diary_read |
Read recent diary entries |
System
| Tool | What |
|---|---|
mempalace_hook_settings |
Get/set hook behavior (silent save, toast) |
mempalace_memories_filed_away |
Check if recent checkpoint was saved |
mempalace_reconnect |
Force DB reconnect after external writes |
The AI learns AAAK and the memory protocol automatically from the mempalace_status response. No manual configuration.
Auto-Save Hooks
Two hooks for Claude Code that automatically save memories during work:
Save Hook — every 15 messages, triggers a structured save. Topics, decisions, quotes, code changes. Also regenerates the critical facts layer.
PreCompact Hook — fires before context compression. Emergency save before the window shrinks.
{
"hooks": {
"Stop": [{"matcher": "", "hooks": [{"type": "command", "command": "/path/to/mempalace/hooks/mempal_save_hook.sh"}]}],
"PreCompact": [{"matcher": "", "hooks": [{"type": "command", "command": "/path/to/mempalace/hooks/mempal_precompact_hook.sh"}]}]
}
}
Optional auto-ingest: Set the MEMPAL_DIR environment variable to a directory path and the hooks will automatically run mempalace mine on that directory during each save trigger (background on stop, synchronous on precompact).
Benchmarks
Tested on standard academic benchmarks — reproducible, published datasets.
| Benchmark | Mode | Score | API Calls |
|---|---|---|---|
| LongMemEval R@5 | Raw (ChromaDB only) | 96.6% | Zero |
| LongMemEval R@5 | Hybrid + Haiku rerank | 100% (500/500) | ~500 |
| LoCoMo R@10 | Raw, session level | 60.3% | Zero |
| Personal palace R@10 | Heuristic bench | 85% | Zero |
| Palace structure impact | Wing+room filtering | +34% R@10 | Zero |
The 96.6% raw score is the highest published LongMemEval result requiring no API key, no cloud, and no LLM at any stage.
vs Published Systems
| System | LongMemEval R@5 | API Required | Cost |
|---|---|---|---|
| MemPalace (hybrid) | 100% | Optional | Free |
| Supermemory ASMR | ~99% | Yes | — |
| MemPalace (raw) | 96.6% | None | Free |
| Mastra | 94.87% | Yes (GPT) | API costs |
| Mem0 | ~85% | Yes | $19–249/mo |
| Zep | ~85% | Yes | $25/mo+ |
All Commands
# Setup
mempalace init <dir> # guided onboarding + AAAK bootstrap
# Mining
mempalace mine <dir> # mine project files
mempalace mine <dir> --mode convos # mine conversation exports
mempalace mine <dir> --mode convos --wing myapp # tag with a wing name
# Splitting
mempalace split <dir> # split concatenated transcripts
mempalace split <dir> --dry-run # preview
# Search
mempalace search "query" # search everything
mempalace search "query" --wing myapp # within a wing
mempalace search "query" --room auth-migration # within a room
# Memory stack
mempalace wake-up # load L0 + L1 context
mempalace wake-up --wing driftwood # project-specific
# Compression
mempalace compress --wing myapp # AAAK compress
# Status
mempalace status # palace overview
# MCP
mempalace mcp # show MCP setup command
All commands accept --palace <path> to override the default location.
Configuration
Global (~/.mempalace/config.json)
{
"palace_path": "/custom/path/to/palace",
"collection_name": "mempalace_drawers",
"people_map": {"Kai": "KAI", "Priya": "PRI"}
}
Wing config (~/.mempalace/wing_config.json)
Generated by mempalace init. Maps your people and projects to wings:
{
"default_wing": "wing_general",
"wings": {
"wing_kai": {"type": "person", "keywords": ["kai", "kai's"]},
"wing_driftwood": {"type": "project", "keywords": ["driftwood", "analytics", "saas"]}
}
}
Identity (~/.mempalace/identity.txt)
Plain text. Becomes Layer 0 — loaded every session.
File Reference
| File | What |
|---|---|
cli.py |
CLI entry point |
config.py |
Configuration loading and defaults |
normalize.py |
Converts 5 chat formats to standard transcript |
mcp_server.py |
MCP server — 29 tools, AAAK auto-teach, memory protocol |
miner.py |
Project file ingest |
convo_miner.py |
Conversation ingest — chunks by exchange pair |
searcher.py |
Semantic search via ChromaDB |
layers.py |
4-layer memory stack |
dialect.py |
AAAK index format for closet pointers |
knowledge_graph.py |
Temporal entity-relationship graph (SQLite) |
palace_graph.py |
Room-based navigation graph |
onboarding.py |
Guided setup — generates AAAK bootstrap + wing config |
entity_registry.py |
Entity code registry |
entity_detector.py |
Auto-detect people and projects from content |
split_mega_files.py |
Split concatenated transcripts into per-session files |
hooks/mempal_save_hook.sh |
Auto-save every N messages |
hooks/mempal_precompact_hook.sh |
Emergency save before compaction |
Project Structure
mempalace/
├── README.md ← you are here
├── mempalace/ ← core package (README)
│ ├── cli.py ← CLI entry point
│ ├── mcp_server.py ← MCP server (29 tools)
│ ├── knowledge_graph.py ← temporal entity graph
│ ├── palace_graph.py ← room navigation graph
│ ├── dialect.py ← AAAK compression
│ ├── miner.py ← project file ingest
│ ├── convo_miner.py ← conversation ingest
│ ├── searcher.py ← semantic search
│ ├── onboarding.py ← guided setup
│ └── ... ← see mempalace/README.md
├── benchmarks/ ← reproducible benchmark runners
│ ├── README.md ← reproduction guide
│ ├── BENCHMARKS.md ← full results + methodology
│ ├── longmemeval_bench.py ← LongMemEval runner
│ ├── locomo_bench.py ← LoCoMo runner
│ └── membench_bench.py ← MemBench runner
├── hooks/ ← Claude Code auto-save hooks
│ ├── README.md ← hook setup guide
│ ├── mempal_save_hook.sh ← save every N messages
│ └── mempal_precompact_hook.sh ← emergency save
├── examples/ ← usage examples
│ ├── basic_mining.py
│ ├── convo_import.py
│ └── mcp_setup.md
├── tests/ ← test suite (README)
├── assets/ ← logo + brand assets
└── pyproject.toml ← package config (v3.3.0)
Requirements
- Python 3.9+
chromadb>=0.4.0pyyaml>=6.0
No API key. No internet after install. Everything local.
pip install mempalace
Contributing
PRs welcome. See CONTRIBUTING.md for setup and guidelines.
License
MIT — see LICENSE.