Long-term structured memory for AI agents — facts, hybrid search, profiles, and MCP.
Project description
anima
Long-term, structured memory for AI agents. Extract facts from conversations, deduplicate and supersede them over time, search with hybrid semantic + BM25 retrieval, maintain user profiles, and expose everything over a clean Python library or MCP server.
Features
- Fact extraction — LLM-backed extraction from conversation turns (OpenRouter by default)
- Deduplication — embedding similarity + slot rules + LLM review; supersede outdated facts
- Temporal validity — facts are superseded, not deleted, so you can answer “what did we know before?”
- Production embeddings — local
sentence-transformersor OpenAI by default (not random vectors) - Hybrid search — FAISS semantic recall + BM25 rerank, fused with confidence and recency
- Document ingestion — chunk and index Markdown (extensible ingester registry)
- User profiles — incremental O(1) profile updates on every
add()(requires LLM) - Entity graph — optional NetworkX graph of entities and relationships (requires LLM)
- Lifecycle — confidence decay, TTL expiry, signal-based
watch()auto-save - MCP server — six tools for Cursor, Claude Desktop, or any MCP client
- Export / import — JSON backup and restore of memories + profile
Requirements
- Python 3.12+
- uv (recommended for development) or pip
Quickstart
Install
pip install anima-mem
Optional extras:
pip install "anima-mem[dev]" # pytest, pytest-asyncio, python-dotenv
pip install "anima-mem[benchmark]" # LoCoMo benchmark runner deps (see Development)
Python API
import asyncio
from anima import Config, Memory
async def main():
mem = Memory(config=Config.from_env())
await mem.add("User: I moved to Berlin last week.", user_id="alice")
result = mem.search("Where does Alice live?", user_id="alice")
print(result.injected_prompt)
page = mem.list("alice", limit=20)
print(f"{len(page.items)} of {page.total} memories")
asyncio.run(main())
Without OPENROUTER_API_KEY, Memory() still works for storage and search: each message is stored as a single raw fact and embeddings default to local sentence-transformers. LLM extraction, dedup review, profiles, and entity graph population require OPENROUTER_API_KEY.
MCP server
After installing from PyPI:
{
"mcpServers": {
"anima": {
"command": "anima-mcp"
}
}
}
From a local clone (stdio transport):
{
"mcpServers": {
"anima": {
"command": "uv",
"args": ["run", "anima-mcp"],
"cwd": "/path/to/anima"
}
}
}
Tools: memory_add, memory_search, memory_profile, memory_ingest_file, memory_list, memory_delete
User-controlled memory API
| Method | Description |
|---|---|
mem.list(user_id, offset=0, limit=50) |
Paginated memory list |
mem.pin(memory_id) |
Mark permanent (no decay / TTL deletion) |
mem.export(user_id) |
MemoryExport JSON snapshot |
mem.import_memories(data) |
Restore from export |
export_data = mem.export("alice").model_dump(mode="json")
# ... later or on another machine ...
mem.import_memories(export_data)
Configuration
Set environment variables before creating Memory(config=Config.from_env()). The library reads os.environ only — export variables in your shell or use a tool that loads .env (e.g. uv run).
Full list: .env.example
| Variable | Default | Purpose |
|---|---|---|
OPENROUTER_API_KEY |
— | LLM extraction, dedup, profiles, graph |
DATABASE_URL |
sqlite:///./anima.db |
SQLAlchemy URL |
VECTOR_STORE_PATH |
./data/faiss |
FAISS index directory |
GRAPH_STORE_PATH |
./data/graph |
Entity graph JSON |
EMBEDDER |
st (or openai if OPENAI_API_KEY set) |
st or openai |
EMBEDDING_DIM |
1536 (OpenAI) / auto for ST |
Set for OpenAI; ST uses model dim |
ST_MODEL |
all-MiniLM-L6-v2 |
Local embedding model |
DECAY_RATE_PER_WEEK |
0.05 |
Confidence decay rate |
WATCH_KEYWORDS |
remember,... |
watch() trigger phrases |
Memory(config=Config.from_env()) picks a production embedder automatically. Override explicitly if needed:
from anima import Memory, Config, build_embedder
embed_fn, dim = build_embedder()
cfg = Config.from_env()
cfg.embedding_dim = dim
mem = Memory(config=cfg, embed_fn=embed_fn)
Development
Clone the repository (required for tests, benchmarks, and lint):
git clone https://github.com/themillenniumfalcon/anima.git
cd anima
cp .env.example .env # OPENROUTER_API_KEY — extraction, dedup, profile, graph
uv sync --extra dev # or: pip install -e ".[dev]"
uv run pytest -v
uv run ruff check src tests
Benchmarks
LoCoMo evaluation requires a repo checkout (the runner is not shipped on PyPI):
uv sync --extra benchmark # or: pip install -e ".[benchmark]"
uv run python benchmarks/run_locomo.py
Methodology: benchmarks/README.md
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
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 anima_mem-0.1.0.tar.gz.
File metadata
- Download URL: anima_mem-0.1.0.tar.gz
- Upload date:
- Size: 59.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.6.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
aa8b1feeee76c7bc6d322155790458b8e1305f727eb35f7014a288067695a452
|
|
| MD5 |
92d16348cf0c5fe0b4bf38695ff6d9dd
|
|
| BLAKE2b-256 |
082a1bd2803b04aadc21d694c17fb9107dce67499c9fe53f5e1f680389cd8d74
|
File details
Details for the file anima_mem-0.1.0-py3-none-any.whl.
File metadata
- Download URL: anima_mem-0.1.0-py3-none-any.whl
- Upload date:
- Size: 73.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.6.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b89a2c2e9aa7b841ce4e8cecdd8f959469aa49cb25ff26fb7945b5deb2ba61e4
|
|
| MD5 |
d1a00c863c9e0c69d24420f7ea52cfae
|
|
| BLAKE2b-256 |
ec364e9cb2b409c1ae23b8bffd29070f5a55790a9c90dd64c18e4a5a593c577f
|