Skip to main content

Atomic memory infrastructure for agents — atomic facts on write, atomic sub-question decomposition on read.

Project description

atomir

Atomic memory infrastructure for agents. Memory is atomic on both ends: atomic facts on write (extract → reconcile), atomic sub-question decomposition on read (decompose → retrieve per sub-question → union).

The thesis

Most memory systems store raw text blobs and retrieve with a single fuzzy similarity search. atomir does the opposite at both ends:

  • Write — a message is split into small, self-contained facts, and each is reconciled into memory (ADD new, UPDATE a changed value keeping history, DELETE what's no longer true, NOOP duplicates). A similarity gate biases toward ADD so distinct facts never over-merge.
  • Read — a question is decomposed into atomic sub-questions (only when it helps), each retrieved independently, then results are unioned. This surfaces facts a single whole-question embedding misses.

Vendor-neutral by construction

The LLM, the embedder, and the vector store are each an interface chosen at runtime by config ({provider, config} blocks). The engine imports only the interfaces — never a provider SDK or vendor name. Swapping Groq↔OpenAI, Jina↔Voyage, or Qdrant↔pgvector is one config change plus one small class. Defaults use fake backends, so everything runs with no external keys.

Install

pip install -e .                 # core (offline: fake LLM + fake embedder + JSON store)
pip install -e ".[qdrant]"       # add the Qdrant backend
pip install -e ".[api]"          # add the FastAPI server
pip install -e ".[all]"          # everything

groq and jina need no extra — they call their HTTP APIs over the standard library.

Quickstart — embedded, no Docker

Runs fully offline with the default fake backends:

from atomir.assembly import build_memory_service

mem = build_memory_service()                      # backends chosen by .env
mem.add("user123", "I'm vegetarian and my manager is Dana Lopez.")
mem.add("user123", "I'm working on Project Atlas.")

hits = mem.search("user123", "who should I email about my project?")
print(hits["subquestions"])                       # the sub-questions it asked
for r in hits["results"]:
    print(r["text"], round(r["score"], 3))

mem.get_all("user123")
mem.delete("user123", fact_id)
mem.reset("user123")

To use real providers, copy .env.example to .env and set the keys/backends.

Production — Docker Compose (API + Qdrant server)

cp .env.example .env             # optional: add real keys; without it, LLM/embedder run fake
docker compose up --build        # brings up the API and a Qdrant server

The API points at the Qdrant service via STORE_URL=http://qdrant:6333. Then:

curl -XPOST localhost:8000/memories -H 'content-type: application/json' \
  -d '{"user_id":"u1","text":"My manager is Dana."}'
curl -XPOST localhost:8000/search -H 'content-type: application/json' \
  -d '{"user_id":"u1","query":"who is my manager?"}'

HTTP endpoints

Method Path Body / query Returns
POST /memories {user_id, text} {operations, facts}
POST /search {user_id, query, k?, decompose?} {subquestions, results}
GET /memories ?user_id= list of facts
DELETE /memories/{id} ?user_id= {deleted, id} (404 if absent)
DELETE /memories ?user_id= {reset}
GET /health {status, store, llm, embedder}

MemoryClient(base_url) (in atomir.client) wraps these with the same method names and return shapes.

Configuration

All config is read from the environment (see .env.example): LLM_BACKEND, LLM_API_KEY, MODEL, EMBED_BACKEND, EMBED_API_KEY, EMBED_DIM, RECONCILE_MIN_SIM, STORE_BACKEND, COLLECTION, STORE_URL, STORE_PATH.

Known limitations

  • Reconciler threshold is untuned. RECONCILE_MIN_SIM defaults to 0.6; on Jina, real "same-attribute" pairs measured ~0.6, so it sits right on the edge. It should be tuned per embedder with the eval harness, not trusted as-is.
  • The JSON backend is NOT crash-safe. It rewrites the whole file without atomic replace/fsync — dev and tests only. Use Qdrant for durable storage.
  • No transactions. Writes are serialized per user with a simple lock (Step 9); full transactional rollback is deferred (DECISION #5).
  • Read returns facts, not a composed answer. search returns the relevant facts and sub-questions; turning them into a final sentence is the caller's LLM's job.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

atomir-0.1.0.tar.gz (25.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

atomir-0.1.0-py3-none-any.whl (31.2 kB view details)

Uploaded Python 3

File details

Details for the file atomir-0.1.0.tar.gz.

File metadata

  • Download URL: atomir-0.1.0.tar.gz
  • Upload date:
  • Size: 25.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for atomir-0.1.0.tar.gz
Algorithm Hash digest
SHA256 47af75d499017515c05bf86281e98fb1300a5b208e1a119ad9d16d5c2b53a8cb
MD5 5fc380a67c7cac2c17e150033d1bcc5b
BLAKE2b-256 8ac6f2f902e38e01fcba3ee136a33e6b0cfd80468b5b1ccfc6a944646ea1f903

See more details on using hashes here.

File details

Details for the file atomir-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: atomir-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 31.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for atomir-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f9231a5632c29e98f7a567c20e93d7e2b7e718a618458bde4e8f562caf707f39
MD5 326e5e8cd98a6c7ce602376c5ddb15ca
BLAKE2b-256 5a672aaf7ce44061f67a9598733a1f948301a2dd3164b251fcc360497ad6a1a4

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page