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
Memory that actually forgets — a lifecycle-correct memory layer for AI agents
Quickstart · The Four Verbs · Proof · How it Works · Deep Dive
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
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 contradicts → superseded; 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 mechanicalforget()— 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 showssuperseded, the casual recordactive). 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()queriesWHERE 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 totombstoned; 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 — andlint()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 |
🗺️ Roadmap
- v1.x — additional
BaseStoreadapters (Postgres, Pinecone, Chroma), 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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file memzia-0.1.3.tar.gz.
File metadata
- Download URL: memzia-0.1.3.tar.gz
- Upload date:
- Size: 67.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dd14d68d65a96eeb2f52d0bfcd5c6db18fcbea56ca02ee09962c28de66543bfa
|
|
| MD5 |
26cc91fb6d96ae82071fec8fbd003bff
|
|
| BLAKE2b-256 |
f96111a57b831d0e2cb2f73208d0b7d95f3bf59a061ccec6bbb44d9c610a03ec
|
Provenance
The following attestation bundles were made for memzia-0.1.3.tar.gz:
Publisher:
publish.yml on ai-codesmith-solver/memzia
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
memzia-0.1.3.tar.gz -
Subject digest:
dd14d68d65a96eeb2f52d0bfcd5c6db18fcbea56ca02ee09962c28de66543bfa - Sigstore transparency entry: 2164326548
- Sigstore integration time:
-
Permalink:
ai-codesmith-solver/memzia@7cf9e001d5f64eb53a3dc01fb3b9b75475b97f65 -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/ai-codesmith-solver
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7cf9e001d5f64eb53a3dc01fb3b9b75475b97f65 -
Trigger Event:
push
-
Statement type:
File details
Details for the file memzia-0.1.3-py3-none-any.whl.
File metadata
- Download URL: memzia-0.1.3-py3-none-any.whl
- Upload date:
- Size: 35.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7da240ac0e162b2ef28179c96b272b1bf4708fb8aec1dd63646ded1aaa38782e
|
|
| MD5 |
6c6ec2466eab3dae189f4ce237ee2ec3
|
|
| BLAKE2b-256 |
fd8a1f8455931cd525018448067a1e8d9c26cc98d21384b2229e4ce9c2268a1c
|
Provenance
The following attestation bundles were made for memzia-0.1.3-py3-none-any.whl:
Publisher:
publish.yml on ai-codesmith-solver/memzia
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
memzia-0.1.3-py3-none-any.whl -
Subject digest:
7da240ac0e162b2ef28179c96b272b1bf4708fb8aec1dd63646ded1aaa38782e - Sigstore transparency entry: 2164326553
- Sigstore integration time:
-
Permalink:
ai-codesmith-solver/memzia@7cf9e001d5f64eb53a3dc01fb3b9b75475b97f65 -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/ai-codesmith-solver
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7cf9e001d5f64eb53a3dc01fb3b9b75475b97f65 -
Trigger Event:
push
-
Statement type: