Skip to content

feat: add MemPalace memory provider plugin - #6871

Closed
mssteuer wants to merge 1 commit into
NousResearch:mainfrom
mssteuer:feat/mempalace-memory-provider
Closed

feat: add MemPalace memory provider plugin#6871
mssteuer wants to merge 1 commit into
NousResearch:mainfrom
mssteuer:feat/mempalace-memory-provider

Conversation

@mssteuer

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds MemPalace as a memory provider plugin for Hermes Agent, following the existing MemoryProvider ABC in agent/memory_provider.py.

MemPalace is a local-only memory system that stores conversations verbatim in ChromaDB with semantic search — no summarization, no extraction, no API calls. It holds the highest published LongMemEval recall score (96.6% R@5, independently reproduced). The philosophy: store everything, make it findable.

This plugin bridges MemPalace's 4-layer memory stack and temporal knowledge graph into the Hermes lifecycle, giving agents deep cross-session recall without any cloud dependencies or subscriptions.

Related Issue

N/A — new plugin contribution. Happy to create a tracking issue if preferred.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

3 new files in plugins/memory/mempalace/:

  • __init__.py (~550 lines) — Full MemoryProvider implementation:

    • MemPalaceProvider class implementing all abstract methods + optional hooks
    • 5 tool schemas: palace_search, palace_store, palace_browse, palace_kg, palace_status
    • Lazy import of mempalace (graceful degradation if not installed)
    • Thread-safe background writes via sync_turn
    • Profile-scoped config via $HERMES_HOME/config.yaml
  • plugin.yaml — Plugin manifest declaring on_session_end, on_pre_compress, on_memory_write hooks

  • README.md — Documentation with architecture diagrams, tool reference, config options, and palace structure

Architecture

System Prompt Injection (L0 + L1)
  └── L0: ~/.mempalace/identity.txt (~100 tokens)
  └── L1: Top 15 drawers by importance (~500-800 tokens)

Prefetch (each turn)
  └── L3: Semantic search on user message (3 results)

sync_turn (background thread)
  └── Store user+assistant exchange verbatim as ChromaDB drawers

on_session_end
  └── Archive full session at importance=4

on_pre_compress
  └── Save about-to-be-discarded context at importance=4

on_memory_write
  └── Mirror MEMORY.md/USER.md writes at importance=5

4-Layer Memory Stack

Layer What Tokens When
L0 Identity (~/.mempalace/identity.txt) ~100 Always (system prompt)
L1 Essential Story (top 15 drawers) ~500-800 Always (system prompt)
L2 On-Demand (wing/room filtered) ~200-500 Tool call via palace_browse
L3 Deep Search (full semantic) unlimited Tool call via palace_search / prefetch

5 Agent Tools

Tool Description
palace_search Semantic search across all memories (ChromaDB embeddings)
palace_store Store a memory with wing/room/importance metadata
palace_browse Navigate palace taxonomy — list wings, rooms, drawer counts
palace_kg Temporal knowledge graph — query, add, timeline, invalidate
palace_status Palace stats: total drawers, layer info, KG status

Palace Organization

Palace (ChromaDB)
├── Wings (projects/topics)
│   └── hermes/              (default wing, configurable)
│       ├── conversation     — live turn-by-turn verbatim storage
│       ├── session_archive  — end-of-session consolidated
│       ├── precompress      — saved before context compression
│       ├── memory_user      — mirrored from built-in USER.md
│       ├── memory_memory    — mirrored from built-in MEMORY.md
│       └── general          — explicit palace_store calls
└── Knowledge Graph (SQLite)
    ├── Entities (people, projects, tools, concepts)
    └── Triples (subject → predicate → object, with temporal validity)

How to Test

  1. Install the dependency:

    pip install mempalace
  2. Enable the provider:

    # ~/.hermes/config.yaml
    memory:
      provider: mempalace
  3. Verify the plugin loads:

    import sys; sys.path.insert(0, '.')
    from plugins.memory.mempalace import MemPalaceProvider
    
    p = MemPalaceProvider(config={'palace_path': '/tmp/test_palace', 'wing': 'test'})
    assert p.is_available()
    p.initialize('test-session')
    
    # Store and search
    p._handle_store({'content': 'Test memory about Python preferences', 'room': 'preferences'})
    result = p._handle_search({'query': 'Python preferences'})
    assert 'Python' in result
  4. Test lifecycle hooks:

    import time
    # sync_turn (background thread)
    p.sync_turn('What is X?', 'X is Y.')
    time.sleep(1)
    result = p._handle_search({'query': 'What is X'})
    assert 'X is Y' in result
    
    # on_memory_write mirror
    p.on_memory_write('add', 'user', 'User prefers dark mode')
    result = p._handle_search({'query': 'dark mode'})
    assert 'dark mode' in result
    
    # Knowledge graph
    p._handle_kg({'action': 'add', 'subject': 'Alice', 'predicate': 'works_on', 'object': 'ProjectX'})
    result = p._handle_kg({'action': 'query', 'entity': 'Alice'})
    assert 'works_on' in result
  5. Clean up:

    rm -rf /tmp/test_palace

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: Ubuntu 24.04 (Debian-based), Python 3.11

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — plugin includes README.md
  • I've updated cli-config.yaml.example if I added/changed config keys — N/A (config is plugin-scoped under plugins.mempalace)
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — N/A
  • I've considered cross-platform impact (Windows, macOS) — uses os.path.expanduser, os.makedirs, threading (all cross-platform). ChromaDB and SQLite are cross-platform.
  • I've updated tool descriptions/schemas if I changed tool behavior — N/A (new tools only)

Screenshots / Logs

Initialization

MemPalace connected: ~/.mempalace/palace (0 drawers)
MemPalace knowledge graph initialized

Store + Search

>>> p._handle_store({'content': 'Michael prefers direct communication and dad humor'})
{"stored": true, "wing": "hermes", "room": "general", "drawer_id": "drawer_hermes_general_a1b2c3..."}

>>> p._handle_search({'query': 'communication style'})
{"results": "## L3 — SEARCH RESULTS for \"communication style\"\n  [1] hermes/general (sim=0.753)\n      Michael prefers direct communication and dad humor"}

Knowledge Graph

>>> p._handle_kg({'action': 'add', 'subject': 'Michael', 'predicate': 'works_at', 'object': 'Casper Association'})
{"stored": true, "triple": ["Michael", "works_at", "Casper Association"], "valid_from": "2026-04-10"}

>>> p._handle_kg({'action': 'query', 'entity': 'Michael'})
{"entity": "Michael", "facts": [{"subject": "Michael", "predicate": "works_at", "object": "Casper Association", "valid_from": "2026-04-10", "valid_to": null, "current": true}]}

System Prompt Block (~900 tokens)

══ MEMPALACE (42 memories) ══
You have a memory palace with semantic search. Use palace_search to recall past conversations and decisions.

## L0 — IDENTITY
I am Jean Clawd, a personal AI assistant for Michael.

## L1 — ESSENTIAL STORY
[preferences]
  - Michael prefers direct communication and dad humor
[session_archive]
  - Discussion about MemPalace integration for Hermes Agent...

Credits:

Integrates MemPalace (https://github.com/milla-jovovich/mempalace) as a
pluggable memory provider for Hermes Agent.

Features:
- 4-layer memory stack (L0 identity → L3 deep semantic search)
- Verbatim conversation storage via ChromaDB (no summarization)
- Temporal knowledge graph with entity resolution (SQLite)
- 5 agent tools: palace_search, palace_store, palace_browse, palace_kg, palace_status
- Full lifecycle hooks: sync_turn, on_session_end, on_pre_compress, on_memory_write
- Zero API calls, local-only, free

Based on MemPalace by Ben Sigman — 96.6% LongMemEval R@5 (highest published).

Requires: pip install mempalace
@bensig

bensig commented Apr 10, 2026

Copy link
Copy Markdown

mempalace updated to 3.1.0 today - more updates coming soon

@mssteuer

Copy link
Copy Markdown
Contributor Author

Thanks @bensig — verified the plugin against mempalace 3.1.0 locally, no changes needed.

Compatibility check (mempalace 3.1.0):

  • All imports resolve: mempalace.config.MempalaceConfig, mempalace.layers.MemoryStack, mempalace.palace.get_collection, mempalace.knowledge_graph.KnowledgeGraph
  • All method signatures used by the plugin still match on 3.1.0:
    • MemoryStack(palace_path=..., identity_path=...), .wake_up(wing=...), .search(query, wing, room, n_results), .status()
    • MemoryStack.l3.search(query, wing, room, n_results) (used for deep search)
    • KnowledgeGraph(db_path=...), .query_entity(name, as_of, direction), .add_triple(...), .invalidate(...), .stats()
    • get_collection(palace_path, collection_name)
  • End-to-end round trip through the plugin handlers works against a fresh 3.1.0 palace:
    • palace_store → returned drawer_id
    • palace_search → retrieved the drawer via L3 semantic search (sim=0.612)
    • palace_status → reports provider: mempalace, layer info, drawer count

The plugin has no version pin on mempalace (the README just says pip install mempalace), so users on 3.1.0 get it automatically. Happy to add a minimum version constraint (mempalace>=3.1.0) if reviewers want one, but current code works across the 3.x line as far as I can tell.

@teknium1

Copy link
Copy Markdown
Contributor

This needs further review before it can be merged into core — no tests, heavy dependency chain (chromadb), and we'd want to verify the external package. For now, we'd recommend publishing this as a standalone plugin repo and sharing it in our Discord. That way users can install it independently and it can iterate faster without being tied to our release cycle. Happy to revisit for core inclusion once it has test coverage and some community usage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants