Global AI Hackathon with Qwen Cloud · Track 1: MemoryAgent

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

LongMemEval oracle, 50 questions — the evidence was in context every time.

15.4% → 76.9%
Temporal reasoning

After giving every memory a validity anchor (event_time), n=13.

~1187
Tokens per query

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/6

The budget fills with whatever happened most recently. Every important older fact is missed — not ranked low, missed entirely.

in contextimportant, misseddistractor

Tenax hybrid retrieval

6/6

Dense vectors + full-text + recency + importance, packed to the same ceiling. Every buried fact surfaces, within the identical token budget.

in contextdistractor

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

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

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

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

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

  5. reflect()

    Consolidate what's left

    Near-duplicates are clustered and distilled into canonical facts, shrinking the store and sharpening precision.

    consolidate.py · qwen-turbo

Reasoning
qwen-turbo · qwen3.7-plus
Qwen Cloud (DashScope-intl)
Embeddings
text-embedding-v4
1024-dim, Qwen Cloud
Store
PostgreSQL + pgvector
vectors · metadata · full-text, one database

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.

step 1 / 14
remember()62d ago

Two months ago — the first real conversation.

Input

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.

Extracted 4 memories
  • Maya leads the recommendation engine team at Kalibrasi.

    i=8
    semantic
  • Maya's research advisor on the project is Dr. Lin.

    i=7
    semantic
  • Maya lives in Jakarta.

    i=6
    semantic
  • Maya is allergic to shellfish, so team dinners need alternatives.

    i=9
    semantic

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

100%
Retrieval hit rate
n=50. The right evidence reached the reader every single time.
42% → 64%
End-to-end accuracy
Same memory engine, reader swapped qwen-turbo reader → qwen3.7-plus reader.
76.9%
Temporal reasoning
Up from 15.4%, after event_time validity anchors. n=13.
91.7%
Non-temporal control
n=12. The temporal change cost nothing elsewhere.
projection, not a measurement

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.

Categorynqwen-turbo readerqwen3.7-plus reader
Abstention3
100%
100%
Knowledge update7
42.9%
71.4%
Multi session12
33.3%
66.7%
Single session assistant6
66.7%
100%
Single session preference3
0%
33.3%
Single session user6
83.3%
83.3%
Temporal reasoning13
15.4%
30.8%
Overall50
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

Genuine updates applied6/6
Trap facts left untouched3/3
Wrong supersedes0

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

Actively-used facts surviving6/6
Wrong merges0
Dormant facts surviving0/6

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=50
  • benchmark/results/qwen37plus_sample50.summary.json — qwen3.7-plus reader, LongMemEval oracle, n=50
  • benchmark/results/temporal_tier1A.summary.json — temporal-reasoning subset after event_time validity, n=13
  • benchmark/results/regression_nontemporal.summary.json — non-temporal control set after the temporal change, n=12
  • benchmark/results/hybrid_vs_naive.summary.json — hybrid vs recency-only on a 30-distractor haystack
  • benchmark/results/update.summary.json — belief revision / knowledge update
  • benchmark/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

stdio

Drop the config block into claude_desktop_config.json and the five tools appear in the client.

Qwen desktop client

stdio

Same server, same config shape — Tenax does not special-case the client it is talking to.

Any MCP client

stdio

Tenax implements the MCP tool protocol, so compatibility is at the protocol level rather than per-integration.

Your own agent

REST

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

claude_desktop_config.json
{
  "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"
      }
    }
  }
}
Run it locally
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 transport

The 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[]