Your AI assistant forgets every decision you've made. It repeats the same failed approaches. It re-explains your stack every session. XME fixes this.
XME gives AI coding assistants persistent memory across sessions. Works with Claude Code, Kiro, Cursor, Codex, and any MCP-compatible tool. No cloud required.
pip install xanther-memory-engine
xme hook install . # 30 seconds — auto-captures every session
xme start my-project # memory starts now
Want code intelligence too? Install XME bundled with the Xanther Context Engine (XCE) in one command:
pip install "xanther-xce[all]" # XCE + XME together # or run instantly, no install: uvx --from "xanther-xce[all]" xanther --help
Architecture
graph TB
subgraph "AI Agent (Claude Code / Kiro / Cursor)"
AGENT[Agent]
HOOKS[IDE Hooks<br/>agentStop · promptSubmit]
end
subgraph "XME Memory Engine"
ENGINE[MemoryEngine<br/>xme/engine.py]
subgraph "Layer 1 — Episodic"
EP[EpisodicStore<br/>Verbatim session transcripts]
end
subgraph "Layer 2 — Facts"
FG[FactGraphStore<br/>Decisions · Attempts<br/>Preferences · Conventions]
EXT[FactExtractor<br/>LLM or regex]
EMB[LocalEmbedder<br/>all-MiniLM-L6-v2]
EXT --> FG
EMB --> FG
end
subgraph "Layer 3 — Context"
CTX[ContextStore<br/>Working state per project+user<br/>UPSERT semantics]
end
ENGINE --> EP & FG & CTX
end
subgraph "Storage"
OS[(OpenSearch<br/>port 9200<br/>Full-text + k-NN)]
NEO4J[(Neo4j<br/>port 7687<br/>Fact graph + vectors)]
SQLITE[(SQLite<br/>.xanther/xme.db<br/>Context + fallback)]
end
subgraph "Outputs"
MCP[MCP Server<br/>11 tools]
DASH[Dashboard<br/>port 8001]
EXP[Exports<br/>Obsidian · Wiki · Graphify]
end
HOOKS -- buffer files --> ENGINE
AGENT -- MCP tool calls --> MCP
EP --> OS & SQLITE
FG --> NEO4J & SQLITE
CTX --> SQLITE
ENGINE --> DASH & EXP
ENGINE --> MCP
Local Infrastructure
graph LR
subgraph "Your Machine"
subgraph "Docker Compose"
NEO4J[(Neo4j:7687<br/>Fact knowledge graph)]
OS[(OpenSearch:9200<br/>Episodic search)]
end
subgraph "XME Process"
CLI[xme CLI]
DASH[xme dashboard<br/>:8001]
MCP_SRV[MCP Server]
end
subgraph "Hook Files"
BUF[.xanther/turns/<br/>Buffer files<br/>written per turn]
DB[.xanther/xme.db<br/>SQLite warm store]
end
subgraph "IDE"
KIRO[Kiro / Claude Code]
MCP_CFG[mcp.json]
end
end
subgraph "External APIs (optional)"
OR[OpenRouter API<br/>LLM fact extraction]
end
KIRO -- agentStop hook --> BUF
KIRO -- promptSubmit hook --> BUF
CLI -- drain buffer --> DB
CLI -- index to --> NEO4J & OS
MCP_CFG -- spawn --> MCP_SRV
MCP_SRV -- read --> NEO4J & OS & DB
KIRO -- MCP tool calls --> MCP_SRV
CLI -. LLM extraction .-> OR
DASH -- read --> NEO4J & OS & DB
Session lifecycle
sequenceDiagram
participant IDE as Kiro / Claude Code
participant HOOK as Hook Handler<br/>.xanther/hook.py
participant BUF as Buffer<br/>.xanther/turns/
participant XME as XME Engine
participant DB as Neo4j + SQLite
IDE->>HOOK: promptSubmit (user message)
HOOK->>BUF: write turn JSON (< 5ms)
IDE->>HOOK: promptSubmit (next message)
HOOK->>BUF: write turn JSON
Note over IDE,DB: ... more turns ...
IDE->>HOOK: agentStop (response finished)
HOOK->>BUF: write session_end marker
Note over BUF,DB: On next xme start or xme_session_end MCP call
XME->>BUF: drain all buffer files
XME->>XME: extract facts (LLM or regex)
XME->>DB: upsert facts with vector dedup
XME->>DB: save episode to OpenSearch
XME->>DB: update working context (UPSERT)
Note over IDE,DB: Next session
IDE->>XME: xme_session_start
XME->>DB: load working context
XME->>DB: load recent facts
XME->>DB: load last episode summary
XME-->>IDE: primed context block (inject into prompt)
Three memory layers
flowchart LR
subgraph "Layer 1 — Episodic"
direction TB
E1[Full session transcripts<br/>verbatim]
E2[Searchable by:<br/>full-text · semantic · date · user]
E3[Backend: OpenSearch<br/>Fallback: SQLite FTS5]
E1 --> E2 --> E3
end
subgraph "Layer 2 — Facts"
direction TB
F1[Extracted knowledge nodes]
F2[Types:<br/>Decision · Attempt<br/>Preference · Convention · Entity]
F3[UPSERT dedup<br/>cosine similarity > 0.85]
F4[Backend: Neo4j graph<br/>+ vector index]
F1 --> F2 --> F3 --> F4
end
subgraph "Layer 3 — Context"
direction TB
C1[Live working state<br/>per project + user]
C2[Fields:<br/>current_task · next_steps<br/>recent_decisions · blockers]
C3[UPSERT only — always current<br/>Backend: SQLite]
C1 --> C2 --> C3
end
EP[Episodic\nStore] --> L1(Layer 1)
FG[Fact\nGraph] --> L2(Layer 2)
CTX[Context\nStore] --> L3(Layer 3)
style L1 fill:#dbeafe
style L2 fill:#dcfce7
style L3 fill:#fef9c3
Quickstart
pip install xanther-memory-engine
xme hook install .
xme start my-project
With full infrastructure (Neo4j + OpenSearch):
cp .env.example .env # set NEO4J_PASSWORD
docker-compose up -d
xme start my-project
Zero infrastructure (SQLite only, no Docker):
XME_FALLBACK_MODE=true xme start my-project
What gets captured automatically
After xme hook install .:
- Every prompt is buffered to
.xanther/turns/(< 5ms, no blocking) - On
agentStop: buffer drains → facts extracted → context updated - Next session: agent gets a primed context block injected automatically
**Current task**: Refactor auth module
**Last session**: Moved JWT to dedicated auth service — success
**Recent decisions**:
- [VALIDATED] Use FastAPI — async support required
- [VALIDATED] PostgreSQL — ACID compliance
**Known failed approaches**:
- Redis distributed lock — timeout under high load
**Next steps**: Deploy auth service to staging
MCP tools (11)
| Tool | Description |
|---|---|
xme_session_start |
Start session, get primed context block |
xme_session_end |
End session: persist episode, extract facts, update context |
xme_add |
Add content — Mem0-style UPSERT with deduplication |
xme_search |
Search across all 3 layers simultaneously |
xme_get_context |
Get working context for prompt injection |
xme_facts |
Query fact graph (filter by type, user, keyword) |
xme_episodes |
Full-text + semantic search over past sessions |
xme_remember |
Explicitly store a typed fact |
xme_forget |
Soft-delete a memory node |
xme_export |
Export to Obsidian vault / wiki / Graphify JSON |
xme_context_update |
Partial UPSERT of working context fields |
Add to MCP config:
{
"mcpServers": {
"xme": {
"command": "xme",
"args": ["serve"],
"env": {
"NEO4J_PASSWORD": "your-password"
}
}
}
}
Deduplication
Facts are stored once, not repeated across sessions:
flowchart TD
A[New content added] --> B[Embed with\nall-MiniLM-L6-v2]
B --> C{Similar fact exists?\ncosine > 0.85}
C -- Yes --> D[Merge into existing fact\nupdate content + metadata]
C -- No --> E[Create new fact node]
D --> F[Update Neo4j + SQLite]
E --> F
Comparison
| Mem0 | Zep | MemPalace | XME | |
|---|---|---|---|---|
| Episodic memory | ✅ | ✅ | ✅ | ✅ |
| Fact graph | partial | ✅ | ❌ | ✅ |
| Working context UPSERT | ❌ | ❌ | ❌ | ✅ |
| Multi-user scoping | ✅ | ✅ | ❌ | ✅ |
| Deduplication | ✅ | ✅ | ❌ | ✅ |
| Local-first / open source | ❌ | ❌ | ✅ | ✅ |
| MCP tools | ❌ | ❌ | ❌ | ✅ (11) |
| Obsidian export | ❌ | ❌ | ❌ | ✅ |
| Dashboard UI | ❌ | ✅ | ❌ | ✅ |
| Code graph integration | ❌ | ❌ | ❌ | ✅ via XCE |
CLI
xme start <project> # init + show stats
xme add <project> <user> <text> # add content to memory
xme search <project> <query> # search all layers
xme facts <project> # list facts
xme stats <project> # memory health metrics
xme export <project> # export (obsidian/wiki/graphify)
xme dashboard # launch web UI (port 8001)
xme hook install [path] # install Kiro + Claude Code hooks
xme hook uninstall [path] # remove hooks
Configuration
# LLM for better fact extraction (optional — regex works without it)
OPENROUTER_API_KEY=sk-or-...
XME_LLM_MODEL=openai/gpt-4o-mini
# Neo4j — fact graph (recommended, free tier at console.neo4j.io)
NEO4J_URI=bolt://localhost:7687
NEO4J_PASSWORD=your-password
# OpenSearch — episodic search (optional, falls back to SQLite FTS5)
XME_OPENSEARCH_URL=http://localhost:9200
# Zero-infrastructure mode
XME_FALLBACK_MODE=false # set true for SQLite-only, no Docker needed
See .env.example for the complete reference.
Related
Xanther Context Engine (XCE) — code graph intelligence. When installed alongside XME, decisions link directly to the code they affect.
pip install "xanther-context-engine[memory]" # XCE + XME together
License
Apache 2.0. See LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file xanther_xme-0.1.1.tar.gz.
File metadata
- Download URL: xanther_xme-0.1.1.tar.gz
- Upload date:
- Size: 100.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1625290c0cd909524c19741a957a38ba201d76ead7f1b432627a0e698d81841a
|
|
| MD5 |
0b5fd50668c514e5ff37ae8d047e7f08
|
|
| BLAKE2b-256 |
19323787ff096b76ff02c1d0863b3e397b0bcf27985ffd3a239a2d6bf65aa0ae
|
File details
Details for the file xanther_xme-0.1.1-py3-none-any.whl.
File metadata
- Download URL: xanther_xme-0.1.1-py3-none-any.whl
- Upload date:
- Size: 56.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e0379bb1e00a65ba1f88f93857a9dd95381898317a87836c0f7e885756c452fb
|
|
| MD5 |
58b3d8a589600cc843b180f99f1f5306
|
|
| BLAKE2b-256 |
d3834c37f8474a0034759df1c4f284bf6dd5f8968e0a8f4b76594cdd9ee4fb87
|