Skip to main content

nomem

Persistent, per-user memory for LLM applications, stored as a knowledge graph that updates itself.

Feed it conversation turns. It pulls out entities and relationships, matches them against what it already knows, and applies real CREATE, UPDATE, and RETIRE operations. Ask it a question later and you get back a small, relevant subgraph instead of a growing pile of chat history.

pip install nomem

The core has no required dependencies. SQLite comes with Python, and the default embedder talks to a local Ollama over plain HTTP.

Quick start

ollama pull nomic-embed-text   # embeddings, 768-dim
ollama pull llama3.1:8b        # extraction
from datetime import UTC, datetime
from nomem import MemoryGraph

with MemoryGraph(user_id="u123", backend_options={"path": "memory.sqlite"}) as graph:
    graph.ingest(
        user="Where's Kira living now?",
        assistant="Kira lives in Berlin.",
    )

    checkpoint = datetime.now(UTC)

    # A later turn that contradicts the first. nomem retires what no longer holds.
    receipt = graph.ingest(
        user="Any news about Kira?",
        assistant="Kira has left Berlin. She lives in Lisbon now.",
    )
    print("retired:", receipt.nodes_retired)
    print("held for review:", [(o.extracted.label, round(o.confidence, 2))
                                for o in receipt.ambiguous_resolutions])

    for node in graph.retrieve("Where does Kira live?").nodes:
        print(node.type, node.label)

Nothing was deleted. The superseded facts were retired, so you can ask what the graph believed before that second turn by passing the checkpoint you captured:

for node in graph.retrieve("Where does Kira live?", as_of=checkpoint).nodes:
    print(node.type, node.label)

What comes back depends on your extraction model — these snippets print whatever it found rather than promising exact labels. You may also see "Lisbon" land in ambiguous_resolutions instead of becoming a node: nomem never silently merges an entity on a low-confidence match, and a real embedding model can score two cities close enough to each other to fall into that band. That is the ambiguity-reporting behavior described below working as intended, not a bug in the example. For a version that is fully deterministic and needs no Ollama at all, see examples/quickstart.py.

Every method has an async twin (aingest, aretrieve, arun_decay). The synchronous API is a thin wrapper, not a second implementation.

Why it works this way

Conversations change facts, so the graph changes with them. When someone moves house, the old fact is retired and superseded rather than left to contradict the new one. Nothing is ever hard-deleted, and both timelines are tracked: when a fact was true in the world, and when nomem learned it.

Retrieval stays bounded. A user with three years of history gets the same token budget as one with three days. You configure the budget; nomem drops the least important nodes to stay inside it.

Uncertainty is reported, not resolved silently. When a name is a near-match for an existing entity, nomem will not quietly merge them. The ambiguity comes back in the ingest receipt and you decide what happens.

You own the settings. Extraction model, traversal depth, decay rates, pruning, edge vocabulary, resolution thresholds: all configurable, with defaults that are documented rather than hidden.

Backends

Backend Setup Vector search
sqlite none, the default linear scan in Python
postgres pip install "nomem[postgres]" pgvector index
neo4j pip install "nomem[neo4j]" native vector index
MemoryGraph(
    user_id="u1",
    backend="postgres",
    backend_options={"dsn": "postgresql://user:pass@localhost:5432/nomem"},
)

All three pass one shared behavioral test suite, so switching backends does not change results. SQLite is for development and small graphs; its vector search is a linear scan that degrades past roughly ten thousand nodes. Use Postgres or Neo4j in production, and give each embedding model its own database, since the vector width is fixed when the schema is created.

Extending it

Storage, embedding, and extraction are all interfaces. Implement BaseBackend, BaseEmbedder, or BaseLLM and pass an instance, or wrap a plain function:

from nomem.embedders import CallableEmbedder

MemoryGraph(user_id="u1", embedder=CallableEmbedder(my_embed_fn, dimensions=768))

For capability rather than substitution, publish a nomem.plugins entry point and MemoryGraph will mount it at graph.<yourname>. See docs/adapters.md, and docs/stability.md for what counts as public API.

Documentation

docs/ covers the architecture, schema, adapters and plugins, stability guarantees, and the roadmap.

Licence and paid tier

The core is MIT and covers everything above: graph CRUD, all three backends, the default embedder, decay, and ingest/retrieve.

Graph export and import, cross-user queries, retrieval analytics, GDPR tooling, team namespacing, and a hosted backend are planned as a separate commercial package. None of them is built or available yet, and nothing in this repository depends on them.

That package, if and when it ships, will depend on this one and extend it through the same public interfaces documented above — the plugin entry point and the adapter registries. It will not be a fork, and none of it will be required to use nomem.

Download files

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

Source Distribution

nomem-0.1.0.tar.gz (84.2 kB view details)

Uploaded Source

Built Distribution

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

nomem-0.1.0-py3-none-any.whl (58.2 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: nomem-0.1.0.tar.gz
  • Upload date:
  • Size: 84.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.11.15

File hashes

Hashes for nomem-0.1.0.tar.gz
Algorithm Hash digest
SHA256 3e942686efd9f1fc38f93a2a67d2b3e9216fa1d7c15bac509edbf98e97fdb652
MD5 ec999671726b3980fed597016a338d83
BLAKE2b-256 a588c74952adc508462245c3790de39453ea7d04d7832f593ba8133aaa4eb3f6

See more details on using hashes here.

File details

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

File metadata

  • Download URL: nomem-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 58.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.11.15

File hashes

Hashes for nomem-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3ff9ec896aa99706404d95760d9a07f5854d9300eeafa00cc94bcd0ba53db2ef
MD5 0fff9068a3ea1284bce67aa027050cc0
BLAKE2b-256 2fcef972df2e48e1213be022c724179fdc9d88078ac3fe2c063dce87726deb06

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page