atomir

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

SlotEnv varValues
ModelLLM_BACKENDfake, openai, anthropic, gemini, azure_openai, groq, ollama
EmbedderEMBED_BACKENDfake, openai, gemini, azure_openai, jina, voyage, ollama
StoreSTORE_BACKENDqdrant, json
Timeline storeEPISODIC_STOREunset (co-located JSON) or sqlite

Environment variables

VariableDefaultPurpose
LLM_BACKENDfakeWhich model provider.
LLM_API_KEYKey for the model provider.
MODELllama-3.3-70b-versatileModel name.
LLM_BASE_URLOverride endpoint (self-host / proxy / Ollama).
LLM_TEMPERATUREUnset = provider default; 0 = deterministic.
EMBED_BACKENDfakeWhich embedder provider.
EMBED_API_KEYKey for the embedder.
EMBED_DIM1024Embedding dimension (match your model).
EMBED_BASE_URLOverride embedder endpoint.
STORE_BACKENDqdrantVector store backend.
STORE_PATH./qdrant_dataLocal path; for the JSON store must end in .json.
STORE_URLQdrant server URL (else embedded/local).
COLLECTIONatomir_memoriesQdrant collection name.
HYBRID_SEARCHtrueFuse dense + BM25 on read.
PLAN_CACHEtrueCache query decompositions.
RECONCILE_MIN_SIM0.5Write-side similarity gate for updates.
EPISODIC_ENABLEDfalseTurn on the timeline layer.
EPISODIC_STOREUnset = JSON; or sqlite.
ONTOLOGY_PACKOptional predicate seed (e.g. personal).
ENTITY_V2falseEmbedding + LLM entity resolution.
ENTITY_MATCH_MIN0.60Similarity floor for v2 entity match.
CHECKPOINT_EVERY50Summarize 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().

MethodReturns
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 & pathBody / 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/answer behave 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_V2 opts 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.