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_max_snapshots(max) Override the snapshot-manifest retention cap (default 256; 0 disables)
stats() Summary statistics
export_encrypted(dest) Copy the (already-encrypted) file elsewhere
len(db) Live memory count

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.

License

Apache-2.0.

Metadata

Release files for mnemo-engine 0.4.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.4.0
File Size Uploaded Provenance
mnemo_engine-0.4.0.tar.gz 161.0 kB Without provenance Details

Built distributions (wheels)

Table of built distributions (wheels) for mnemo-engine 0.4.0
File
mnemo_engine-0.4.0-cp38-abi3-win_amd64.whl CPython 3.8 abi3 Windows x86-64 Details
mnemo_engine-0.4.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.4.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.8 abi3 Linux glibc 2.17+ ARM64 Details
mnemo_engine-0.4.0-cp38-abi3-macosx_11_0_arm64.whl CPython 3.8 abi3 macOS 11.0+ ARM64 Details
mnemo_engine-0.4.0-cp38-abi3-macosx_10_12_x86_64.whl CPython 3.8 abi3 macOS 10.12+ x86-64 Details

Total release size: 3.0 MB

Release files / mnemo_engine-0.4.0.tar.gz

Download URL mnemo_engine-0.4.0.tar.gz
Size 161.0 kB
Tags Source
SHA-256 checksum
How to use checksums
2fbccb4c25fde28b5efb52975ccc5b297c7ac4b9cf4e25c4d8bd28b7b97d627a
BLAKE2b-256 checksum
How to use checksums
18e582cbd9accba0d7400e8c35aaa094108515e295b4f18962bc2c8d709e650d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via maturin/1.15.0

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

Download URL mnemo_engine-0.4.0-cp38-abi3-win_amd64.whl
Size 496.0 kB
Tags CPython 3.8 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
f75497996656fcd2d88545c0ba76eb59dfcac910ab0bc0ea9d262982f1aef9bc
BLAKE2b-256 checksum
How to use checksums
f859ea9300ed4e426298581e2be91c76db4a4ee6de6f64510dda0f862a5ea082
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via maturin/1.15.0

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

Download URL mnemo_engine-0.4.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 619.9 kB
Tags CPython 3.8 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
31d4beb3fddb141414fa73cfa869ee32f2b28ad8f6cb5c7c26e7813b64d70695
BLAKE2b-256 checksum
How to use checksums
d10de7d99ea75157fb86887bdb6c3f8332a90ab09608b471873e66e1c7b8b2b9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via maturin/1.15.0

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

Download URL mnemo_engine-0.4.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 601.6 kB
Tags CPython 3.8 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
f0f2b0fb2a64a25dbc7c4bcd43b8ee419e09461e096d18ec921b1cd4afa620b4
BLAKE2b-256 checksum
How to use checksums
7085bdb566f09ab56b5c4162d935def8e829c2f05c665e4d60570cb935f82233
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via maturin/1.15.0

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

Download URL mnemo_engine-0.4.0-cp38-abi3-macosx_11_0_arm64.whl
Size 566.9 kB
Tags CPython 3.8 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
5c888070455994308a2a577977e1916798f5b8e3b680f7ca3d135bf5aecfe13f
BLAKE2b-256 checksum
How to use checksums
72fe63f4a368f63be3d1650992fa2fbe53e182059ca07620b775b58fdec437dc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via maturin/1.15.0

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

Download URL mnemo_engine-0.4.0-cp38-abi3-macosx_10_12_x86_64.whl
Size 592.9 kB
Tags CPython 3.8 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
07c0050ac4f2b78ce1f732632cc8a4c45b7da65ad33f4a88a59daae06afe4871
BLAKE2b-256 checksum
How to use checksums
e4ac5a1d769d217ae456659cdaeb9e3be7d10d7687d37ba291c43ca6f33c3262
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via maturin/1.15.0

Security for mnemo-engine 0.4.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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

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