Skip to main content

Memory Nemo (MNemo) — Python bindings

Repository overview: root README · landing page: index.html.

Python bindings for Memory Nemo (MNemo), the encrypted single-file agent-memory engine. The package is a thin PyO3 wrapper over the Rust core in the sibling mnemo/ crate — the storage engine, AES-256-GCM encryption, the write-ahead log, the IVF+PQ index, snapshots, and the agent-memory model all run as compiled Rust; Python only sees a small, ergonomic surface.

Distribution name on PyPI: mnemo-engine (both mnemo and mnemo-db were already taken by unrelated projects). The import name stays mnemo, so pip install mnemo-engine then import mnemo is the full setup.

For AI agents

An agent that's been handed a .mnemo file and its passphrase can become productive in two calls — no external documentation required:

import mnemo, os

db = mnemo.open("agent.mnemo", os.environ["MNEMO_PASSPHRASE"])

# The file introduces itself: returns memories tagged metadata.area="onboarding",
# manifest first. Each entry tells you the embedder, agent_id convention,
# project metadata, and any other context the file's author recorded.
for entry in db.about():
    print(entry["content"])

Creating a new database? It's self-describing from creation:

db = mnemo.open("new.mnemo", "passphrase", dimensions=384)
db.insert_default_manifest()    # same scaffold that `mnemo init` adds
db.flush()

The scaffold tells the next agent what to do: replace it with one that records your real embedder and conventions. See the main README for the full pattern.

Build & install

The bindings build with maturin:

pip install maturin
cd mnemo-python
maturin build --release          # produces a wheel in target/wheels/
pip install target/wheels/mnemo-*.whl

maturin develop installs straight into the active virtualenv during development. The extension is built against the stable ABI (abi3-py38), so a single wheel works on CPython 3.8 and newer.

Usage

import mnemo

# Open an existing database, or create one (dimensions required to create).
db = mnemo.open("agent.mnemo", "passphrase", dimensions=4)

# Store typed memories. memory_type is one of:
# "episodic", "semantic", "procedural", "working".
db.remember(
    "the user prefers concise answers",
    "procedural",
    [0.1, 0.2, 0.3, 0.4],
    importance=0.8,
    agent_id="assistant",
    metadata={"source": "onboarding"},
)

# Multi-signal recall — similarity blended with recency, importance, frequency.
for hit in db.recall([0.1, 0.2, 0.3, 0.4], top_k=5):
    print(hit["score"], hit["content"])

db.flush()
db.close()

mnemo.open returns a Mnemo object that is also a context manager — with mnemo.open(...) as db: flushes automatically on exit.

Sessions

A Session wraps the database for one conversation: it records each turn as working memory and, when closed, consolidates those turns into durable episodic memory.

db = mnemo.open("agent.mnemo", "passphrase", dimensions=4)

with db.session("assistant") as chat:
    chat.add_turn(mnemo.Turn.user("my flight is Friday", [1.0, 0.0, 0.0, 0.0]))
    chat.add_turn(mnemo.Turn.assistant("noted", [0.9, 0.1, 0.0, 0.0]))
    context = chat.recall([1.0, 0.0, 0.0, 0.0], top_k=5)
    # leaving the block consolidates the turns into episodic memory

# or, explicitly:
chat = db.session("assistant")
chat.add_turn(mnemo.Turn("system", "be concise", [0.0, 0.0, 0.0, 1.0]))
chat.close()      # consolidate working -> episodic
# chat.discard()  # alternative: throw the turns away

mnemo.Turn has Turn.user(...), Turn.assistant(...), Turn.system(...), and Turn(role, content, vector). A Session's recall is always scoped to its own agent.

API

mnemo.open(path, passphrase, dimensions=None) -> Mnemo

Mnemo methods:

Method Purpose
remember(content, memory_type, vector, *, agent_id, importance, session_id, ttl_secs, shared, metadata) Store a memory; returns its id
recall(query, top_k=10, memory_types=None, agent_id=None, track_access=True) Multi-signal ranked retrieval. track_access=False skips access-stat updates (fully read-only recall)
search(query, top_k=10) Exact nearest-neighbour search
get(id) / delete(id) Fetch / soft-delete by id
about() Self-describing onboarding briefing — memories tagged metadata.area="onboarding", manifest first
insert_default_manifest() Insert the canonical scaffold manifest (same one mnemo init adds); returns its id
session(agent_id) Begin a conversation Session
flush() / close() Persist pending changes
verify() Decrypt and re-validate every record
build_index() / drop_index() / has_index() Approximate index control
snapshots() / restore_to(txn_id) / restore_to_time(unix_secs) Point-in-time recovery
set_cache_capacity(pages) / page_cache_stats() Page-cache tuning (renamed from cache_stats in v0.4.0 — that name now belongs to the result cache below)
cache_put(namespace, key, value, content_type="text", ttl_secs=None) Exact-key result cache put (Phase 10.1). value accepts str or bytes
cache_get(namespace, key) Exact-key cache get — returns dict on hit (value is bytes) or None
cache_delete(namespace, key) / cache_purge(namespace=None, expired_only=False) Tombstone by key or in bulk
cache_stats(namespace=None) Result-cache stats: {entries, bytes, hits, misses, hit_rate, evictions}
cache_put_semantic(namespace, key, vector, value, model, content_type="text", ttl_secs=None) Semantic cache put (Phase 10.2) — vector must match db dimensions
cache_get_semantic(namespace, query, model, threshold=0.97) Top-1 cosine over the namespace's vectored entries whose model matches
set_cache_flush_policy(policy, max_dirty=None, max_age_secs=None) policy="strict" (default; every put durable on return) or "batched" (engine auto-flushes at max_dirty puts or max_age_secs seconds). Under batched, cache_put and friends no longer force an eager flush — call db.flush() yourself if you need it before a specific boundary
set_cache_budget(namespace, max_entries, max_bytes) Per-namespace LRU cap (defaults 10 000 entries / 64 MiB). On the next cache_put the LRU is evicted until both limits are satisfied
set_max_snapshots(max) Override the snapshot-manifest retention cap (default 256; 0 disables)
memories() Enumerate every live (non-deleted, non-expired) memory as a list of dicts
dimensions() Embedding dimensionality this database was created with (also on stats()["dimensions"])
rekey(new_passphrase, fast=False) Change the passphrase. Re-derives the KEK and re-wraps the DEK; encrypted pages are not rewritten (O(1) in db size). fast=True uses the cheap-Argon2 test params — omit for production
set_embedder(fn, *, name, dimensions, normalize=True) Register a Python callable embed(texts: list[str]) -> list[list[float]] as this handle's text embedder (Phase 3). Dimension-checks against the database's width; identity-checks against (and on first use upserts) the scaffold manifest's embedder_name / embedder_dimensions / embedder_normalize keys, so swapping embedders between opens fails loudly
remember_text(content, memory_type, **kwargs) Embed content via the registered embedder and remember the result. Accepts the same per-memory kwargs as remember (agent_id, importance, session_id, ttl_secs, shared, metadata)
recall_text(query, top_k=10, memory_types=None, agent_id=None, track_access=True) Embed query via the registered embedder and run recall. Same return shape as recall
cache_get_semantic_text(namespace, text, threshold=0.97) Text-side semantic cache lookup; uses the embedder's name as the semantic cache's model component
stats() Summary statistics
export_encrypted(dest) Copy the (already-encrypted) file elsewhere
len(db) Live memory count

Plus one module-level function:

Function Purpose
mnemo.compact_file(path, passphrase) Rewrite the file out-of-place, dropping tombstoned and expired memories and reclaiming stale pages. Caller's own handle must be closed first. Returns {"before": int, "after": int}

Session methods: add_turn(turn), recall(query, top_k=10, memory_types=None), close(), discard(), id(), agent(), turn_ids(), turn_count(); also a context manager (exiting consolidates).

Memories and results are returned as plain dicts; metadata round-trips as a nested dict.

Result caching

MNemo doubles as a durable result cache — memoize LLM tool-call outputs, prompt→completion pairs, HTTP responses, anything reconstructible on miss — in the same encrypted single file that holds your agent memory. See the core README's Result caching section for the concepts (namespaces, budgets, TTL, Strict vs Batched flush policy).

Recipe: @db.cached(...) decorator (~90 lines, pure Python)

Copy this into your project as mnemo_cached.py or paste inline. It wraps any function whose arguments serialize to JSON, keying the cache on f"{namespace}:{fn_name}:{json_args}". When embed is supplied it switches to semantic mode; otherwise it's an exact-key cache.

"""@db.cached — pure-Python helper on top of mnemo.Mnemo's cache API.

Usage:
    import mnemo, json, os
    from mnemo_cached import cached

    db = mnemo.open("agent.mnemo", os.environ["MNEMO_PASSPHRASE"])

    @cached(db, namespace="llm", ttl=3600)
    def call_llm(prompt: str) -> str:
        return openai.chat.completions.create(...).choices[0].message.content

    # Second call with the same prompt is a cache hit.
    answer = call_llm("summarize this doc")
"""
from __future__ import annotations

import functools
import hashlib
import json
from typing import Any, Callable, Optional


def cached(
    db,
    *,
    namespace: str,
    ttl: Optional[int] = None,
    embed: Optional[Callable[[str], list[float]]] = None,
    model: Optional[str] = None,
    threshold: float = 0.97,
    key_fn: Optional[Callable[..., str]] = None,
):
    """Memoize a function's results into a mnemo.Mnemo cache.

    - Exact-key mode (default): key is JSON(args, kwargs) plus fn name.
    - Semantic mode: pass `embed=my_embedder` and `model="..."`; the key
      is embedded and semantic recall is tried before falling back.
    - Custom `key_fn(*args, **kwargs) -> str` overrides the default key.
    """
    if embed is not None and model is None:
        raise ValueError("semantic mode requires `model=`")

    def decorator(fn: Callable[..., Any]) -> Callable[..., Any]:
        @functools.wraps(fn)
        def wrapper(*args, **kwargs):
            key = key_fn(*args, **kwargs) if key_fn else _default_key(fn, args, kwargs)

            # Try semantic hit first (if configured); otherwise exact-key.
            if embed is not None:
                vec = embed(key)
                hit = db.cache_get_semantic(namespace, vec, model, threshold)
                if hit is not None:
                    return _decode(hit["value"])
                # Miss — compute the real result.
                result = fn(*args, **kwargs)
                encoded = _encode(result)
                db.cache_put_semantic(namespace, key, vec, encoded, model, ttl_secs=ttl)
                return result

            hit = db.cache_get(namespace, key)
            if hit is not None:
                return _decode(hit["value"])
            result = fn(*args, **kwargs)
            encoded = _encode(result)
            db.cache_put(namespace, key, encoded, ttl_secs=ttl)
            return result

        return wrapper

    return decorator


def _default_key(fn, args, kwargs) -> str:
    payload = json.dumps(
        {"fn": fn.__qualname__, "args": args, "kwargs": kwargs},
        default=str, sort_keys=True,
    )
    return hashlib.sha256(payload.encode()).hexdigest()


def _encode(v: Any) -> bytes:
    if isinstance(v, (bytes, bytearray)):
        return bytes(v)
    if isinstance(v, str):
        return v.encode()
    return json.dumps(v, default=str).encode()


def _decode(b: bytes) -> Any:
    # Best-effort round-trip: try JSON first, fall back to str, then bytes.
    try:
        return json.loads(b)
    except (json.JSONDecodeError, UnicodeDecodeError):
        try:
            return b.decode()
        except UnicodeDecodeError:
            return b

Recipe: OpenAI/Anthropic call wrapped end-to-end

import os, mnemo
from openai import OpenAI
from mnemo_cached import cached

db = mnemo.open("agent.mnemo", os.environ["MNEMO_PASSPHRASE"])
client = OpenAI()

@cached(db, namespace="openai-gpt-4o-mini", ttl=86_400)
def chat(prompt: str) -> str:
    r = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}],
    )
    return r.choices[0].message.content

# First call → hits the model, caches the response.
# Second identical call → hits the cache, no OpenAI request.
print(chat("Give me a haiku about SQLite."))
print(chat("Give me a haiku about SQLite."))

print("cache stats:", db.cache_stats("openai-gpt-4o-mini"))

Anthropic swap-in — same shape, different client:

import anthropic
from mnemo_cached import cached

client = anthropic.Anthropic()

@cached(db, namespace="claude-3-5-sonnet", ttl=86_400)
def chat(prompt: str) -> str:
    r = client.messages.create(
        model="claude-3-5-sonnet-latest",
        max_tokens=1024,
        messages=[{"role": "user", "content": prompt}],
    )
    return r.content[0].text

Recipe: semantic cache with an embedder

from openai import OpenAI
from mnemo_cached import cached

client = OpenAI()
def embed(text: str) -> list[float]:
    return client.embeddings.create(
        model="text-embedding-3-small", input=text,
    ).data[0].embedding

# Semantic mode: prompts that differ in wording but mean the same
# thing hit the same cache entry.
@cached(db, namespace="llm-semantic", ttl=86_400,
        embed=embed, model="text-embedding-3-small", threshold=0.97)
def chat(prompt: str) -> str:
    return client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}],
    ).choices[0].message.content

Note: the database must have been created with dimensions matching your embedder's output — text-embedding-3-small is 1536-dim, bge-large-en-v1.5 is 1024-dim, etc. Set at mnemo.open(path, pw, dimensions=1536) on first creation.

Don't use this for: multi-node fleets where the cache must be consistent across hosts (use Redis / DynamoDB DAX). MNemo's cache is optimized for a single-host agent that owns its cache file.

Recipe: set_embedder + text-only remember / recall

Phase 3 ships a per-handle embedder hook so callers never have to pass vectors by hand. Register once at open time; every subsequent remember_text / recall_text / cache_get_semantic_text call on the handle uses it.

import mnemo
from sentence_transformers import SentenceTransformer

model = SentenceTransformer("all-MiniLM-L6-v2")  # 384-dim, normalized

def embed(texts: list[str]) -> list[list[float]]:
    return model.encode(texts, normalize_embeddings=True).tolist()

db = mnemo.open("agent.mnemo", "pw", dimensions=384)
db.set_embedder(embed, name="all-MiniLM-L6-v2",
                dimensions=384, normalize=True)

db.remember_text("the user prefers tea", "semantic",
                 importance=0.8, agent_id="assistant")
hits = db.recall_text("how does the user like their drinks?", top_k=5)
for h in hits:
    print(h["score"], h["content"])

Or with OpenAI's hosted embeddings:

from openai import OpenAI
client = OpenAI()

def embed(texts: list[str]) -> list[list[float]]:
    r = client.embeddings.create(model="text-embedding-3-small", input=texts)
    return [item.embedding for item in r.data]

db = mnemo.open("agent.mnemo", "pw", dimensions=1536)
db.set_embedder(embed, name="text-embedding-3-small",
                dimensions=1536, normalize=False)

The identity (name, dimensions, normalize) is persisted on the scaffold manifest the first time you set_embedder. Reopening with a different embedder — say you swap MiniLM for BGE on the same file — raises RuntimeError: embedder identity mismatch: database manifest records embedder (name="all-MiniLM-L6-v2", …), but this handle was passed embedder (name="bge-large-en-v1.5", …). Mixing embedders silently corrupts recall quality — open the file with the original embedder, or create a fresh database for the new one.

Mnemo also now refuses any remember call with an all-zero vector, so a mis-wired embedder (returning [0.0] * dims) fails loudly on the first write instead of polluting recall silently.

License

Apache-2.0.

Metadata

Release files for mnemo-engine 0.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mnemo-engine 0.5.0
File Size Uploaded Provenance
mnemo_engine-0.5.0.tar.gz 212.2 kB With provenance Details

Built distributions (wheels)

Table of built distributions (wheels) for mnemo-engine 0.5.0
File
mnemo_engine-0.5.0-cp38-abi3-win_amd64.whl CPython 3.8 abi3 Windows x86-64 Details
mnemo_engine-0.5.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.8 abi3 Linux glibc 2.17+ x86-64 Details
mnemo_engine-0.5.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.8 abi3 Linux glibc 2.17+ ARM64 Details
mnemo_engine-0.5.0-cp38-abi3-macosx_11_0_arm64.whl CPython 3.8 abi3 macOS 11.0+ ARM64 Details
mnemo_engine-0.5.0-cp38-abi3-macosx_10_12_x86_64.whl CPython 3.8 abi3 macOS 10.12+ x86-64 Details

Total release size: 3.3 MB

Release files / mnemo_engine-0.5.0.tar.gz

Download URL mnemo_engine-0.5.0.tar.gz
Size 212.2 kB
Tags Source
SHA-256 checksum
How to use checksums
5f71bb81eeb3be39cc0821322f60226381527b0466b0b2fe19e073c3beb20742
BLAKE2b-256 checksum
How to use checksums
fdabeac28b84ad15f59f259ce4e63c6ad6066e10de83d4ad312e7e2d7213f38d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.

Transparency log

Release files / mnemo_engine-0.5.0-cp38-abi3-win_amd64.whl

Download URL mnemo_engine-0.5.0-cp38-abi3-win_amd64.whl
Size 537.8 kB
Tags CPython 3.8 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
565d13707f0783a82a9343e80975bc2edcf29582dc25a82fe09a2ea8bac3e0b1
BLAKE2b-256 checksum
How to use checksums
217771d7034116e5d352bd9076278e6658b44ef8cf9834495a6d5f0dde549b50
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.

Transparency log

Release files / mnemo_engine-0.5.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL mnemo_engine-0.5.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 654.9 kB
Tags CPython 3.8 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
1b220903f38cf00e2d4c3d3ce29b9fdec73514c09fef6fa9dd62675d382d97f7
BLAKE2b-256 checksum
How to use checksums
f854d91ef122ff72d7d1aca06aa270da586a4d115ee3850c6e495722b978a7e9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.

Transparency log

Release files / mnemo_engine-0.5.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL mnemo_engine-0.5.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 633.7 kB
Tags CPython 3.8 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
f01a218393565a7b2298de335db7da7be1f2b69395cab67ec2d5b7bc99ab5d92
BLAKE2b-256 checksum
How to use checksums
8cb2a2eec2fe7c966efd68b44d38ac92cca470b228348d865f1439cbad455858
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.

Transparency log

Release files / mnemo_engine-0.5.0-cp38-abi3-macosx_11_0_arm64.whl

Download URL mnemo_engine-0.5.0-cp38-abi3-macosx_11_0_arm64.whl
Size 603.0 kB
Tags CPython 3.8 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
691c8712b237a3b04bdafb2e79b1041cd3cdefe2b0c481bf5affd84250f80268
BLAKE2b-256 checksum
How to use checksums
2dca8e964fb42815e52ab5838ccb6f3c2618a7484f758a7c41457fe439a7f5b4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.

Transparency log

Release files / mnemo_engine-0.5.0-cp38-abi3-macosx_10_12_x86_64.whl

Download URL mnemo_engine-0.5.0-cp38-abi3-macosx_10_12_x86_64.whl
Size 632.0 kB
Tags CPython 3.8 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
e5cad895854de61fca2498a60380be507113b4a433e5065793f92ff155903410
BLAKE2b-256 checksum
How to use checksums
67b7e2a9341956b80c6df1acc05d2f4d28de58d85af4bebbead8590ed2fa5327
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.

Transparency log

Security for mnemo-engine 0.5.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 6 files in this release have been uploaded with provenance metadata.

Select from 6 files

Attestation for mnemo_engine-0.5.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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