Memry
Memry is the long-term memory system for AI agents. It is open source and you host it yourself - memry.tech
pip install memry
export OPENAI_API_KEY=sk-... MEMRY_DECISION_PROVIDER=jev MEMRY_DECISION_API_KEY=...
memry mcp # your agent now has long-term memory
Memry gives any MCP-capable agent - Claude Code, Claude Desktop, Cursor, Windsurf, Codex - durable long-term memory. It distills conversations into discrete facts, reconciles each new fact against what it already knows, and serves the result back as token-budgeted context. All knowledge state is a single SQLite file on your machine: no external vector database or queue service. To run it, you need two models: a text model (OpenAI, Anthropic or Ollama) and a decision model, TypeSafe Jev.
A scratchpad or a MEMORY.md file is text your agent rereads at the start of every
session. You keep it short by hand, and a line that is no longer true is still in the file
until you delete it. Memry is a database that holds every fact your agents have saved and
returns the few that fit the question. Every MCP client you connect reads and writes the
same memory.
Why Memry
It runs anywhere, with two models. Knowledge storage is one SQLite file with no
services around it. A text model extracts the facts from what you save. A decision model,
Jev, returns a calibrated probability that two names belong to one thing, and Memry merges
the duplicates on its own. The server doesn't start without both models. If you set
MEMRY_DECISION_PROVIDER=llm, Memry sends the decision questions to the text model,
merges entities only by fixed rules, and you confirm the other merges yourself.
It remembers the way you would want it to. New facts are reconciled against existing
ones: duplicates are skipped, refinements update in place, and contradictions supersede
the old memory instead of deleting it. Memories are bi-temporal (valid_from /
invalid_at / superseded_by), so "moved to Amsterdam" does not erase "lived in
Berlin" - it dates it. Nothing is forgotten for being old: a memory leaves use only when
you delete it or a later one replaces it, and either can be undone.
Nothing is a black box. Raw episodes are stored immutably before anything is derived from them, every memory links back to its source episodes, every mutation is an inspectable event, and every search hit carries its score signals (BM25, vector, recency, importance). When a memory looks wrong, you can trace where it came from, or re-run a better extraction pipeline over the original episodes.
Entities are disambiguated by evidence. Mentions become first-class entities. An exact first-and-last name with overlapping context merges automatically; a shared short name or full name without supporting context stays separate with a reviewable merge proposal. Prior merges are followed, so an older proposal cannot leave a broken merge target.
Topics organize themselves. Extraction tags each memory at the level a later
conversation would ask about (liver health, weekly gym, 2026 taxes), reusing the
vocabulary already in your store rather than coining a synonym every session. Mechanical
variants such as food/foods merge without review, and two tags that name one subject
are compared and merged as two names of one thing are. Search and list by topic or by date
window.
It stays simple as one shared service. SQLite is the sole production store. One Memry
server can serve many agents, devices, and tenant namespaces without an external database.
For larger stores, the optional memry[ann] extra adds a rebuildable usearch HNSW
candidate index. Multiple server replicas writing one store are not currently supported.
Multi-user, with real OAuth. Beyond static config tenants, one server can host
runtime-managed accounts, created with memry account add. The first account is the
bootstrap administrator and keeps only the existing default memory space; every later
account gets one private memory space. Set MEMRY_PUBLIC_URL and Memry becomes an OAuth 2.1
authorization server for those accounts (dynamic client registration, PKCE, refresh,
revocation, discovery
at the domain root), so any OAuth-capable MCP client - Claude, Cursor, VS Code - can sign in
and get a token scoped to that account. No IdP required; Memry verifies against its own
accounts. Off by default: the single-user path stays keyless. See
docs/self-hosting.md.
You can measure it. A built-in eval harness scores retrieval (recall@k, MRR, latency percentiles) deterministically and offline. An optional Mem0 adapter lets comparison or import tooling read and exercise Mem0 under the same interface; it cannot be selected as Memry's runtime store.
Quickstart
As an MCP server (any agent)
Memry speaks MCP two ways: stdio for agents on the same machine (zero config, no port, no auth) and streamable HTTP for a shared server that several agents and devices talk to.
Local, stdio - the fastest start:
# Claude Code
claude mcp add memry -e OPENAI_API_KEY=sk-... -e MEMRY_DECISION_PROVIDER=jev \
-e MEMRY_DECISION_API_KEY=... -- memry mcp
// Claude Desktop / Cursor / Windsurf config
{
"mcpServers": {
"memry": {
"command": "memry",
"args": ["mcp"],
"env": {
"OPENAI_API_KEY": "sk-...", // text model (or ANTHROPIC_API_KEY)
"MEMRY_DECISION_PROVIDER": "jev", // decision model
"MEMRY_DECISION_API_KEY": "..." // TypeSafe key
}
}
}
}
The Anthropic SDK is an optional extra: pip install "memry[anthropic]".
Without it an ANTHROPIC_API_KEY is ignored with a warning, and with no other text
model the server does not start. With OPENAI_API_KEY you don't need the extra.
Remote, streamable HTTP - point any MCP client at a self-hosted server (see below) and share one memory across every machine:
# Claude Code
claude mcp add --transport http memry https://memory.example.com/mcp \
--header "Authorization: Bearer <MEMRY_API_KEY>"
// Cursor / Windsurf / anything that takes a config file
{
"mcpServers": {
"memry": {
"type": "http",
"url": "https://memory.example.com/mcp",
"headers": { "Authorization": "Bearer <MEMRY_API_KEY>" }
}
}
}
claude.ai (web, desktop, mobile) - add Memry as a custom connector under Settings → Connectors → Add custom connector. The dialog has no header field, so embed the key in the URL instead:
https://memory.example.com/mcp/<MEMRY_API_KEY>
Full walkthrough with screenshots of the flow, security notes, and troubleshooting: docs/connect-claude-ai.md.
ChatGPT - add Memry as a connector and sign in with OAuth (no key to
paste). Set MEMRY_PUBLIC_URL first, then give ChatGPT the MCP URL
https://memory.example.com/mcp; the bare origin works too. Walkthrough and
troubleshooting: docs/connect-chatgpt.md.
The server exposes save_memories, search_memories, get_memory_context,
list_memories, list_categories, update_memory, delete_memory,
memory_history, and memory_stats. Recall takes two filters, applied before
ranking: when (a day, month, year or range: "Was habe ich am 01. April 2025
gemacht?" is when="2025-04-01") and about (names of people, projects, things
or tags); a phrase in double quotes in the query must appear exactly. Agents are
instructed to recall context at the start of a task and to send save_memories what was said in words close
to the original, one statement per line, with the speaker named when it is
someone other than the user ("Ada: I got the job"). Memry extracts the facts
and keeps the saved text as the source turns it shows with each memory in later
searches. If an agent sends a summary, those searches show the summary, and the
details it dropped are lost. Related statements go in one call. If they arrive
in separate calls, the client can repeat a semantic context label and
run_id; up to three optional tags are treated as classification hints. For
something said on another day, such as an imported chat, the client passes
said_at (YYYY-MM-DD): the memories are dated that day, and "yesterday" or
"next month" in the text counts from it. When a fact changes or the user
corrects it, the agent saves the new statement, and Memry keeps the old value
as dated history or retires it when it was wrong. With the default
infer=true, the exact text is acknowledged immediately and remains
searchable. The managed worker waits for two minutes of quiet, then distills
each related group when an LLM is configured.
As a Python library
from memry import MemoryStore
store = MemoryStore()
# write: extraction + reconciliation (or infer=False to store verbatim)
store.add(
[{"role": "user", "content": "I'm Ada, a data engineer in Berlin. I prefer uv over pip."}],
user_id="ada",
)
# read: hybrid search with explainable scores
for hit in store.search("what tooling does the user prefer?", user_id="ada"):
print(hit.score, hit.memory.content, hit.signals)
# or a ready-to-inject, token-budgeted context block
ctx = store.reconstruct_context("help me set up a new project", user_id="ada", token_budget=1200)
print(ctx.text)
As a self-hosted server (REST + dashboard + MCP)
export OPENAI_API_KEY=sk-... MEMRY_DECISION_PROVIDER=jev MEMRY_DECISION_API_KEY=...
memry serve --host 0.0.0.0 --port 8787
# dashboard: http://localhost:8787/
# REST API: http://localhost:8787/api/v1/...
# MCP (HTTP): http://localhost:8787/mcp
The dashboard shows your memories with inline editing, filtered search, lossless JSON
backup/restore, a unified Upkeep area, and a galaxy map aggregated over every active
memory independently of the paginated detail list. The map groups by entity: it shows
hubs, with the parts of a project or product as moons on it, and tags once their type is
turned on; concept, other and tag are hidden by default and the type menu controls what is
shown. Upkeep > Entities lists people, things and tags in one list filtered by type, and
the memory list's About filter picks any of them. Heavily-used groups gravitate to the gold
core, the working set orbits in the teal belt, and one-off groups drift at the violet rim.
Orbit-marker shapes distinguish semantic, procedural, episodic, and working memories. Idle
link and orbit rendering is bounded for large stores (above 400 groups, orbit markers stay
on the core and on whatever you hover or select); selecting a planet reveals its complete
visible neighborhood and filters the detail list through the server. Selecting an entity
also surfaces its summary, aliases, and rename control, with explicit controls to merge a
duplicate or remove a mistaken entity without deleting memories. When a mistaken entity
occurs in multiple memories, its name is retained as a tag.
Opening Upkeep temporarily unloads the map and restores it on close to avoid holding both views in memory.
With Docker: docker compose up -d --build (see docker-compose.yml).
Or on a fresh Ubuntu/Debian VPS, one command installs Docker, Memry, and Caddy with automatic HTTPS (full guide):
curl -fsSL https://raw.githubusercontent.com/cosmin-novac/memry/main/deploy/install.sh \
| MEMRY_DOMAIN=memory.example.com bash
Set MEMRY_API_KEY to require Authorization: Bearer <key> on the API. Without a
key (or accounts) the server only binds loopback; MEMRY_ALLOW_OPEN=1 overrides that
when a reverse proxy protects the port. More in
docs/self-hosting.md. To plug a hosted Memry into
claude.ai as a custom connector, see
docs/connect-claude-ai.md.
Multi-user accounts
One server can host multiple accounts. The first account is the bootstrap administrator and
uses only the existing default memory space. Every later account uses only its own
name::default space. The role does not expose other accounts' memories. Manage accounts
on the server with the CLI:
memry account add alice --password s3cret # creates it, prints an API key (shown once)
memry account list
memry account issue-key alice --label laptop # another key for the same account
memry account disable alice # its keys + sessions stop working immediately
An account connects with its API key (Authorization: Bearer <key>, or
https://<host>/mcp/<key>), or - once you set
MEMRY_PUBLIC_URL=https://memory.example.com - by signing in through OAuth
from any client that supports it (Claude, ChatGPT, Cursor, VS Code). On the
dashboard, accounts sign in at /login with their name and password. Full
walkthrough: docs/self-hosting.md.
From the CLI
memry add "I moved to Amsterdam and joined ASML" -u ada
memry search "where does ada work" -u ada
memry context "plan a commute" -u ada
memry history <memory_id> # full audit trail
memry eval --dataset evals/datasets/synthetic_v1.jsonl
How it works
flowchart LR
A[conversation] --> E["episode + active pending memory"]
E --> ACK[durable acknowledgement]
E --> X[background extraction]
X --> R{reconcile}
R -->|new| ADD[add]
R -->|refines| UPD[update in place]
R -->|contradicts| SUP[supersede old]
R -->|duplicate| SKIP[skip]
ADD --> M[(memories)]
UPD --> M
SUP --> M
Q[agent query] --> H[hybrid retrieval]
E --> H
M --> H
H --> C[token-budgeted context]
- Durable acknowledgement. The MCP fast-save path commits the exact input as an immutable episode and active, searchable pending memory before replying. That SQLite row is also the recovery marker, so no external queue is required.
- Managed enrichment. One in-process worker drains small database batches. Each payload is extracted separately so user scopes, provenance, and retries cannot mix. Provider failure leaves the raw memory active and schedules a bounded-backoff retry; restart recovery reads the same pending rows. Without an LLM key, they stay verbatim.
- Reconciliation. Each extracted fact is compared to similar active memories: duplicates are skipped, refinements rewrite in place, contradictions invalidate the old memory and link it to its successor. The pending raw memory is superseded only after enrichment succeeds.
- Retrieval. BM25 and cosine similarity are fused with reciprocal-rank fusion, then boosted by recency and importance. A question naming a known entity also takes the memories of the entities linked to it (the linked search). Pending raw memories are searchable immediately.
- No forgetting by age. Recency lifts newer memories in ranking, but age never
takes a memory out of use. Tag filters (
memry search -c diet) narrow any query.
Configuration
Before you start a server, set a text model and a decision model; the server doesn't start
without them (docs/self-hosting.md).
Every other setting has a default.
Override via env vars, ~/.memry/config.json, or Config(...):
| Env var | Default | Notes |
|---|---|---|
MEMRY_DB_PATH |
~/.memry/memry.db |
knowledge SQLite file; back it up with auth.db when accounts are enabled |
MEMRY_AUTH_DB_PATH |
next to MEMRY_DB_PATH as auth.db |
accounts and OAuth; include it in every complete server backup |
MEMRY_DEFAULT_USER |
default |
user scope when the agent doesn't pass one |
MEMRY_LLM_PROVIDER |
auto | anthropic | openai | ollama | none - auto-detected from OPENAI_API_KEY / ANTHROPIC_API_KEY. With both keys set, OpenAI wins so the LLM and the embeddings stay on one provider (Anthropic has no embeddings API); pin this to override |
MEMRY_LLM_MODEL |
per provider | claude-haiku-4-5 / gpt-6-luna / llama3.1; Haiku is the Anthropic default for lower save cost and enrichment latency |
MEMRY_EMBEDDING_PROVIDER |
auto | openai | ollama | voyage | hash | none |
MEMRY_API_KEY |
- | bearer token for the REST/MCP HTTP server |
MEMRY_DECISION_PROVIDER |
required | jev, or on purpose llm: Memry then sends the decision questions to the text model, merges entities only by fixed rules, and you confirm the other merges yourself |
MEMRY_DECISION_API_KEY |
required for jev |
TypeSafe API key |
Anthropic extraction requires the optional SDK: pip install "memry[anthropic]".
Memry sends its decision questions to TypeSafe Jev
Memry's decision questions each have a fixed set of answers: are these two entities the
same one, how long does this fact stay worth remembering, do these memories contain the
same fact, which result is the best answer to the question. With
MEMRY_DECISION_PROVIDER=jev, Memry sends them to TypeSafe Jev, a
hosted System One model that returns a calibrated probability for each possible answer.
export MEMRY_DECISION_PROVIDER=jev
export MEMRY_DECISION_API_KEY=... # TypeSafe API key
With Jev, Memry merges duplicate entities on its own above 0.70 confidence, a threshold
measured on the labelled identity set in evals/. Jev also judges the first 20 results
of a search, and for a question whose answer is a set (a list, a total, a comparison) one
more batch of memories. Scoring how long each memory stays relevant is off unless
MEMRY_DURABILITY=1. You still need a text model for extraction. If you set
MEMRY_DECISION_PROVIDER=llm, Memry sends these questions to the text model, merges
entities only by fixed rules, and you confirm the other merges yourself. The
measurements and the remaining settings are in docs/self-hosting.md.
Evaluation
memry eval --dataset evals/datasets/synthetic_v1.jsonl -k 5
The harness ingests each case through the full write path, then scores retrieval
(recall@k, MRR, latency p50/p95). It is deterministic and offline, so it runs in CI.
Datasets in its JSONL schema compare providers, configs and the optional Mem0 comparison
adapter under identical conditions. evals/external_benchmarks.py runs LoCoMo and
LongMemEval as published (evidence recall at 5, 10 and 20 and MRR, without a model).
Project layout
src/memry/
models.py # Episode / Memory / MemoryEvent (bi-temporal, provenance)
config.py # env + file config, provider auto-detection
store.py # MemoryStore - the public API
enrichment.py # managed pending-memory worker and restart recovery
retrieval.py # hybrid search: RRF + recency + importance
backends/ # storage contract + the production SQLite engine
intelligence/ # extraction, reconciliation, entities, context building
providers/ # LLMs (Anthropic/OpenAI/Ollama) & embeddings (+hash fallback)
mcp_server.py # MCP tools (stdio + streamable HTTP)
rest.py # REST API + dashboard + /mcp mount
evals/ # retrieval eval harness; supports explicit comparison adapters
Development
pip install -e ".[dev]"
pytest
License
Metadata
Release files for memry 0.2.50
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| memry-0.2.50.tar.gz | 408.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| memry-0.2.50-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 842.1 kB
Release files / memry-0.2.50.tar.gz
| Download URL | memry-0.2.50.tar.gz |
|---|---|
| Size | 408.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8296af49937699e08594fb94d051f91c57965fe91798492f9c09105d7e4c6d85
|
|
BLAKE2b-256 checksum How to use checksums |
fe1b61478105071c644843ba7372eede3406f0aff7e181b38941ed2ea6ff5c19
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency logRelease files / memry-0.2.50-py3-none-any.whl
| Download URL | memry-0.2.50-py3-none-any.whl |
|---|---|
| Size | 433.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cb7dca5aeab0426ee78b44d51f12783b1138b9eb83ae1318a40af878706f645f
|
|
BLAKE2b-256 checksum How to use checksums |
66bc34608ed2f70751fbf6b65ca3b9b520b685c94577c566f5c5fe4d845ce008
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency log