Persistent memory for AI agents, that manages itself.
Tenax is an MCP server that gives any compatible agent long-term memory across sessions. It decides what is worth keeping, retrieves the right facts inside a strict token budget, revises beliefs when the world changes, and lets the rest fade — all running on Qwen Cloud.
Latin tenax — “holding fast”, as in memoria tenax, a memory that retains.
- 100%
- Retrieval hit rate
- 15.4% → 76.9%
- Temporal reasoning
- ~1187
- Tokens per query
LongMemEval oracle, 50 questions — the evidence was in context every time.
After giving every memory a validity anchor (event_time), n=13.
Packed against a 1200-token ceiling, not dumped whole.
The problem
An agent that forgets is an agent you have to repeat yourself to.
Most memory layers are a chat log with a similarity search bolted on. They keep everything, rank by recency, and pour whatever fits into the prompt. That fails in the exact moment memory is supposed to matter: when the fact you need is old, important, and buried under a week of noise.
Recency-only retrieval
0/6The budget fills with whatever happened most recently. Every important older fact is missed — not ranked low, missed entirely.
Tenax hybrid retrieval
6/6Dense vectors + full-text + recency + importance, packed to the same ceiling. Every buried fact surfaces, within the identical token budget.
◆ 30 distractors, 6 planted facts, 400-token budget for both arms · benchmark/results/hybrid_vs_naive.summary.json · reproduce with python -m benchmark.run --reset --budget 400
How it works
Five skills, one self-managing loop.
Tenax is not a vector store with a nice wrapper. Each write and each read triggers maintenance, so the store stays small, current, and non-contradictory without anyone curating it.
remember()Extract, don't log
Qwen turns raw turns into distilled, self-contained facts with a type and an importance score. Chat logs are not memories.
extract.py · qwen-turbo
remember()Revise beliefs at write time
A new fact that genuinely contradicts a stored one archives it with a superseded_by pointer — instead of leaving both versions to fight in the prompt.
revise.py · qwen-turbo
recall()Retrieve hybrid, pack to budget
Vector similarity + Postgres full-text + recency + importance produce one score; MMR packs the highest-value non-redundant set into the token ceiling.
retrieve.py · text-embedding-v4
forget()Let the rest fade
An Ebbinghaus-style decay score — importance · e^(−Δt/τ) · (1 + ln(1+accesses)) — drives a sweep that archives what stopped earning its place. Recall reinforces what it returns.
forget.py · no LLM call
reflect()Consolidate what's left
Near-duplicates are clustered and distilled into canonical facts, shrinking the store and sharpening precision.
consolidate.py · qwen-turbo
See it run
A real session, replayed step by step.
Every response below is a verbatim capture of the live engine — Qwen Cloud for extraction and embeddings, Postgres/pgvector for the store. It is replayed rather than called live so it is identical every time you watch it, but nothing here is mocked or written by hand.
remember()62d agoTwo months ago — the first real conversation.
I'm Maya, I lead the recommendation engine team at Kalibrasi. My research advisor on the project is Dr. Lin. I live in Jakarta. Also worth noting: I'm allergic to shellfish, so team dinners need alternatives.
Maya leads the recommendation engine team at Kalibrasi.
i=8semanticMaya's research advisor on the project is Dr. Lin.
i=7semanticMaya lives in Jakarta.
i=6semanticMaya is allergic to shellfish, so team dinners need alternatives.
i=9semantic
◆ Captured by scripts/record_replay.py · full transcript in web/data/replay.json
This session is a recording — identical every viewing. Drive a live instance yourself →
Measured, not claimed
Every number here comes from a run you can reproduce.
Evaluated on LongMemEval against Qwen Cloud. Raw records live in benchmark/results/, and the site reads those files directly — so nothing on this page can drift from the artifacts.
Substituting the separately measured 76.9% temporal subset into the 50-item run projects ~76% overall. We report it as a projection because it was never produced by one end-to-end run — the measured overall figure remains 64%.
Where the gains came from
Retrieval was already saturated, so the remaining headroom was on the reasoning side. Swapping the reader lifted every weak category without touching the memory engine.
| Category | n | qwen-turbo reader | qwen3.7-plus reader |
|---|---|---|---|
| Abstention | 3 | 100% | 100% |
| Knowledge update | 7 | 42.9% | 71.4% |
| Multi session | 12 | 33.3% | 66.7% |
| Single session assistant | 6 | 66.7% | 100% |
| Single session preference | 3 | 0% | 33.3% |
| Single session user | 6 | 83.3% | 83.3% |
| Temporal reasoning | 13 | 15.4% | 30.8% |
| Overall | 50 | 42% | 64% |
◆ baseline_oracle.summary.json · qwen37plus_sample50.summary.json · LongMemEval oracle set, budget 1200 tokens
The parts that are easy to get wrong
A memory that updates beliefs can corrupt them, and a memory that forgets can drop something load-bearing. Both were measured for false positives, not just for recall.
Belief revision
Traps are facts that merely look similar. Superseding one would mean silently destroying a true memory — so the count that matters is the zero.
◆ update.summary.json · python -m benchmark.update
Staleness, 3 forget/reflect cycles
The last row is the design working as intended, not a failure: memories that are never accessed are meant to fade. Access is what buys retention.
◆ staleness.summary.json · python -m benchmark.staleness
Every artifact behind these numbers
benchmark/results/baseline_oracle.summary.json— qwen-turbo reader, LongMemEval oracle, n=50benchmark/results/qwen37plus_sample50.summary.json— qwen3.7-plus reader, LongMemEval oracle, n=50benchmark/results/temporal_tier1A.summary.json— temporal-reasoning subset after event_time validity, n=13benchmark/results/regression_nontemporal.summary.json— non-temporal control set after the temporal change, n=12benchmark/results/hybrid_vs_naive.summary.json— hybrid vs recency-only on a 30-distractor haystackbenchmark/results/update.summary.json— belief revision / knowledge updatebenchmark/results/staleness.summary.json— 3 forget+reflect cycles
Measured 2026-07-07 on Qwen Cloud (DashScope-intl).
Works with
One server, no per-client adapters.
Memory should not be something you re-integrate for every agent you try. Tenax exposes its five skills through MCP, so any compatible client gets them without a bespoke plugin — and a REST API covers everything that speaks HTTP instead.
Claude Desktop
stdioDrop the config block into claude_desktop_config.json and the five tools appear in the client.
Qwen desktop client
stdioSame server, same config shape — Tenax does not special-case the client it is talking to.
Any MCP client
stdioTenax implements the MCP tool protocol, so compatibility is at the protocol level rather than per-integration.
Your own agent
RESTSeven endpoints (/remember, /recall, /forget, /reflect, /memories, /stats, /health) for anything that does not speak MCP.
What is and isn’t true today
- stdio is the only MCP transport. There is no hosted HTTP or SSE MCP endpoint — you run the server next to your agent.
- Compatibility is protocol-level, not a per-client integration list. The configs shipped in the repo are for Claude Desktop and Qwen clients; other MCP clients should work, but we only claim what we ship.
- It needs Postgres with pgvector and a Qwen Cloud API key. Both are set up by the quickstart below.
Install
It's an MCP server. Point your agent at it.
Tenax speaks MCP over stdio, so any compatible client — Claude Desktop, a Qwen client, your own agent — gets five memory tools with no bespoke integration. A REST API ships alongside it for dashboards and cloud deployment.
{
"mcpServers": {
"tenax": {
"command": "pipenv",
"args": ["run", "python", "-m", "app.mcp_server"],
"cwd": "/absolute/path/to/tenax",
"env": {
"QWEN_API_KEY": "sk-...",
"DATABASE_URL": "postgresql+psycopg://tenax:tenax@localhost:5432/tenax"
}
}
}
}git clone https://github.com/YantUgli/tenax
cd tenax
pipenv install
cp .env.example .env # add your QWEN_API_KEY
docker compose up -d db # Postgres + pgvector
pipenv run python -m scripts.init_db
pipenv run python -m app.mcp_server # stdio transportThe five tools
remember(text, user_id?, source?)Extracts durable facts from an interaction, embeds them, and applies belief revision against what is already stored.
→ { created: [...], superseded: [...] }recall(query, user_id?, token_budget?)Hybrid retrieval packed to the budget. Returns an assembled context string, ready to paste into a prompt.
→ { context, tokens_used, memories: [{ id, content, score }] }forget(user_id?)Runs the decay sweep and archives memories that fell below the retention threshold.
→ { scanned, archived, archived_ids }reflect(user_id?)Clusters near-duplicates and distills each cluster into one canonical semantic fact.
→ { clusters_found, memories_merged, semantic_created, merged_ids }list_memories(user_id?, status?, limit?)Inspect the store, including live decay scores and supersession pointers.
→ Memory[]