Skip to main content

A self-contained, plug-and-play memory library for AI agents, built for lifecycle correctness: an updated or forgotten fact is guaranteed to stop influencing future output.

Project description

Memzia

Memory that actually forgets — a lifecycle-correct memory layer for AI agents

Quickstart · The Four Verbs · Proof · How it Works · Storage Backends · Deep Dive

MIT License Python 3.9-3.13 56 tests passing mypy strict Ruff


Memzia is a self-contained, plug-and-play memory library for AI agents. It exists to close one specific, validated gap: across a month of hands-on testing of five funded memory platforms (Zep, Mem0, Hindsight, Supermemory, Cognee), none could guarantee that a corrected or forgotten fact actually stops influencing future output. They store and retrieve well; they do not manage lifecycle.

Memzia's core bet is that lifecycle correctness — knowing when a fact is outdated, and guaranteeing that "forget" really means forget — is the product, not a feature bolted on afterward.

pip install memzia    # one OpenAI key, no other vendor account
Memzia architecture

The problem, in one table

This is the author's own hands-on testing — realistic multi-session scenarios, a consistent evaluation framework, and (for two tools) a forced retrieval probe that asks "what do you remember about X?" directly, rather than only observing a task's output.

Tool Update handling Delete / forget handling
Zep ❌ Accepted a correction, then reused the exact stale context in the next reply ❌ Accepted a forget request; old preference still surfaced in the next output
Mem0 ❌ Self-documented as "single-pass ADD-only… nothing is overwritten" ❌ Old preference retrieved and used after an explicit forget
Hindsight ⚠️ Best of the five — stale memory visible but not dominant ⚠️ Same pattern: visible but not dominant
Supermemory ❌ A "0 memory used" tag contradicted a panel showing 71–88% relevance matches ❌ A forced probe proved the "forgotten" preference was still retrievable
Cognee ❌ Correction acknowledged, then stale content resurfaced ❌ Most conclusively — a forced probe returned the original preference with an evidence chunk tracing to the source

The pattern is not an edge case in any one product. It is the default behavior of the category on the two dimensions that matter most for trust. Memzia is built specifically against these failure modes.


🚀 Quickstart

from memzia import Memzia

memzia = Memzia(llm_api_key="sk-...")   # or set OPENAI_API_KEY

# Store a preference…
memzia.remember("I prefer a formal tone in emails.", user_id="ayush")

# …then correct it. Memzia classifies this as a contradiction and
# supersedes the old fact in the SAME call.
memzia.remember("Actually, keep my emails casual now.", user_id="ayush")

# recall() returns ONLY the current, active memory.
memzia.recall("what tone do I prefer?", user_id="ayush")
# -> [{'content': 'Actually, keep my emails casual now.', ...}]

# forget() is a permanent, structural tombstone — no LLM decides whether to comply.
memzia.forget(user_id="ayush", query="email tone preference")

# The forced retrieval probe: it is simply gone.
memzia.recall("what tone do I prefer?", user_id="ayush")
# -> []

That's the whole point: the superseded formal-tone fact and the forgotten casual-tone fact are not retrievable — not merely down-ranked.


🧩 The four verbs

Verb What it guarantees
remember() Every new fact is classified against existing active memories (contradicts / updates / extends / unrelated) before being stored. A contradicting/updating fact marks the prior record superseded in the same call.
recall() Retrieval filters WHERE status = 'active' in the SQL itself, then filters again in Python — two independent layers. Superseded and tombstoned records never appear.
improve() Batch enrichment: merges redundant facts and derives higher-level patterns. Reads only active memory (tombstoned records are structurally unreachable) and refuses to create a derived memory from fewer than 2 source memories — the guard against generic/noisy storage.
forget() A direct, permanent status mutation to tombstoned, enforced by the same query filter recall() uses. No LLM sits in this path to "decide" whether to comply.

Plus a diagnostic companion — lint() — a read-only self-audit that reports orphaned supersession chains, missed stale pairs, and (the hard one) any derived memory that still references a tombstoned source. Memzia holds itself to the same scrutiny it applied to the five tools above.


✅ Proof: the benchmark

Memzia ships a reproducible benchmark that replays the exact research scenarios against itself, including the forced retrieval probes. Run it yourself:

OPENAI_API_KEY=sk-...  python benchmark/run_benchmark.py

Latest real run — all four scenarios passed:

# Scenario Result The decisive check
1 Personal Work Brain ✅ PASS Style preference captured; the internal update applies the casual/bullet/emoji style, while the formal email is kept free of it
2 Client Relationship (update + scope) ✅ PASS The SSO fact was contradictssuperseded; a login probe returns only email/password. A second client's recall never sees ACME
3 Team Handoff (direction change + scope) ✅ PASS Original direction superseded; the pivot is the only active fact. An unrelated project's recall stays isolated
4 Delete / Forget (forced probe) ✅ PASS After forget(), the probe "what writing style preference do you have on file for me?" returns [] — and the natural task never uses it

Verbatim from the run:

=== [PASS] 1. Personal Work Brain Memory ===
  - compose internal team update -> '### Team Update: Weekly Progress 🚀
      - Project Alpha: Completed initial testing phase! ✅ ...'
  - compose formal client email  -> 'Subject: Update on Project Timeline
      Dear [Client's Name], I hope this message finds you well. ...'   (no emoji, formal register)

=== [PASS] 2. Client Relationship Memory (update + scope isolation) ===
  - remember (ACME): moved off SSO -> email/password (relationship=contradicts, superseded=['15707919...'])
  - PROBE recall (ACME login) -> ['Update: ACME Corp has fully moved off SSO. They now log in with email and password only.']
  - scope-isolation recall (Beta for 'ACME') -> ['Beta LLC uses a magic-link email login.']

=== [PASS] 4. Delete/Forget Control (forced retrieval probe) ===
  - forget -> {'status': 'tombstoned', 'reason': 'user requested'}
  - PROBE recall after forget -> []   (and the natural task produced a real update that never uses the forgotten "formal" preference)

These guarantees are also pinned by the unit suite — see tests/test_forget.py (the forced-probe test), tests/test_recall.py (a test that fails if the SQL status='active' clause is removed), and tests/test_lint.py (tombstone-violation detection). The whole suite runs with no API key present — proving it is fully mocked and deterministic.

Two separate forget claims — both backed by evidence

Most "memory" tools are judged only on whether they store and retrieve. Memzia is judged on forget, and there are two distinct things to prove — so we prove and report them separately:

1. The mechanical guarantee (deterministic, offline): once forget() runs, the record is permanently unretrievable. Pinned by tests/test_forget.py and benchmark Scenario 4 above.

2. Conversational recognition (live, LLM-in-the-loop): given remember/recall/forget as real tools, does an LLM correctly recognise a natural forget request and target the right memory — without a false-positive delete on a lookalike question? This is the layer most tools never test. Run it yourself:

OPENAI_API_KEY=sk-...  python benchmark/forget_recognition_test.py

It works via forget_by_description(): recall the active candidates, let the LLM pick which record is meant (or decline), then call the mechanical, LLM-free forget(memory_id=…). The LLM only chooses the target — it never decides whether to comply — and it fails safe: an ambiguous request forgets nothing rather than the wrong thing.

Reliability — each phrasing run 10× against a user seeded with three decoy memories plus one target:

Natural message Expected Result
"Forget that formal tone preference." forget 10/10 forgot the right memory
"Actually, ignore what I said earlier about how updates should sound." forget 10/10 forgot the right memory
"Can you tell me what tone preference you have on file for me?" not forget 10/10 recalled, zero false-positive deletes
"We're not doing that anymore — you can drop that…" (no antecedent) forget 0/10 — asks "which memory?" every time (see limitation below)

Two harder scenarios:

  • Competing candidates — two equally-plausible memories ("formal corporate tone" vs "concise bullet-point summaries") and a deliberately ambiguous "forget that preference about how things should sound." All 5/5 runs declined to guess: the disambiguation step judged the request too ambiguous to map to a single record, forgot nothing, and the assistant asked which one to forget. No run deleted anything. (Traced: the LLM did pass a real query; the ambiguity gate lives in forget_by_description, which returns "no confident match" and never calls the mechanical forget() — so the "unconditional once the target is found" guarantee is untouched.)
  • Forget + remember in one turn — "Stop using formal tone, use casual instead." 5/5 both halves correct: the model stores the new preference and Memzia's remember() automatically supersedes the contradicted old one (verified in the ledger — the formal record shows superseded, the casual record active). The correct "replace" semantics fall out of the update path, no explicit forget needed.

Honest limitation (documented, not hidden): a fully referent-free forget ("drop that," with nothing in the message identifying what "that" is) does not trigger a one-shot forget — the assistant asks a clarifying question instead (10/10). That's safe (it never guesses-and-wrong-deletes), but if your product needs silent one-shot forgets on maximally-vague phrasing, that gap is real today. Every other case tested — direct, vague-but-grounded, competing, and replace — is handled correctly, and every failure mode is safe: clarify, decline, or supersede — never "confidently keep or delete the wrong memory," which is exactly where all five researched tools failed. Claim 1 (mechanical) is deterministic; claim 2 (conversational) is a live LLM measurement, reported as such.


🔍 How it works

Every memory record lives in a ledger with exactly one status. The lifecycle is the product:

stateDiagram-v2
    [*] --> active: remember()
    active --> superseded: contradicts / updates (remember)<br/>or merged (improve)
    active --> tombstoned: forget()
    superseded --> [*]: excluded from recall
    tombstoned --> [*]: excluded from recall — forever
  • recall() queries WHERE status = 'active' at the storage layer, then re-filters in Python. Both layers exist on purpose — this is the exact boundary where every competitor leaked.
  • forget() never calls an LLM. It flips status to tombstoned; the same filter that powers recall makes the record permanently invisible.
  • improve() only ever sees active memory, so a forgotten fact can never be re-derived into a new one — and lint() will flag it as a hard failure if it somehow were.

Inspect any user's full ledger — every status, what superseded what, and why:

$ memzia inspect --user ayush
ID        STATUS      SUPERSEDED_BY  CREATED                              CONTENT
--------------------------------------------------------------------------------
a1b2c3d4  superseded  e5f6a7b8       2026-07-11T08:54:17+00:00            I prefer a formal tone in emails.
e5f6a7b8  tombstoned  -              2026-07-11T08:54:18+00:00            Actually, keep my emails casual now.
          reason: user requested

Read the full design — every folder, module, and data flow explained in plain language — in the Project Report, the Architecture doc, and the API Reference.


🔗 LangChain integration

Memzia ships a drop-in BaseMemory adapter. Core pip install memzia does not require LangChain — install the extra only if you need it:

pip install "memzia[langchain]"
from memzia import Memzia
from memzia.integrations.langchain_memory import MemziaMemory

memory = MemziaMemory(memzia=Memzia(llm_api_key="sk-..."), user_id="ayush")

# Each turn's human input is stored via remember() (so corrections supersede);
# prior context is injected via recall() (active memory only — a forgotten fact
# is never re-injected into the prompt).
memory.save_context({"input": "I use SSO to log in."}, {"output": "noted"})
memory.save_context({"input": "I now use email and password."}, {"output": "updated"})
memory.load_memory_variables({"input": "how do I log in?"})
# -> {'history': 'I now use email and password.'}   # the SSO fact is gone

A runnable version lives in examples/langchain_example.py.


🖥️ CLI

memzia remember "I'm allergic to peanuts." --user ayush
memzia recall   "any allergies?"           --user ayush
memzia improve  --user ayush
memzia forget   --user ayush --query "peanut allergy"
memzia inspect  --user ayush     # every record, every status — the observability surface
memzia lint     --user ayush     # read-only self-audit (non-zero exit on a tombstone violation)

📊 Where Memzia sits

The full 16-attribute framework (API/SDK, dashboard, hosting, memory types, extraction, retrieval, update handling, delete support, scoping, metadata, observability, integrations, privacy, retention, latency, pricing) is applied to Memzia below. For the five competitor tools, the two decisive, verified dimensions from the research — update and forget handling — are reproduced above; the remaining attributes come from that same hands-on testing and are the author's assessment, not an independent audit.

Attribute Memzia
API / SDK Python API + CLI + LangChain adapter
Inspection UI memzia inspect + memzia lint (CLI observability surface)
Hosting Self-contained / embedded (v1)
Memory extraction LLM classification on every write
Memory retrieval Vector similarity over active-only records
Update handling ✅ Classify-then-supersede, in the same call
Delete / forget ✅ Structural tombstone; excluded at the SQL level
Scoping user_id + optional session_id
Observability Full ledger state, supersession chains, tombstone reasons
Integrations LangChain BaseMemory; framework-free helper
Privacy Local SQLite; your OpenAI key never stored on disk
Latency In-process; brute-force numpy vector search (v1 scale)
Pricing MIT-licensed, free; you pay only your own OpenAI usage

🗄️ Storage backends

Memzia ships two BaseStore implementations. Swapping backends is purely a constructor argument — nothing else in your code changes, because both honor the exact same active-status SQL invariant.

SQLiteStore (default) PostgresStore
Setup Zero — a local file A Postgres database with the pgvector extension
Concurrency Single-writer (fine for embedded/single-process use) Real connection pooling — safe for multiple threads/processes
Install Included in core pip install "memzia[postgres]"
from memzia import Memzia
from memzia.store.postgres_store import PostgresStore

store = PostgresStore(
    dsn="postgresql://user:pass@host/dbname?sslmode=require",
    embedding_dim=1536,  # must match your embedder's output dimension
)
memzia = Memzia(llm_api_key="sk-...", store=store)

PostgresStore creates its schema (and the vector extension, if it has permission to) automatically on first connect. If you'd rather enable pgvector yourself first: CREATE EXTENSION IF NOT EXISTS vector;

The CLI works with either backend too — --db accepts a Postgres DSN directly:

memzia --db "postgresql://user:pass@host/dbname" inspect --user ayush
memzia --db "postgresql://user:pass@host/dbname" lint --user ayush

What's actually verified for PostgresStore — not just claimed

72 tests pass, in two groups, each proving something specific:

collected 72 items

tests\test_cli.py .......                                                [  9%]
tests\test_forget.py ........                                            [ 20%]
tests\test_improve.py .....                                              [ 27%]
tests\test_langchain_integration.py ....                                 [ 33%]
tests\test_lint.py ......                                                [ 41%]
tests\test_postgres_store.py ........                                    [ 52%]
tests\test_postgres_store_live.py ................                       [ 75%]
tests\test_recall.py ....                                                [ 80%]
tests\test_remember.py .....                                             [ 87%]
tests\test_store.py .........                                            [100%]

============================= 72 passed in 43.77s =============================

56 tests run offline, every time, no external service needed (this is the number the badge above reflects) — including 8 for PostgresStore's row/type conversion logic and the "clear error if the extra isn't installed" behavior.

The remaining 16 run live against a real Postgres + pgvector database (skipped automatically, not failed, when none is configured — same pattern the benchmark suite uses for the OpenAI key). What they actually prove, one by one:

Claim How it's checked
The storage contract is identical to SQLiteStore's Same 9 tests as tests/test_store.py, run against real Postgres instead
The whole engine works on this backend, not just raw storage remember → recall → forget → forget_by_description → improve → lint run through the actual Memzia class, live
Concurrent access — the reason this backend exists — actually works 20 threads write through the same connection pool at once; all 20 rows verified persisted, none lost or corrupted
A misconfiguration fails loudly, not silently An embedding of the wrong dimension is rejected by a real Postgres error, not silently stored wrong
The CLI, not just the Python API, works against Postgres memzia --db postgresql://... inspect/lint run for real and checked against the database

Run it yourself against your own database:

# add MEMZIA_TEST_DATABASE_URL=postgresql://... to .env, then:
pytest tests/test_postgres_store_live.py -v

Known, disclosed gap: this live suite isn't wired into CI yet (no database secret is configured there), so it currently runs locally/on-demand rather than on every push — noted in docs/PRODUCTION_READINESS.md rather than left unsaid.

🗺️ Roadmap

  • v0.2.0 (shipped)PostgresStore, a BaseStore adapter backed by PostgreSQL + pgvector, for concurrent/multi-process deployments SQLite can't offer. See Storage backends below.
  • v1.x — additional BaseStore adapters (Pinecone, Chroma, Qdrant), driven by real usage rather than speculative scale; approximate-nearest-neighbor indexing.
  • v2 — multi-tenancy, auth, a hosted option, and expanded language SDKs.

See CHANGELOG.md for release history.


🤝 Contributing

Contributions are welcome — see CONTRIBUTING.md. The non-negotiables: the active-status SQL invariant, an LLM-free forget() path, and never weakening a test to make it pass. Please also read the Code of Conduct and Security Policy.

📄 License

MIT © 2026 Ayush Ghosh

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

memzia-0.2.0.tar.gz (78.9 kB view details)

Uploaded Source

Built Distribution

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

memzia-0.2.0-py3-none-any.whl (40.9 kB view details)

Uploaded Python 3

File details

Details for the file memzia-0.2.0.tar.gz.

File metadata

  • Download URL: memzia-0.2.0.tar.gz
  • Upload date:
  • Size: 78.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for memzia-0.2.0.tar.gz
Algorithm Hash digest
SHA256 0f421a87e4ad7921bd0fcf4f070d1d4b103135b6a8b8be45708176b5e953720f
MD5 f4c535453a159ec9a01f0875e51b4d1b
BLAKE2b-256 5249061e578d22d487239aa8b5d8eac5b2267137cdbdf6accb82bf7fc72c9281

See more details on using hashes here.

Provenance

The following attestation bundles were made for memzia-0.2.0.tar.gz:

Publisher: publish.yml on ai-codesmith-solver/memzia

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file memzia-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: memzia-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 40.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for memzia-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4c081eafd78d8a01b97d7eb1113d4e4e2e768172b3668381a7524814b3dc1b6e
MD5 c6a6c1e71c22212fb1e6dad09d304a96
BLAKE2b-256 8dba35bd0ef1e924364782abdee7dfbbd3943b319ff3e7a5bf259a597b628ad5

See more details on using hashes here.

Provenance

The following attestation bundles were made for memzia-0.2.0-py3-none-any.whl:

Publisher: publish.yml on ai-codesmith-solver/memzia

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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