Documentation
atomir is atomic memory for agents: atomic facts on write, atomic sub-question decomposition on read, and an opt-in timeline layer for temporal questions. Vendor-neutral — the model, embedder, and store are each chosen at runtime by config.
01 — Installation
Install
Requires Python 3.11+. The core is dependency-light; provider SDKs aren't needed — OpenAI, Anthropic, Gemini, Azure, Groq, Jina, Voyage and Ollama are all reached over plain HTTP.
pip install atomir
Optional extras
Install only what a deployment uses:
pip install "atomir[api]" # HTTP server (FastAPI + uvicorn) pip install "atomir[mcp]" # MCP server pip install "atomir[qdrant]" # Qdrant vector store pip install "atomir[langchain]" # LangChain integration pip install "atomir[langgraph]" # LangGraph nodes pip install "atomir[all]" # everything above
With no configuration, atomir defaults to the fake model and embedder, so it runs offline with zero keys — useful for tests and a first look.
02 — Quickstart
Quickstart
Build a memory service — backends are chosen entirely from the environment — then add, search, and answer.
from atomir.assembly import build_memory_service mem = build_memory_service() # providers from env; fake by default mem.add("u1", "I'm vegetarian and my manager is Dana.") mem.search("u1", "who should I email about my project?") mem.answer("u1", "who is my manager?") # {answer, subquestions, results}
Timelines (opt-in)
Set EPISODIC_ENABLED=true to turn on the timeline layer. A change is then recorded as a dated event rather than an overwrite, and timeline() returns the ordered history.
mem.add("u1", "I left Beta in November and joined Acme Corp.") mem.timeline("u1", branch="works_at") # ordered events (when things changed)
03 — Configuration
Configuration
All configuration is read from the environment (a local .env is loaded in development). There are three independent, swappable slots — model, embedder, and store.
Providers
| Slot | Env var | Values |
|---|---|---|
| Model | LLM_BACKEND | fake, openai, anthropic, gemini, azure_openai, groq, ollama |
| Embedder | EMBED_BACKEND | fake, openai, gemini, azure_openai, jina, voyage, ollama |
| Store | STORE_BACKEND | qdrant, json |
| Timeline store | EPISODIC_STORE | unset (co-located JSON) or sqlite |
Environment variables
| Variable | Default | Purpose |
|---|---|---|
| LLM_BACKEND | fake | Which model provider. |
| LLM_API_KEY | — | Key for the model provider. |
| MODEL | llama-3.3-70b-versatile | Model name. |
| LLM_BASE_URL | — | Override endpoint (self-host / proxy / Ollama). |
| LLM_TEMPERATURE | — | Unset = provider default; 0 = deterministic. |
| EMBED_BACKEND | fake | Which embedder provider. |
| EMBED_API_KEY | — | Key for the embedder. |
| EMBED_DIM | 1024 | Embedding dimension (match your model). |
| EMBED_BASE_URL | — | Override embedder endpoint. |
| STORE_BACKEND | qdrant | Vector store backend. |
| STORE_PATH | ./qdrant_data | Local path; for the JSON store must end in .json. |
| STORE_URL | — | Qdrant server URL (else embedded/local). |
| COLLECTION | atomir_memories | Qdrant collection name. |
| HYBRID_SEARCH | true | Fuse dense + BM25 on read. |
| PLAN_CACHE | true | Cache query decompositions. |
| RECONCILE_MIN_SIM | 0.5 | Write-side similarity gate for updates. |
| EPISODIC_ENABLED | false | Turn on the timeline layer. |
| EPISODIC_STORE | — | Unset = JSON; or sqlite. |
| ONTOLOGY_PACK | — | Optional predicate seed (e.g. personal). |
| ENTITY_V2 | false | Embedding + LLM entity resolution. |
| ENTITY_MATCH_MIN | 0.60 | Similarity floor for v2 entity match. |
| CHECKPOINT_EVERY | 50 | Summarize a branch's oldest segment past N nodes. |
The branch-matcher thresholds (BRANCH_MATCH_AUTO, BRANCH_MATCH_GRAY_LOW, BRANCH_RESOLVE_FLOOR, BRANCH_RESOLVE_MARGIN) default per-embedder from a calibrated table and are overridable by env. Leave them unset unless you're tuning.
Example .env
LLM_BACKEND=openai LLM_API_KEY=sk-... MODEL=gpt-4o EMBED_BACKEND=openai EMBED_API_KEY=sk-... EMBED_DIM=1536 STORE_BACKEND=json STORE_PATH=./atomir.json # JSON store path must end in .json EPISODIC_ENABLED=true # opt into timelines
04 — Python
Python API
Everything hangs off a MemoryService built by build_memory_service().
| Method | Returns |
|---|---|
build_memory_service(settings=None) | A MemoryService wired from env. |
mem.add(user_id, text, recorded_at=None) | Extract & reconcile facts (and events if episodic). recorded_at is an ISO date for late-reported events. |
mem.search(user_id, query, k=6, decompose=True) | {subquestions, results} |
mem.answer(user_id, query, k=6, decompose=True) | {answer, subquestions, results} — composed, grounded. |
mem.timeline(user_id, entity=None, branch=None, since=None, until=None) | Ordered events. Empty unless episodic is on. |
mem.get_all(user_id) | All current facts. |
mem.forget(user_id, entity) | bool — cascade-deletes an entity's facts, events, and source text. Needs episodic. |
mem.delete(user_id, fact_id) | bool — remove one fact. |
mem.reset(user_id) | bool — wipe a user. |
LangChain / LangGraph
from atomir.assembly import build_memory_service from atomir.integrations.langchain import AtomirMemory mem = AtomirMemory(build_memory_service(), user_id="user:1") # LangGraph: from atomir.integrations.langgraph import recall_node, remember_node
05 — HTTP
HTTP API
Run the server (needs the api extra); it reads the same environment.
pip install "atomir[api]" uvicorn atomir.api:app --port 8000
| Method & path | Body / query |
|---|---|
POST /memories | {user_id, text} |
POST /search | {user_id, query, k?} |
POST /answer | {user_id, query, k?} |
GET /memories | ?user_id= |
GET /timeline | ?user_id=&entity=&branch=&since=&until= |
POST /forget | {user_id, entity} |
DELETE /memories/{fact_id} | ?user_id= |
DELETE /memories | ?user_id= (reset) |
GET /health | — |
Python client
from atomir.client import MemoryClient c = MemoryClient("http://localhost:8000") c.add("u1", "I moved to Acme") c.answer("u1", "where do I work?")
06 — MCP
MCP server
Expose atomir as memory to Claude Desktop, Claude Code, or any MCP client.
pip install "atomir[mcp]" atomir-mcp
Claude Desktop config
{
"mcpServers": {
"atomir": {
"command": "atomir-mcp",
"env": {
"LLM_BACKEND": "openai", "LLM_API_KEY": "sk-...",
"EMBED_BACKEND": "openai", "EMBED_API_KEY": "sk-...",
"EMBED_DIM": "1536",
"STORE_BACKEND": "json", "STORE_PATH": "./atomir.json",
"EPISODIC_ENABLED": "true"
}
}
}
}Tools
remember(text)— store a memory.recall(query)— retrieve relevant memories.list_memories()— all current facts.forget(fact_id)— delete one fact.timeline(entity="", branch="")— ordered events (needs episodic).forget_about(entity)— cascade-forget an entity (needs episodic).
07 — Limitations
Limitations
Written plainly, because knowing where a tool stops is part of using it.
- Not a graph. Questions that hop across people's timelines (“does anyone I know work at Acme?”) need a graph database; atomir deliberately isn't one.
- JSON store is dev-scale. Fine for development and small deployments; use Qdrant (facts) or SQLite (timeline) as you grow.
- Timelines are experimental and opt-in. Off by default via
EPISODIC_ENABLED; with it off,add/search/answerbehave exactly as the atomic-fact core always has. - Entity resolution is conservative. The default matches entities by exact alias and under-merges rather than risk a wrong merge;
ENTITY_V2opts into embedding + LLM resolution. - Branch-naming quality tracks model strength. The timeline layer emits knowledge-graph predicates; weaker models name them less consistently.
- Benchmarks are pending. Public results aren't published yet — see Research.