trusty-memory
Memory palace MCP server (HTTP/SSE + Unix domain socket) backed by usearch
vector store, SQLite metadata, and fastembed embeddings. Stores and retrieves
natural-language memories organized into named "palaces" (namespaces), with an
optional knowledge-graph layer for structured triples.
Claude Code integration uses the companion trusty-memory-mcp-bridge binary
which speaks stdio MCP and pipes it over the daemon's Unix domain socket.
The legacy in-process serve --stdio flag was removed in issue #150 because
it deadlocked on redb's exclusive write lock whenever a long-lived daemon
was already running.
Integrates with Claude Code and any other MCP-aware client as a first-class long-term memory backend.
System Requirements
- RAM: 512 MB minimum; 1 GB+ recommended (ONNX embedding model loads ~22 MB, usearch index scales with corpus size)
- Disk: ~100 MB for the model cache on first run
(
~/Library/Application Support/trusty-memory/on macOS,~/.local/share/trusty-memory/on Linux) - Rust: 1.88+ (if building from source)
Installation
The installed binary is named trusty-memory.
Quick Start
Start the daemon
By default, serve self-spawns a detached background daemon (alias for
trusty-memory start) and returns control to the shell so you keep your
prompt. The daemon binds HTTP/SSE on a dynamic port in the 7070..=7079
range (with OS fallback) and writes the resolved address to its
discovery file. Pass --foreground to keep the daemon inline (used by
launchd / systemd / Docker), or --http <ADDR> to pin a specific address.
Browser dashboard + REST API
The same trusty-memory serve daemon serves the embedded Svelte admin UI
at the bound address (printed by trusty-memory monitor web once the
daemon is running) and a REST API under /api/v1/.
Bind to a named palace
When all tool calls should default to one palace namespace, use --palace:
With a default set, the palace argument becomes optional in every MCP tool
call.
Claude Code Integration
Run trusty-memory setup once — it installs the launchd LaunchAgent
(macOS), pre-warms the embedder cache, and patches every Claude settings
file it finds with the canonical MCP server entry. The MCP entry points
at trusty-memory-mcp-bridge, a thin stdio-to-UDS pipe (PR #149) that
Claude Code spawns as a child process. The bridge then connects to the
long-lived daemon over the Unix domain socket the daemon owns.
If you prefer to edit .mcp.json (or ~/.claude/mcp.json) by hand:
Claude Code auto-discovers .mcp.json on project open. The
trusty-memory-mcp-bridge binary must be on PATH and the daemon must be
running (started either by trusty-memory setup's LaunchAgent or by
trusty-memory start / trusty-memory serve).
Available MCP Tools
All 12 tools are exposed via both the MCP protocol (over the
trusty-memory-mcp-bridge → UDS path) and the HTTP API (/api/v1/). The
palace argument is required unless the server was started with
--palace <name>.
Memory tools
| Tool | Arguments | Description |
|---|---|---|
memory_remember |
palace, text, room?, tags? |
Store a memory. Returns the drawer_id. |
memory_recall |
palace, query, top_k? |
Hybrid BM25+vector recall (L0/L1/L2 layers). |
memory_recall_deep |
palace, query, top_k? |
Deep recall (L3 — slower, higher recall). |
memory_list |
palace, room?, tag?, limit? |
List stored memories, optionally filtered. |
memory_forget |
palace, drawer_id |
Delete a specific memory by ID. |
Palace management tools
| Tool | Arguments | Description |
|---|---|---|
palace_create |
name, description? |
Create a new palace namespace. See Palace as Project for naming rules. |
palace_list |
— | List all palaces and their IDs. |
palace_info |
palace |
Palace metadata and statistics. |
Knowledge graph tools
| Tool | Arguments | Description |
|---|---|---|
kg_assert |
palace, subject, predicate, object, confidence?, provenance? |
Assert a knowledge triple. |
kg_query |
palace, subject |
Query triples by subject. |
Dream and status tools
| Tool | Arguments | Description |
|---|---|---|
memory_dream |
palace? |
Run a consolidation cycle (merge near-duplicates, prune, compact). |
memory_status |
— | Global statistics (total drawers, vectors, KG triples). |
Inter-project messaging (issue #99)
| Tool | Arguments | Description |
|---|---|---|
memory_send_message |
to_palace, purpose, content, from_palace? |
Deliver a message to another palace's inbox. |
Plus two CLI subcommands:
trusty-memory send-message --to <palace> --purpose <p> --content <text> [--from <palace>]— non-MCP entry point. Posts to the daemon'sPOST /api/v1/messages.trusty-memory inbox-check [--palace <id>]— installed as a Claude CodeSessionStarthook bysetup. Reads unread messages from the cwd-derived palace, prints them to stdout (Claude Code injects stdout as session context), and atomically marks them read.
Design
A message is a drawer in the recipient's palace carrying a namespaced tag envelope. No new schema, no new database — just convention:
| Tag | Example | Meaning |
|---|---|---|
msg:v1 |
(literal) | Marker tag for the v1 envelope. |
msg:from=<palace> |
msg:from=trusty-tools |
Sender palace id. |
msg:to=<palace> |
msg:to=claude-mpm |
Recipient palace id (audit). |
msg:purpose=<text> |
msg:purpose=task |
Free-text purpose / category. |
msg:sent_at=<rfc3339> |
msg:sent_at=2026-05-25T12:34:56+00:00 |
UTC send timestamp. |
msg:read=<bool> |
msg:read=false |
Receiver-flipped read flag. |
Addressing
Sender and recipient palaces are addressed by repo slug. The slug is derived from the working directory by:
- Take the basename of
git rev-parse --show-toplevel(or cwd, when not in a git checkout). - Strip a trailing
.gitsuffix if present. - Lowercase.
- Replace every run of whitespace or
_with a single-. - Strip every character outside
[a-z0-9-]. - Collapse consecutive
-and trim leading/trailing-.
Examples (all resolve to trusty-tools):
/Users/bob/Projects/trusty-tools,
/Users/bob/Projects/Trusty_Tools,
/Users/bob/Projects/trusty tools,
/Users/bob/Projects/.trusty-tools.git.
No central registry; sender and receiver agree on the slug out of band.
Delivery
The receiver's trusty-memory setup installs trusty-memory inbox-check as
a SessionStart hook in every Claude Code settings file it finds (alongside
the existing UserPromptSubmit prompt-context hook). On every new Claude
Code session, the hook:
- Resolves the receiver palace slug from cwd.
- Fetches unread messages from
GET /api/v1/messages?palace=<slug>&unread_only=true. - Prints each as a Markdown block to stdout — Claude Code injects stdout as session context.
- Atomically marks each delivered message read via
POST /api/v1/messages/mark_read.
The mark-read step uses an in-memory compare-and-swap on the palace's
drawer table so two concurrent sessions opening at once cannot
double-deliver: exactly one observes read=false and flips the flag, the
other returns false and emits nothing.
Every failure path in inbox-check degrades to exit 0 with empty stdout
so a missing or slow daemon never blocks Claude Code session start.
Migration from claude-mpm /mpm-message
This primitive replaces the Python /mpm-message skill in claude-mpm
(which wrote to ~/.claude-mpm/messaging.db via a process-local SQLite
file). The companion ticket in claude-mpm is #557; data migration is
out of scope here.
Palace as Project
(Issue #88) Palace names are anchored to the project they belong to.
One palace per project
When you create a new palace, trusty-memory walks upward from the current
working directory looking for project markers (.git, Cargo.toml,
pyproject.toml, package.json, go.mod) and derives a canonical slug
from the project root's directory name:
/Users/bob/Projects/trusty-tools → trusty-tools
/Users/bob/Projects/My_App! → my-app
/Users/bob/Projects/my project → my-project
New palaces must be named with the derived slug:
# Inside /Users/bob/Projects/trusty-tools — this succeeds
)
# Any other name fails with a descriptive error:
# "palace name 'notes' does not match the project slug 'trusty-tools'.
# Either use 'trusty-tools' or use 'personal' for non-project memories."
)
The personal palace
When operating outside any project directory (no .git / Cargo.toml /
etc. found anywhere above CWD), only the special palace name personal is
allowed. This is the "no project, no problem" escape hatch for global notes,
one-off sessions, and personal task lists.
# From any directory without a project root:
) # always succeeds
) # fails — no project root detected
Existing palaces are grandfathered
The enforcement applies only to new palace creation. Every palace created before this feature was added continues to work without any migration. Existing palaces remain read-write and are never auto-renamed.
doctor --fix-palaces
The doctor --fix-palaces subcommand audits existing palaces and reports
which ones are orphaned (name does not correspond to a detectable project
directory on disk):
# Dry-run audit (no filesystem changes):
# Include rename suggestions for orphaned palaces (still read-only):
Output example:
✅ trusty-tools — project palace ok
⚠️ old-project — orphaned (no matching project directory found on disk)
→ rename suggested: old-project → personal
❌ broken-palace — empty (no palace.json; directory may be a leftover)
· palace audit: 1 ok, 1 orphaned, 1 empty.
The --fix flag prints rename suggestions but does not mutate the
filesystem. Actual renaming (merging orphaned data into personal) is
planned for a future release. The doctor audit is purely advisory for
existing palaces.
Web UI
When running in HTTP mode, the embedded Svelte admin dashboard is available at:
http://127.0.0.1:<port>/
The dashboard provides:
- Real-time palace overview (drawer counts, vector counts, KG triple counts)
- Live event stream (palace created, drawer added/deleted, dream completed)
- Manual dream (consolidation) trigger
- Palace-scoped memory browsing
Configuration
Environment variables
| Variable | Default | Description |
|---|---|---|
RUST_LOG |
warn |
Tracing filter. E.g. RUST_LOG=info or RUST_LOG=trusty_memory=debug. |
OPENROUTER_API_KEY |
— | Enables chat completions via OpenRouter (/api/v1/chat). |
TRUSTY_DATA_DIR_OVERRIDE |
— | Override the data directory (intended for tests). |
Config file
~/.trusty-memory/config.toml (created on first run if absent):
[]
= ""
= "anthropic/claude-3.5-sonnet"
[]
= false
= "http://127.0.0.1:11434"
= "llama3"
Data directory
Memories and vector indexes persist under the OS-standard data directory:
- macOS:
~/Library/Application Support/trusty-memory/<palace-id>/ - Linux:
~/.local/share/trusty-memory/<palace-id>/
Each palace directory contains:
drawers.db— SQLite metadata storevectors.usearch— usearch vector indexkg.db— knowledge-graph triples (SQLite)chat_sessions.db— chat session history
Architecture
trusty-memory (this crate) trusty-memory-core
axum HTTP/SSE server ──────► PalaceRegistry
Unix domain socket ──────► usearch vector index
embedded Svelte UI SQLite metadata + KG
12 MCP tools fastembed (AllMiniLML6V2Q)
trusty-memory-mcp-bridge (separate binary, PR #149)
Claude Code stdio ◄──pipe──► trusty-memory UDS
trusty-memory-core owns the storage engine: usearch for approximate
nearest-neighbor search, SQLite for metadata and knowledge-graph triples, and
fastembed for 384-dim text embeddings. The MCP server (trusty-memory) is a
thin protocol layer on top.
The embedded Svelte UI is compiled at build time and served via rust-embed —
no separate web server or Node.js installation is needed at runtime.
Feature Flags
| Flag | Default | Description |
|---|---|---|
axum-server |
enabled | Compiles the HTTP server, SSE endpoint, and axum-based REST API. Disable with default-features = false when embedding only the in-process MCP tools (e.g. from open-mpm). |
# Full daemon build — no change needed (axum-server is on by default)
= { = true }
# rlib consumer — omit the HTTP stack
= { = true, = false }
Migration
From kuzu-memory (MCP config, issue #278)
If you're switching from the legacy kuzu-memory Python MCP server, rewrite
every Claude mcpServers config entry in one command:
# Dry-run: see what would change
# Apply: rewrite all Claude settings files atomically
Knowledge-graph hygiene (issue #278)
Auto-KG extraction skips drawers tagged cross-project-qa, test, or
fixture so synthetic content never pollutes the graph. Drawers deleted via
memory_forget cascade-delete their derived triples automatically.
The REST endpoint DELETE /api/v1/palaces/{id}/kg/triples/{triple_id} lets
you surgically remove a single active triple; triple_id is the base64url
encoding of subject + "\0" + predicate.
From kuzu-memory data (issue #277)
Import entities and relations from a kuzu-memory store.redb into a
trusty-memory palace:
# Dry-run: see what would be imported
# Apply the import (idempotent — safe to re-run)
# Cap at 100 entities
Each kuzu-memory entity becomes one drawer; each relation becomes one KG triple. Re-running is idempotent: entity IDs are SHA-256-derived UUIDs so duplicate imports produce the same drawer ID and are silently skipped.
Development
# Build and run (background daemon, dynamic port)
# Run inline on a specific address (foreground, useful for debuggers)
# Tests
# Check only (faster)
License
Licensed under the MIT License.