{"slug": "show-hn-tensemem-deterministic-memory-for-ai-agents", "title": "Show HN: TenseMem – deterministic memory for AI agents", "summary": "TenseMem, a new open-source memory layer for AI agents, was released as a pip-installable package that stores agent memory in a single SQLite file and keeps one current value per slot, with the engine rather than the model deciding which typed facts apply. In the project's packaged benchmark of 101 conversations run with its rule-based FakeExtractor and no network, TenseMem scored 1.00 on current-state accuracy, 0.00 on stale-fact leak, 1.00 on freshness honesty and 0.00 on contamination, versus 0.37 current-state accuracy and 1.00 stale-fact leak for both concatenated transcripts and token-overlap retrieval. The package ships adapters for OpenAI, Anthropic, Ollama, LiteLLM and Gemini, exposes Python, CLI, REST and MCP surfaces over a shared Memory/AsyncMemory core, and fences recalled context so the engine refuses to learn from its own output.", "body_md": "**Memory that knows its tenses.** Embedded, deterministic memory that AI agents can safely act on.\n\nOne SQLite file. No server required. The model proposes typed facts; the engine decides what is current.\n\n```\npip install tensemem\npython\nfrom tensemem import FakeExtractor, Memory\n\nmem = Memory(llm=FakeExtractor())\nmem.register_predicate(\"lives_in\")\nadded = mem.add(\"u1\", \"I live in Delhi\")\nctx = mem.context(user_id=\"u1\", min_seq=added.seq, wait=1.5)\nprint(ctx.text)  # fenced block the model can read\nprint(ctx.freshness.is_current)  # True once extract finished\n```\n\n`FakeExtractor` is rule-based and needs no API key. Same API with `tensemem[openai]`, `tensemem[anthropic]`, `tensemem[ollama]`, `tensemem[litellm]`, or Gemini (` GEMINI_API_KEY` in the process environment).\n\nSimilarity search returns both “I live in Delhi” and “I moved to Bangalore” and leaves the model to guess. TenseMem keeps one current value per slot, says whether the read is fresh, and refuses to learn from its own output.\n\n- **One current truth per ONE slot.** A new city supersedes the old one. History still answers “where did I live before?”\n- **Every read reports freshness.**`as_of_seq` ,`pending` ,`is_current` . Wait for a message, or see that it is not applied yet.\n- **No self-echo.** Recalled`context()` is fenced. Extract needs a verbatim quote from the user.\n- **Corrections stick.**`correct()` restores the displaced value and survives`rebuild()` with a newer model.\n- **The engine decides.** The LLM emits typed ops. Deterministic rules apply them.\n- **No infrastructure.** Default store is one SQLite file inside your process.\n\n```\nmem.add(\"u1\", \"I live in Delhi\")\nmem.set(user_id=\"u1\", predicate=\"lives_in\", value=\"Bangalore\")\n\nprint(mem.state(\"u1\").values)\n# {\"lives_in\": \"Bangalore\"}\n\nhistory = mem.history(\"u1\", predicate=\"lives_in\")\n# ACTIVE Bangalore, SUPERSEDED Delhi\n\nbangalore = next(f for f in history.facts if f.object == \"Bangalore\")\nmem.correct(bangalore.id)  # that move was never true\nprint(mem.state(\"u1\").values)\n# {\"lives_in\": \"Delhi\"}\nphp\nflowchart LR\n  E[episodes] --> X[extractor proposes ops]\n  X --> R[resolver]\n  R --> F[facts]\n  F --> C[\"context() + freshness\"]\n  C -.->|fence stripped| X\n```\n\nEvery surface is an adapter over `Memory` / `AsyncMemory`. The model proposes typed ops; `resolve()` decides mutations; `apply()` writes one SQLite file. Facts are a rebuildable projection of an append-only episode log.\n\nEditable source: [docs/architecture.excalidraw](https://github.com/tensemem/tensemem/blob/main/docs/architecture.excalidraw). File map: [Architecture](https://github.com/tensemem/tensemem/blob/main/docs/architecture.md).\n\n| If you are changing… | Start here | \n|---|---|\n| Python / CLI / REST / MCP API | `src/tensemem/memory.py` ,`cli/main.py` ,`server.py` ,`mcp.py` | \n| Studio UI | `studio/app/` (board, memories, playground, echo, ops) and`studio/lib/api.ts` | \n| Extract prompt or worker | `pipeline/prompts/extract_v1.md` ,`pipeline/worker.py` ,`pipeline/extractor.py` | \n| What an op is allowed to do | `core/resolver.py` (pure),`pipeline/normalize.py` ,`pipeline/policy.py` ,`pipeline/guard.py` | \n| A new LLM or embedder | `providers/llm/` ,`providers/embed/` — keys in process env only | \n| SQLite schema or reads | `storage/sqlite/` (one writer; ADR 0003 / 0020) | \n| Freshness, fence, corrections | `storage/sqlite/snapshot.py` ,`security/fence.py` ,`correct()` | \n| Invariants | `tests/core/` ,`tests/property/` ,`tests/golden/` — default pytest never calls a hosted model | \n\nDo not auto-register unknown predicates, skip `keep_ops`, or teach the engine from fenced `context()` text. Those are locked by tests.\n\n`tensemem bench` scores the packaged 101 conversations with `FakeExtractor` (no network):\n\n| System | Current-state accuracy | Stale-fact leak | Freshness honesty | Contamination | \n|---|---|---|---|---|\n| TenseMem | 1.00 | 0.00 | 1.00 | 0.00 | \n| Concatenated transcript | 0.37 | 1.00 | 0.00 | n/a | \n| Token-overlap retrieval | 0.37 | 1.00 | 0.00 | n/a | \n\nReproduce with `tensemem bench`. Methodology: [TenseBench](https://github.com/tensemem/tensemem/blob/main/docs/tensebench.md).\n\n```\ntensemem inspect --user u1\ntensemem history --user u1 lives_in\ntensemem search --user u1 \"where did I live\"\n```\n\n[TenseMem Studio](https://github.com/tensemem/tensemem/blob/main/docs/studio.md) is a local Next.js inspector over `tensemem serve` (board, memories table, playground, compare, echo). It is not a chatbot. Secrets stay in the serve process environment — never in the Token field. Studio lives in the git checkout, not on PyPI.\n\n```\ngit clone https://github.com/tensemem/tensemem.git\ncd tensemem\npip install \"tensemem[server]\"\n```\n\nTerminal 1 — HTTP:\n\n```\ntensemem --db ./studio.db serve --port 8080\n```\n\nTerminal 2 — Studio:\n\n```\ncd studio && npm install && npm run dev\n```\n\n| Extra | What you get | \n|---|---|\n| `tensemem[openai]` /`[anthropic]` /`[ollama]` /`[litellm]` | Hosted or local extract | \n| `tensemem[server]` | `tensemem serve` REST + Studio backend | \n| `tensemem[mcp]` | stdio or `tensemem mcp --http` | \n| `tensemem[langgraph]` | `TenseMemStore` (`put` /`search` ) | \n| `tensemem[llamaindex]` | `TenseMemChatMemory` (`put` /`get` ) | \n| `tensemem[local]` | FastEmbed vectors for search | \n\nGemini extract uses `GEMINI_API_KEY` (or `GOOGLE_API_KEY`) in the environment of the process that runs extract. No extra package beyond the server if you use the built-in Gemini adapter.\n\n`--token` / `TENSEMEM_TOKEN` is optional bearer auth on HTTP. Bind stays loopback unless you put a TLS proxy in front.\n\n**0.1.0 alpha.** The Python API, CLI, REST, MCP, and Studio are usable on a single SQLite file. The default store is SQLite; a second SQL dialect is not shipped. Multi-tenant hosted Studio is not in this tree.\n\n[Quickstart](https://github.com/tensemem/tensemem/blob/main/docs/quickstart.md) · [Concepts](https://github.com/tensemem/tensemem/blob/main/docs/concepts.md) · [Architecture](https://github.com/tensemem/tensemem/blob/main/docs/architecture.md) · [API](https://github.com/tensemem/tensemem/blob/main/docs/api.md) · [CLI](https://github.com/tensemem/tensemem/blob/main/docs/cli.md) · [REST](https://github.com/tensemem/tensemem/blob/main/docs/server.md) · [Studio](https://github.com/tensemem/tensemem/blob/main/docs/studio.md) · [MCP](https://github.com/tensemem/tensemem/blob/main/docs/mcp.md) · [Operations](https://github.com/tensemem/tensemem/blob/main/docs/ops.md) · [ADRs](https://github.com/tensemem/tensemem/blob/main/docs/adr)\n\n`uvx mkdocs serve` previews the same pages locally.\n\nDefault tests need no API keys and never call a model. From a git checkout:\n\n```\ngit clone https://github.com/tensemem/tensemem.git\ncd tensemem\nuv sync\nuv run ruff check .\nuv run ruff format --check .\nuv run pyright\nuv run pytest\n```\n\nStudio (optional, when you change `studio/`):\n\n```\ncd studio && npm ci && npm run typecheck && npm run build\n```\n\nLive extract against a real model (skipped unless both are set):\n\n```\nuv sync --extra openai\nTENSEMEM_LIVE=1 OPENAI_API_KEY=… uv run pytest -m live\n```\n\nModel keys belong in the process that runs extract (your script, or the `tensemem serve` terminal). They are never valid values for Studio **Token**.\n\n| Variable | Used for | \n|---|---|\n| `GEMINI_API_KEY` or`GOOGLE_API_KEY` | Gemini extract | \n| `OPENAI_API_KEY` | OpenAI extract / embeddings ( `tensemem[openai]` ) | \n| `ANTHROPIC_API_KEY` | Anthropic extract ( `tensemem[anthropic]` ) | \n| `TENSEMEM_LLM` | Default extractor name for `tensemem serve` (`fake` ,`gemini` ,`openai` , …) | \n| `TENSEMEM_LITELLM_MODEL` | Override Gemini / LiteLLM model id | \n| `TENSEMEM_TOKEN` | Optional bearer for `tensemem serve` /`tensemem mcp --http` (not a model key) | \n| `TENSEMEM_URL` | Studio rewrite target (default `http://127.0.0.1:8080` ) | \n| `TENSEMEM_LIVE` | Set to `1` with`OPENAI_API_KEY` to run`pytest -m live` | \n\nOllama needs a local Ollama process, not an env key. Details: [Studio](https://github.com/tensemem/tensemem/blob/main/docs/studio.md), [REST](https://github.com/tensemem/tensemem/blob/main/docs/server.md), [SECURITY.md](https://github.com/tensemem/tensemem/blob/main/SECURITY.md).\n\nSee [CONTRIBUTING.md](https://github.com/tensemem/tensemem/blob/main/CONTRIBUTING.md). `main` is protected; every change is a pull request. Merges wait until **CI**, **PR quality**, and CodeQL are green.", "url": "https://wpnews.pro/news/show-hn-tensemem-deterministic-memory-for-ai-agents", "canonical_source": "https://github.com/tensemem/tensemem", "published_at": "2026-10-22 05:19:23+00:00", "updated_at": "2026-10-22 06:12:04.718157+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "agent-protocols", "artificial-intelligence"], "entities": ["TenseMem", "SQLite", "OpenAI", "Anthropic", "Ollama", "LiteLLM", "Gemini"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/show-hn-tensemem-deterministic-memory-for-ai-agents", "markdown": "https://wpnews.pro/news/show-hn-tensemem-deterministic-memory-for-ai-agents.md", "text": "https://wpnews.pro/news/show-hn-tensemem-deterministic-memory-for-ai-agents.txt", "jsonld": "https://wpnews.pro/news/show-hn-tensemem-deterministic-memory-for-ai-agents.jsonld"}}