mnemo
by Nas01010101
README.md
<div align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="docs/brand/banner-dark.svg">
<img src="docs/brand/banner-light.svg" alt="Tenet, bi-temporal belief memory for agents: temporal correctness without a graph database" width="820">
</picture>
<h3>
<a href="https://nas01010101.github.io/tenet/">🌐 Website</a>
·
<a href="paper/tenet.pdf">📄 Paper (PDF)</a>
</h3>
[](https://github.com/Nas01010101/tenet/actions/workflows/test.yml)
[](LICENSE)
[](#quickstart)
[](https://github.com/Nas01010101/tenet/releases/latest)
[](https://qwencloud-hackathon.devpost.com)
[](src/tenet/mcp_server.py)
*Agent memory that stays true as your life changes: a self-consistent, bi-temporal belief
state instead of retrieval over a log, with zero LLM calls on the read path.*
Most agent memory is retrieval over a growing log of turns, and that log silently breaks
the moment a fact changes. Tenet gives every fact two clocks, event time and transaction
time, so a new value **supersedes** the old one instead of sitting beside it: current vs.
superseded, always queryable, always human-readable.
**English** · [简体中文](README.zh-CN.md)
</div>
---
```bash
pip install git+https://github.com/Nas01010101/tenet.git
```
```python
from tenet import Tenet
mem = Tenet()
mem.ingest("I live in Boston") # needs an LLM key (distills the raw message)
mem.ingest("I moved to Seattle") # supersedes: Boston kept in history
mem.recall("where do I live?") # → [Seattle] (current beliefs, no LLM call)
mem.recall("where do I live?", as_of=t0) # → [Boston] (time-travel, no LLM call)
mem.navigate("where do I live and work?") # → adaptive multi-hop recall, no LLM call
```
Everything except `ingest` is **LLM-free** (embeddings + closed-form math, low-milliseconds;
fully offline with `EMBED_PROVIDER=local`). Only `ingest` needs a model key, because turning
free-form text into atomic facts is the one judgment call that needs one.
The [60-second zero-key demo](#quickstart) shows exactly where that line sits.
## Tenet vs Zep · Mem0 · Letta
The 2026 agent-memory field splits by job: **Mem0** for per-user personalization, **Zep/Graphiti**
for facts that change over time, **Letta** for self-managing long-horizon agents. Tenet targets
Zep's job (*temporal correctness when facts change*) but removes its cost of entry.
| | **Tenet** | Zep / Graphiti | Mem0 | Letta |
|---|---|---|---|---|
| Facts that change over time | ✅ bi-temporal supersession | ✅ bi-temporal graph | ❌ create-ts only | agent-managed |
| **Infra to run it** | **`pip install`: sqlite + numpy** | graph DB (Neo4j / FalkorDB) | vector DB | agent server + Postgres |
| Read path cost | **no LLM call** | no LLM call | no LLM call | an LLM call per op |
| **Read what it knows?** | ✅ **plain belief state** (`get_all()`) | ❌ graph nodes | ❌ opaque vectors | ❌ state blocks |
| Drop-in API | ✅ **Mem0-compatible** (`add`/`search`/`get_all`/`delete`) | graph API | `add`/`search`/… | full runtime |
| Time-travel (`as_of`) | ✅ | ✅ | ❌ | ❌ |
**The one-liner:** *Zep's temporal correctness, Mem0's drop-in API, and a belief state you can
actually open and read, with zero infrastructure.* Full honest matrix + comparability caveats:
[`docs/COMPARISON.md`](docs/COMPARISON.md).
## What you'd build on it
The general primitive for **any state that must be *currently true* while its history stays
auditable**. Every pattern below runs on a surface that already ships in this repo:
| You're building | The failure Tenet removes | Shipped surface |
|---|---|---|
| An agent in Claude Desktop / an IDE / any MCP client | memory that silently goes stale as facts change | MCP server (`learn`/`recall`/`time_travel`/`doubts`) |
| A LangGraph / LlamaIndex / LangChain / Mem0-style app | framework memory that keeps *both* the old and new value | `BaseStore` · `BaseMemoryBlock` · `TenetMemory` adapters, Mem0-compatible API |
| A support / CRM / ops bot over account state | answering from a superseded plan, address, or entitlement | keyed supersession + the churn result (RAG collapses 100→50%, Tenet holds 100%) |
| A multi-agent system with shared state | concurrent writers trampling each other's facts | the [Majalis](https://github.com/Nas01010101/majalis) pattern: supersession as the write-arbitration rule |
| Anything audited or regulated | "what did we believe when we decided X?" is unanswerable | provenance + `recall(as_of=…)` + `tenet timeline`/`export` |
And it scales like a library, not a service: reads are **~9–12 ms flat from 1k to 100k facts**
on a laptop (one SQLite file), so "add memory" never becomes "operate a database cluster."
> **Reproducibility is the pitch.** Independent 2026 audits found the field's headline numbers
> don't survive reproduction (Mem0's 93.4% on LongMemEval reproduces at
> [73.8%](docs/COMPARISON.md#-frontier-reality-check--the-2026-reproduction-crisis-verified-2026-07-14);
> LoCoMo's answer key is 6.4% wrong). Tenet reports **every** number with a Wilson 95% CI, ships
> **five flags default-OFF because we measured them as no-benefit**, and **falsified its own churn
> claim in public** before fixing it. Built **100% on Qwen Cloud**. Every result reproduces from
> one command.
## Results at a glance
| benchmark | metric | Tenet | comparison | source |
|---|---|---:|---:|---|
| MemoryAgentBench FactConsolidation (arXiv:2507.05257), single-hop | SubEM, pooled 6K–262K | **97.0** [94.8, 98.3] | > published gpt-4o-tier 94.8 · mini-tier SOTA 78.0 · naive-RAG 47.8 | [`BENCHMARK.md` §6](docs/BENCHMARK.md#6-mab-factconsolidation--the-standardized-supersession-benchmark-scriptsbench_factconpy) |
| MAB FactConsolidation, multi-hop | SubEM, pooled 6K–262K | **45.8** [40.9, 50.6] | **1.5×** published mini SOTA 30.2 (CI excludes) · every published memory system ≤7 | [`BENCHMARK.md` §6](docs/BENCHMARK.md#6-mab-factconsolidation--the-standardized-supersession-benchmark-scriptsbench_factconpy) |
| MAB Accurate-Retrieval | avg. official metric | **59.3** (2nd of all published systems) | Mem0 32.6 · Zep 37.5 | [`BENCHMARK.md` §7](docs/BENCHMARK.md#7-mab-accurate-retrieval--the-second-mab-competency-scriptsbench_mab_arpy) |
| MAB Test-Time Learning (5 ICL cells, n=500) | official substr-EM, avg | **77.2** [73.3, 80.7] (local 7B reader, $0) | > BM25 75.4 · MemGPT 67.6 · Zep 62.8 · Mem0 32.4 | [`BENCHMARK.md` §16](docs/BENCHMARK.md#16-mab-test-time-learning--the-third-mab-competency-scriptsbench_mab_ttlpy) |
| Knowledge-churn horizon (fact updated 2→12×) | current-value accuracy | **100%** throughout | naive-RAG collapses 100%→50% | [`BENCHMARK.md` §3](docs/BENCHMARK.md#3-long-horizon-knowledge-churn--where-memory-structurally-wins-scriptsbench_horizonpy) |
| LongMemEval_S (n=100, `qwen3.7-plus` reader) | QA accuracy | **81.0%** | ≥ matched RAG 79.0% · 100% recall@10 · 98.5% less context than full | [`BENCHMARK.md` §1–2](docs/BENCHMARK.md#1-retrieval-recall--longmemeval_s-scriptslme_recallpy) |
| Head-to-head vs **ReMe** (Alibaba's memory framework), LME_S n=100 | QA acc, same reader/judge | **67.0%** [57.3, 75.4] | ReMe 34.0% · matched RAG 64.0% · McNemar p≈2×10⁻⁶ | [`reme_h2h_results.json`](docs/reme_h2h_results.json) |
| Local LoRA distiller (offline, zero-cloud) | key-consistency, decontaminated | **0.775** | cloud reference (`qwen3.7-plus`) 0.707 | [`BENCHMARK.md` §10](docs/BENCHMARK.md#10-local-distiller-zero-cloud-verdict) |
Honest weak spots (multi-session synthesis, multi-hop chaining) are reported, not hidden.
Full tables, protocol notes (including the 2026-07-19 ingestion-keyer fix our own miss-file
audit exposed), and every reproduction command: [`docs/BENCHMARK.md`](docs/BENCHMARK.md).
## Memory reads shouldn't cost an LLM call
Most memory systems architect the *read* path around an LLM in the loop: a rerank call, a
synthesis pass, an agent deciding what to fetch next. **Tenet's bet is the opposite:** the one
judgment call (distilling a message into keyed facts) happens once, at **write time**; every
read is pure vector similarity + closed-form math. Supersession itself is deterministic
bi-temporal bookkeeping; no model is in that loop either.
| system | read/retrieval latency | LLM in read path | infra to run |
|---|---:|:---:|---|
| **Tenet** | **~11 ms** (@100k facts, flat) | **no** | none: sqlite + numpy |
| Zep / Graphiti | ~150–300 ms (graph search) | no | graph DB (Neo4j / FalkorDB) |
| Mem0 | ~1.44 s p95 (base) | no | vector DB |
| Letta | model-dependent (an LLM call per op) | yes | agent server + Postgres |
<sub>Flat ~9–12 ms from 1k to 100k facts ([`docs/SCALE.md`](docs/SCALE.md)). Latency scopes
differ across systems; competitor figures are each project's own published retrieval latency.
The point isn't a race: temporal correctness here costs no graph database and no inference call.</sub>
<div align="center">
<img src="docs/brand/demo.gif" alt="Tenet assistant staying correct as facts change: supersession, time-travel, forgetting" width="740">
<sub>Real recorded session: facts change, the belief state supersedes them, time-travel recalls what was true before, and the read path never calls an LLM.</sub>
</div>
## The failure mode nobody benchmarks
<div align="center">

**As one templated fact is updated 2→12 times, RAG-memory falls 100%→50%. Tenet holds 100%.**
<sub>The single-attribute churn primitive (`bench_horizon`), pre-registered to favor Tenet. Under harder
*paraphrased* churn ([ChurnBench §9](docs/BENCHMARK.md#9-churnbench--parametric-high-churn-stress-test-measured-2026-07-10)),
the honest picture: read-time fixes lift Tenet's half-life <2→32; it ties an idealized delete-arm but
beats the real `mem0ai` package. Falsification and fix reported in full.</sub>
</div>
## Why it's different
| | retrieval memory (RAG) | **Tenet** |
|---|---|---|
| abstraction | document index of turns | **bi-temporal belief state** |
| a changed fact | two similar passages | **superseded** (bi-temporal, history kept) |
| stale evidence | retrieved forever | **retired** (belief–evidence consistency) |
| write policy | store everything | **surprise-gated** (predictive coding) |
| forgetting | none (grows forever) | salience-decay sweep |
| fact drift | unmodeled | **staleness hints**: learned P(still-valid) per attribute, `tenet doubts` |
| queryable across time | no | **time-travel** (`recall(as_of=t)`) |
| multi-hop bridging | fixed-depth *k*, or none | **adaptive `navigate()`**: LLM-free, gated by relevance gain |
| read path | n/a | **no LLM call** |
## Quickstart
### 1. One command, no API key
```bash
git clone https://github.com/Nas01010101/tenet && cd tenet
pip install -e ".[local]" # bge-small embedder, CPU; offline after install
python examples/00_zero_key_demo.py # supersession + time-travel + doubts, zero LLM calls
```
Walks the entire LLM-free read path end to end against a pre-formed fact ledger. The one thing
it can't show is `ingest()` turning conversation into facts; that's the one call that needs a
model (next step). First-ever install downloads ~1 GB of wheels; every run after is offline.
### 2. The full agent (needs an API key)
```bash
cp .env.example .env && chmod 600 .env # add DASHSCOPE_API_KEY (Qwen Cloud)
pip install -e ".[all]" # base + api/mcp/oss/local/cli/langgraph extras
python scripts/smoke_test.py # verify connectivity
uvicorn tenet.api:app --host 0.0.0.0 --port 8000 # HTTP API incl. POST /chat
python -m tenet.mcp_server # or the MCP server (learn/recall/navigate/forget/stats)
```
No key yet? `tenet recall` / `navigate` / `stats` / `doubts` / `timeline` / `export` work fully
offline with `EMBED_PROVIDER=local`. `tenet timeline --all` is the fastest way to *see* the
bi-temporal chain: current value highlighted, retired values dimmed. Default DB:
`data/tenet.db` (override with `TENET_DB_PATH`; falls back to `~/.tenet/tenet.db`).
**Works with:** any MCP client ([Claude Desktop](examples/03_mcp_client.md), IDEs) ·
[LangChain](examples/04_langchain_memory.py) · [LangGraph](examples/05_langgraph_store.py) ·
[LlamaIndex](examples/06_llamaindex_memory.py) · plain HTTP (`tenet.api:app`, `POST /chat`).
All examples: [`examples/`](examples/).
### LangGraph `BaseStore` adapter
Tenet drops in as a LangGraph [`BaseStore`](https://langchain-ai.github.io/langgraph/reference/store/)
so a LangGraph agent's long-term memory gets bi-temporal supersession for free:
```bash
pip install "tenet-memory[langgraph] @ git+https://github.com/Nas01010101/tenet.git"
```
```python
from tenet.integrations.langgraph import TenetStore
store = TenetStore(db_path="data/agent.db")
store.put(("users", "alex"), "residence", {"city": "Montreal"})
store.put(("users", "alex"), "residence", {"city": "Toronto"}) # supersedes, not overwritten
store.get(("users", "alex"), "residence").value # -> {"city": "Toronto"}
```
### 3. Fully local / air-gapped
Both write-path calls (`ingest()`'s fact-distillation and `embed_texts()`) can run against a
local model, so the whole loop works with **zero cloud calls**:
```bash
LLM_PROVIDER=ollama OLLAMA_MODEL=tenet-distiller-1.5b-v2 EMBED_PROVIDER=local \
tenet remember "I moved from Boston to Seattle" # distilled + embedded 100% locally
```
`tenet-distiller-1.5b-v2` is our LoRA-tuned Qwen2.5-1.5B distiller: on a decontaminated
held-out eval it supersedes 6/6 clean-churn cases (untuned base: 0/6) at **0.775
key-consistency, beating the cloud reference's 0.707**. Trained on one RTX 3080; the full
reproducible pipeline lives in [`scripts/distiller_lora/`](scripts/distiller_lora/) and the
measurement caveats in [`BENCHMARK.md` §10](docs/BENCHMARK.md#10-local-distiller-zero-cloud-verdict).
## The agent
Tenet ships as a personal assistant ([`src/tenet/agent.py`](src/tenet/agent.py)) on Qwen Cloud:
```
you › Hi! I'm Alex, I live in Montreal and work as a data analyst.
assistant › Nice to meet you, Alex! How's the analyst work in Montreal? [remembered 2 facts]
… weeks later …
you › I moved to Toronto and got promoted to senior analyst!
you › Where do I live and what's my job now?
assistant › You live in Toronto and you're a senior analyst. Congrats on the promotion!
```
```bash
python -m tenet.agent # interactive assistant (or: tenet-agent)
python scripts/demo_agent.py # the scripted story (video walkthrough)
```
## Architecture

Two layers over one bi-temporal store (beliefs + evidence), two surfaces (MCP + HTTP), powered
by Qwen Cloud. Component diagram + key equations: [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) ·
original scoping: [`docs/DESIGN.md`](docs/DESIGN.md) · positioning: [`docs/COMPARISON.md`](docs/COMPARISON.md).
## Reproduce the paper
Every benchmark is one CLI command: provider preset + config + git-sha logged to
`data/bench_runs.jsonl`.
```bash
tenet bench list # all benchmarks + which figure/§ each reproduces
tenet bench run <name> --dry-run ... # print the exact command+env, run nothing
tenet bench run churn --provider ollama --principals 12 --k 6 --updates 2,4,6,8,10,12 # Fig.1
```
`--provider` presets: `ollama` (fully offline), `openrouter`, `local`, `qwen` (Qwen Cloud).
Full matrix: [`docs/BENCHMARK.md`](docs/BENCHMARK.md) · [`docs/HARNESS.md`](docs/HARNESS.md).
## Repository
```
paper/ tenet.md tenet.pdf the paper
src/tenet/ core.py memory.py distill.py navigate.py the belief-state memory engine
agent.py mcp_server.py api.py the assistant + MCP/HTTP surfaces
integrations/ LangGraph + LlamaIndex adapters
examples/ 00_zero_key_demo.py … 06_llamaindex_memory.py
scripts/ bench_*.py test_*.py demo_agent.py benchmarks, tests, walkthrough
docs/ BENCHMARK.md COMPARISON.md ARCHITECTURE.md DESIGN.md DEPLOY.md
```
## Citation
```bibtex
@misc{tenet2026,
title = {Tenet: Agent Memory as a Self-Consistent Belief State},
author = {Elghoudane, Anas},
year = {2026},
note = {Global AI Hackathon with Qwen Cloud, Track 1},
url = {https://github.com/Nas01010101/tenet}
}
```
## Origin
Tenet started as a [Global AI Hackathon with Qwen Cloud](https://qwencloud-hackathon.devpost.com)
(Track 1: MemoryAgent) entry. Hackathon materials: [`docs/hackathon/`](docs/hackathon/).
[Majalis](https://github.com/Nas01010101/majalis), our Track 3 agent society, runs its shared
belief board on Tenet's exact supersession design; the mechanism is load-bearing in a second
product.
## License
MIT. See [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues