hypermnesia
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@hypermnesiafind memories related to API design decisions"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
hypermnesia
A semantic memory store for AI agents, exposed over MCP.
Agents call tools to save and recall persistent memories across sessions instead of
relying on per-session context or hand-edited CLAUDE.md files.
Recall is semantic, not key-based — agents search by meaning ("what do I know relevant to this task?"), not by knowing an exact key.
Local-only, CPU-friendly embeddings. Default is
fastembed(ONNX, no PyTorch) withBAAI/bge-small-en-v1.5. The embedder is pluggable via config.Shared & multi-tenant. Memories live in scopes; bearer tokens map to a principal and the scopes it may read/write. Every query is scope-filtered, so tenants are isolated.
Postgres + pgvector for storage, vectors, and metadata in one place.
MCP tools
Tool | Purpose |
| Hybrid (semantic + keyword) recall; ranks by relevance + recency + importance |
| Store; updates a near-duplicate instead of inserting |
| Edit a known memory in place (only given fields change) |
| Fetch one by id |
| Cheap index: descriptions + metadata, no bodies ( |
| Delete by id (hard) |
| Archive stale, low-importance memories; dry-run unless |
| Un-archive a forgotten memory (inverse of |
| Recall health: store size per scope + search volume, empty-rate, score/latency metrics |
| Generates setup + |
Search results carry both a raw similarity (0-1 cosine) and a blended score that
adds recency decay (half-life HM_RECENCY_HALF_LIFE_DAYS) and normalised importance;
tune the mix via HM_SCORE_WEIGHT_*.
Two gates keep weak matches out of the agent's context, and they do different jobs:
HM_SEARCH_MIN_SIMILARITY(default0.4) is an absolute floor — it answers "is anything here relevant at all?". It is tied to the model's cosine scale (bge-small-en-v1.5: unrelated text ~0.30-0.45, relevant ~0.55+), so re-tune it if you switch embedding models.HM_SEARCH_RELATIVE_CUTOFF(default0.15) is a relative one — it drops hits more than that far below the best hit of the same search, answering "is this hit worth reading next to the best one?". An absolute floor cannot do this job: on a measured 11-query eval, the 0.4 floor admitted every irrelevant memory (77/77), and the tightest floor that trimmed a comparable amount had already started discarding relevant hits. The relative gate cut irrelevant hits to 15/77 while keeping all 11 relevant ones. End to end on a 10-memory store it took recall from 8.0 to 1.5 results per query with no change in top-1 accuracy.
Both are overridable per search (min_similarity, relative_cutoff), and 0.0
disables either. Note that min_similarity: 0.0 alone no longer means "return
everything" — pass relative_cutoff: 0.0 too for a broad survey. The relative gate
deliberately does nothing when results are flat (an off-topic query with no standout),
so it narrows a good answer's neighbourhood rather than inventing one, and lexical
hits bypass it exactly as they bypass the floor.
Search is hybrid: a vector (semantic) query runs alongside a Postgres full-text
(keyword) query, so exact tokens the embedding can't capture — error codes, flag
names, file paths, names — still surface. A pure keyword hit bypasses the similarity
floor on purpose. Toggle with HM_HYBRID_SEARCH.
The two signals are combined by keeping cosine similarity as the relevance and
adding a bounded bonus for a keyword match (HM_HYBRID_LEXICAL_BOOST, default
0.10, decaying with lexical rank via HM_HYBRID_RANK_DECAY_K). It is deliberately
not reciprocal-rank fusion: RRF keeps only ranks, which handed every keyword match a
flat relevance jump larger than the entire spread of similarity scores. Because
Postgres' websearch_to_tsquery requires all query terms to match, which memories
match a conversational question is close to arbitrary — so that jump promoted
memories that merely shared the question's function words over the one that answered
it. Capping the bonus keeps exact-token recall without letting wording overrule
meaning.
Forgetting. Stores grow forever and old clutter dilutes recall, so memory_forget
archives memories that are both stale (not recalled in HM_FORGET_AFTER_DAYS, default
180) and unimportant (importance <= HM_FORGET_IMPORTANCE_FLOOR, default 1.0).
Recall bumps last_accessed_at and a higher importance both keep a memory alive, so
anything you use or pin survives. It's a soft delete — archived rows drop out of
search/get/list but are kept, not destroyed — and a dry run by default (pass
apply: true to act). memory_delete remains the hard, irreversible removal.
Review what's been archived with memory_list(include_archived=true) and bring one
back with memory_restore(memory_id) — restoring also refreshes its last-access time
so the next sweep won't immediately re-forget it. In both dry-run and apply results,
matched is the true total the criteria hit (exactly what apply archives) while
memories lists at most limit (default 100) of them, stalest first, with
truncated set when the list was cut — a dry run can never under-report an apply.
Set HM_FORGET_SWEEP_HOURS (default 0 = off) to have the server run the sweep itself
periodically over every scope, using the two thresholds above; archived descriptions
are logged each pass.
Observability. Every memory_search is logged (query, candidate/hit counts, top
scores, latency) to a search_log table, and memory_stats aggregates it: search
volume, empty-result rate, average hits and top score, latency percentiles, and the
most recent queries that returned nothing — the recall misses worth investigating.
Use it to tune HM_SEARCH_MIN_SIMILARITY and the score weights from evidence instead
of feel. Logged queries land in the DB; disable with HM_SEARCH_LOG_ENABLED=false.
description is a one-line summary used for ranking and de-duplication — treat it like
the one-liners in Claude Code's MEMORY.md index. It is also what makes memory_list
an index worth calling: it returns descriptions and metadata but not bodies, because
content runs ~11x longer than its description and would otherwise be ~74% of the
payload (a default limit=50 listing costs ~4k tokens instead of ~16k). Scan the
descriptions, then memory_get the one you need; full: true returns bodies when you
really do want them all. De-duplication is two-gated: the
combined embedding must clear HM_DEDUPE_THRESHOLD and the contents themselves must
agree (HM_DEDUPE_CONTENT_THRESHOLD, default 0.9) — so two different facts that merely
share a description shape insert side by side instead of one silently clobbering the
other. Failing the gate errs toward a duplicate (recoverable) over a lost fact (not).
Related MCP server: mcp-memory-server
Quick start (Docker)
The stack is a shared base (docker-compose.yml) plus one of two overlays:
# Release — pull the published image from GHCR (defaults to the `latest` tag):
docker compose -f docker-compose.yml -f docker-compose.release.yml up -d
# pin a version with HM_TAG, e.g. HM_TAG=v0.1.0 docker compose ... up -d
# Dev — build the image from local source:
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --buildThe MCP server listens on http://localhost:8765/mcp (streamable HTTP). Point an MCP
client at it with header Authorization: Bearer <your-token>.
Tip:
export COMPOSE_FILE=docker-compose.yml:docker-compose.dev.ymlto drop the repeated-fflags during development.
Use it with Claude
Connecting the server is two steps: register it, then tell Claude when to call it.
Claude Code
Register the running server (HTTP transport, with the bearer token):
claude mcp add --transport http hypermnesia http://localhost:8765/mcp \
--header "Authorization: Bearer dev-token" \
--scope user # available in every project; use --scope local/project to narrowVerify with claude mcp list (should show connected); inside a session, /mcp lists
the tools. The stack must be running and reachable on the same machine.
Exposing tools isn't enough — Claude won't reach for them unless told when to. Rather than copying a block from this README (which drifts the moment a default or signature changes), ask the server for it:
Call
memory_usage_guideand write theclaude_mdsection into myCLAUDE.md.
memory_usage_guide generates the guidance from the server's live settings and
registered tool list, so it always states how your server actually behaves — the real
similarity cutoff, forget thresholds, dedup gates, and every tool currently exposed. It
returns four things:
Key | What it is |
| A ready-to-write |
| Client registration snippets using this server's real address (never real tokens) |
| What your connection resolved to: principal, project scope, readable scopes, whether roots were detected |
| Setup problems the server can actually observe — above all, project isolation not being active |
Pass section (claude_md | setup | session) to get just one; warnings always
rides along. Re-run it after upgrading the server to refresh a stale CLAUDE.md.
Check warnings on first setup. If your client doesn't deliver workspace roots and
you haven't set the X-Hypermnesia-Project header, every project shares the default
scope — the guide says so explicitly and hands you the .mcp.json that fixes it.
You can put the generated section in a single global ~/.claude/CLAUDE.md — memories
are partitioned per project (see below), so projects never trample each other.
Claude Desktop
claude_desktop_config.json is stdio-oriented, so bridge to the HTTP server with
mcp-remote:
{
"mcpServers": {
"hypermnesia": {
"command": "npx",
"args": ["mcp-remote", "http://localhost:8765/mcp",
"--header", "Authorization: Bearer dev-token"]
}
}
}Claude API / Agent SDK
Pass the server via the MCP connector (the mcp_servers field), pointing at
http://localhost:8765/mcp with the Authorization: Bearer <token> header.
Project scoping (no trampling)
Memories live in scopes, and the server derives each session's scope so a single global config can't mix projects together:
X-Hypermnesia-Projectheader — a stable key (e.g. a repo slug), mapped toproject:<slug>. Set it per project in a project-scoped.mcp.json. This is the mechanism that works on every client and transport, and it also lets a team or several machines share one project's memory deliberately.Workspace root — MCP clients (Claude Code included) advertise the project directory as a root; the server maps it to
project:<dirname>-<hash>with no per-project setup. This is best effort: the MCP roots capability is deprecated (SEP-2577), and a server cannot request roots at all over a transport with no back-channel for server-initiated requests. Where it works it is the most convenient option; where it doesn't, the server falls back to step 3.default— fallback when neither is available. Every project sharing this server lands in one pool here, so the server logs a warning (once per process) the first time a request falls back. If you see that warning, set the header — it means projects are no longer isolated.
memory_search/memory_list return the current project plus any granted shared
scopes (like shared) — never another project's. memory_save defaults to the project
scope; pass scope: "shared" to cross boundaries deliberately. Because the scope is
derived server-side from the session, the model can't accidentally write to the wrong
project by mistyping a name.
After changing the server's tool signatures, reconnect the MCP client (it caches the tool list on connect) to pick them up.
Local dev
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
# Postgres with pgvector (or: docker compose up db)
hypermnesia # starts the MCP server
pytest -m "not e2e" # unit tests only (no DB/network needed)Tests
Unit (
test_auth,test_embeddings,test_scoping,test_fuse,test_roots,test_dedupe_gate,test_embed_offload,test_sweep,test_admin,test_guide): pure logic, no infra —pytest -m "not e2e".End-to-end (
tests/test_e2e.py): black-box CRUD + semantic recall + auth/scope isolation, driven through the MCP tools against a running server. They auto-skip if no server is reachable.Admin CLI e2e (
tests/test_cli_e2e.py): export/import restore + reindex model swap. Need direct DB access, so they only run whereHM_TEST_DATABASE_URLis set (the compose test container sets it);HM_TEST_ALT_MODELgates the dimension-change reindex test.
Run the whole suite against the Docker stack (dev overlay builds from source):
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build
docker compose -f docker-compose.yml -f docker-compose.dev.yml \
--profile test run --rm tests # waits for health, runs unit + e2ePoint the e2e tests elsewhere with HM_TEST_URL, HM_TEST_TOKEN, HM_TEST_SCOPE.
Configuration
All settings are env vars with the HM_ prefix (see .env.example). Key ones:
Var | Default | Notes |
|
|
|
|
| any model the provider supports |
|
| absolute cosine floor; model-specific, re-tune on a model swap |
|
| drop hits this far below the best hit; |
|
| also run a Postgres full-text query alongside the vector one |
|
| most a keyword match adds to relevance; decays with lexical rank |
|
| similarity / recency / importance blend; recency and importance are tie-breakers, so keep them small next to how much similarity actually varies (~0.18 for bge-small) |
|
| cosine sim above which |
|
| second gate: contents must agree too, or it inserts |
|
| staleness threshold for |
|
| only memories at or below this are forgettable |
|
| server-side periodic sweep; |
|
| log searches to |
|
|
|
|
|
|
|
| when false, all callers are |
Admin CLI (backup & model migration)
The hypermnesia binary doubles as an admin CLI. These subcommands run on the server
host with direct DB access (they read HM_DATABASE_URL etc.) and bypass MCP
auth/scopes on purpose — they are the backup and migration story, not agent tools:
hypermnesia export --out dump.jsonl # all memories (archived too), as JSONL
hypermnesia export --scope shared # limit to scopes; repeatable; - = stdout
hypermnesia import dump.jsonl # restore a dump; existing ids are skipped
hypermnesia reindex # re-embed everything with HM_EMBEDDING_*Dumps exclude embeddings, so they survive an embedding-model change — import
re-embeds with the configured model, preserving ids and timestamps and never
overwriting an existing row (re-running a restore is idempotent). In a Docker setup:
docker compose exec server hypermnesia export --out - > dump.jsonl. Take dumps
periodically — the Postgres volume is otherwise the only copy of every memory.
Swapping the embedding model
Set HM_EMBEDDING_PROVIDER / HM_EMBEDDING_MODEL. The vector dimension is auto-detected
and pinned in the store on first run. Starting the server with a different model
(or dimension) is refused with a clear error, because existing vectors would no longer
be comparable. The migration path is reindex:
HM_EMBEDDING_MODEL=new-model hypermnesia reindex # re-embeds all rows, swaps the
# vector column + HNSW index, and
# updates the pin — one transaction
# then restart the server with the new HM_EMBEDDING_* settingsUntil the restart, the running server still embeds with the old model and will error —
run reindex during a quiet moment. Also re-tune HM_SEARCH_MIN_SIMILARITY after a
model swap (cosine ranges differ per model).
To add a new provider, implement the Embedder protocol and @register("name") it in
src/hypermnesia/embeddings/providers.py.
Status
v1. Implemented: MCP tools, pgvector storage, scope-based auth/isolation, hybrid
(semantic + keyword) recall with absolute and relative precision gates, two-gated
de-duplication, forgetting (manual + scheduled sweep) with restore, search logging and
memory_stats, an admin CLI for backup and model migration, and a unit + e2e suite.
Roadmap: Redis hot-cache, per-principal rate limits, search_log retention (the table
currently grows unbounded), caching workspace roots per session instead of re-asking
per tool call, web UI.
This server cannot be deployed
Maintenance
Related MCP Connectors
Shared, governed long-term memory for AI agents across tools and sessions via MCP and REST.
Persistent memory and knowledge management for AI agents with semantic search and 50+ tools.
Memory system for AI agents with semantic search. Store and recall memories with ease.
Persistent memory for AI agents across Claude, ChatGPT and any MCP client.
Related MCP Servers
- AlicenseAqualityDmaintenanceProvides persistent memory with semantic search for MCP-based AI agents, enabling them to store and recall information across sessions using vector embeddings.41MIT
- AlicenseNot gradedqualityCmaintenanceA persistent semantic memory layer for AI coding agents, enabling storage and retrieval of text with vector embeddings via MCP tools.MIT
- FlicenseNot gradedqualityBmaintenanceA persistent memory server for AI agents using MCP protocol, enabling semantic storage and retrieval of dialogues, documents, and agent states.-
- AlicenseNot gradedqualityBmaintenanceA shared memory MCP server for AI agents that provides persistent, semantic memory across sessions and tools, enabling long-term recall and context sharing.3 npm12MIT