Metadata-Version: 2.4
Name: memzia
Version: 0.2.0
Summary: 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-URL: Homepage, https://github.com/ai-codesmith-solver/memzia
Project-URL: Repository, https://github.com/ai-codesmith-solver/memzia
Project-URL: Issues, https://github.com/ai-codesmith-solver/memzia/issues
Author-email: Ayush Ghosh <ayushghosh605@gmail.com>
License: MIT
License-File: LICENSE
Keywords: agents,ai,langchain,llm,memory,openai,rag
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: numpy<3.0,>=1.24
Requires-Dist: openai<2.0,>=1.0
Provides-Extra: dev
Requires-Dist: mypy<2.0,>=1.8; extra == 'dev'
Requires-Dist: pre-commit<5.0,>=3.5; extra == 'dev'
Requires-Dist: pytest<9.0,>=8.0; extra == 'dev'
Requires-Dist: ruff<1.0,>=0.5; extra == 'dev'
Provides-Extra: langchain
Requires-Dist: langchain-core<1.0,>=0.1; extra == 'langchain'
Provides-Extra: postgres
Requires-Dist: pgvector<1.0,>=0.3; extra == 'postgres'
Requires-Dist: psycopg[binary,pool]<4.0,>=3.1; extra == 'postgres'
Description-Content-Type: text/markdown

<div align="center">

<img src="https://raw.githubusercontent.com/ai-codesmith-solver/memzia/main/docs/images/logo.svg" alt="Memzia" width="420"/>

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

<p>
  <a href="#-quickstart">Quickstart</a> ·
  <a href="#-the-four-verbs">The Four Verbs</a> ·
  <a href="#-proof-the-benchmark">Proof</a> ·
  <a href="#-how-it-works">How it Works</a> ·
  <a href="#-storage-backends">Storage Backends</a> ·
  <a href="docs/PROJECT_REPORT.md">Deep Dive</a>
</p>

<p>
  <img src="https://img.shields.io/badge/license-MIT-green.svg" alt="MIT License"/>
  <img src="https://img.shields.io/badge/python-3.9%20%E2%80%93%203.13-blue.svg" alt="Python 3.9-3.13"/>
  <img src="https://img.shields.io/badge/tests-56%20passing-brightgreen.svg" alt="56 tests passing"/>
  <img src="https://img.shields.io/badge/typed-mypy%20strict-blue.svg" alt="mypy strict"/>
  <img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Ruff"/>
</p>

</div>

---

**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.**

```bash
pip install memzia    # one OpenAI key, no other vendor account
```

<div align="center">
<img src="https://raw.githubusercontent.com/ai-codesmith-solver/memzia/main/docs/images/architecture.svg" alt="Memzia architecture" width="900"/>
</div>

---

## 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

```python
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:

```bash
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:

```text
=== [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`](tests/test_forget.py) (the forced-probe test), [`tests/test_recall.py`](tests/test_recall.py) (a test that fails if the SQL `status='active'` clause is removed), and [`tests/test_lint.py`](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:

```bash
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:

```mermaid
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:

```bash
$ 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](docs/PROJECT_REPORT.md)**, the **[Architecture doc](docs/architecture.md)**, and the **[API Reference](docs/api-reference.md)**.

---

## 🔗 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:

```bash
pip install "memzia[langchain]"
```

```python
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`](examples/langchain_example.py).

---

## 🖥️ CLI

```bash
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]"` |

```python
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:

```bash
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:

```text
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:

```bash
# 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`](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](#-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`](CHANGELOG.md) for release history.

---

## 🤝 Contributing

Contributions are welcome — see [`CONTRIBUTING.md`](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](CODE_OF_CONDUCT.md) and [Security Policy](SECURITY.md).

## 📄 License

[MIT](LICENSE) © 2026 Ayush Ghosh
