atomir
intro

Agent memory that
knows when.

Most memory systems overwrite the past and flatten every question into one search. atomir is atomic on both ends — atomic facts in, atomic sub-questions out — and keeps a timeline when you need to know when it changed.

$ pip install atomir
downloads

Fact memory

2023works at Beta
2025works at Acmenow
“Where do I work?” → Acme.
“When did I switch?” → erased

atomir

2023joined Beta
2025left Beta
2025joined Acmenow
“Where do I work?” → Acme.
“When did I switch?” → 2025. Beta before that, since 2023.

how

How it works

Atomic on both ends.

Memory that's atomic where it counts: atomic facts when you write, atomic sub-questions when you read — with an opt-in timeline for when things change.

01 Atomic on write

Each message is distilled into atomic facts and reconciled against what's already known — a change updates the existing fact instead of piling up a near-duplicate.

02 Sub-questions on read

A question is decomposed into the smaller ones it depends on; atomir retrieves for each and composes the answer from what it finds. Dense and keyword search run together, so a fact that hinges on a rare name or title isn't buried under fuzzy matches.

03 Timelines when it matters

Turn on the timeline layer and a change becomes a dated line on a per-relationship page — so “when did this change?” has something to read instead of an overwrite. One layer, off by default.

04 Traceable and forgettable

Every fact points back to the message it came from, so forgetting an entity removes its facts, any events, and the original text.


why

Why the timeline layer exists

Some answers aren't ranked low. They're gone.

When a fact store learns something changed, it updates the fact. That's the sensible thing to do, and it quietly deletes your history. No amount of better retrieval brings it back, because retrieval isn't the part that failed — so for “when” questions, atomir keeps the change as an event instead of an overwrite.

mem.add("u", "I work at Beta")
mem.add("u", "I moved to Acme")

mem.answer("u", "When did I switch jobs?")
→ fact store:  "You work at Acme."   # answers a different question
→ atomir:      "In 2025 — Beta before that, since 2023."

start

Quickstart

Five lines.

Runs with no keys for local testing. The timeline layer is opt-in and experimental.

from atomir.assembly import build_memory_service

mem = build_memory_service()
mem.add("u1", "I left Beta and joined Acme")

mem.answer("u1", "when did I switch jobs?")
mem.timeline("u1", branch="works_at")
Read the docs →

Runs on your stack

No lock-in, no graph database.

Swap any piece from config. Tested end to end on more than one vendor, deliberately.

  • modelsOpenAI · Anthropic · Gemini · Azure OpenAI · Groq · Ollama
  • embeddingsOpenAI · Gemini · Jina · Voyage · Azure · Ollama
  • storageJSON file · SQLite · Qdrant
  • interfacesPython · HTTP · MCP · LangChain · LangGraph

research

Benchmarks — in progress

No numbers here yet, on purpose.

I have results from my own scenarios and I don't trust them enough to print — it's easy to build an evaluation that agrees with you. I'm running the public benchmarks on the existing harness, with current systems as baselines on the same models, and publishing whatever comes out.

Follow the research →

limits

Where it doesn't fit

Known edges.

Questions that hop between people's timelines — “does anyone I know work at Acme?” — want a graph, and atomir isn't one. That's deliberate: the point is getting structured memory without running a graph database.

The JSON store is for development, not scale. The timeline layer is opt-in and marked experimental. With it off, atomir behaves exactly as its atomic-fact core always has.