Show HN: TenseMem – deterministic memory for AI agents 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. Memory that knows its tenses. Embedded, deterministic memory that AI agents can safely act on. One SQLite file. No server required. The model proposes typed facts; the engine decides what is current. pip install tensemem python from tensemem import FakeExtractor, Memory mem = Memory llm=FakeExtractor mem.register predicate "lives in" added = mem.add "u1", "I live in Delhi" ctx = mem.context user id="u1", min seq=added.seq, wait=1.5 print ctx.text fenced block the model can read print ctx.freshness.is current True once extract finished 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 . Similarity 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. - One current truth per ONE slot. A new city supersedes the old one. History still answers “where did I live before?” - Every read reports freshness. as of seq , pending , is current . Wait for a message, or see that it is not applied yet. - No self-echo. Recalled context is fenced. Extract needs a verbatim quote from the user. - Corrections stick. correct restores the displaced value and survives rebuild with a newer model. - The engine decides. The LLM emits typed ops. Deterministic rules apply them. - No infrastructure. Default store is one SQLite file inside your process. mem.add "u1", "I live in Delhi" mem.set user id="u1", predicate="lives in", value="Bangalore" print mem.state "u1" .values {"lives in": "Bangalore"} history = mem.history "u1", predicate="lives in" ACTIVE Bangalore, SUPERSEDED Delhi bangalore = next f for f in history.facts if f.object == "Bangalore" mem.correct bangalore.id that move was never true print mem.state "u1" .values {"lives in": "Delhi"} php flowchart LR E episodes -- X extractor proposes ops X -- R resolver R -- F facts F -- C "context + freshness" C -.- |fence stripped| X Every 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. Editable 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 . | If you are changing… | Start here | |---|---| | Python / CLI / REST / MCP API | src/tensemem/memory.py , cli/main.py , server.py , mcp.py | | Studio UI | studio/app/ board, memories, playground, echo, ops and studio/lib/api.ts | | Extract prompt or worker | pipeline/prompts/extract v1.md , pipeline/worker.py , pipeline/extractor.py | | What an op is allowed to do | core/resolver.py pure , pipeline/normalize.py , pipeline/policy.py , pipeline/guard.py | | A new LLM or embedder | providers/llm/ , providers/embed/ — keys in process env only | | SQLite schema or reads | storage/sqlite/ one writer; ADR 0003 / 0020 | | Freshness, fence, corrections | storage/sqlite/snapshot.py , security/fence.py , correct | | Invariants | tests/core/ , tests/property/ , tests/golden/ — default pytest never calls a hosted model | Do not auto-register unknown predicates, skip keep ops , or teach the engine from fenced context text. Those are locked by tests. tensemem bench scores the packaged 101 conversations with FakeExtractor no network : | System | Current-state accuracy | Stale-fact leak | Freshness honesty | Contamination | |---|---|---|---|---| | TenseMem | 1.00 | 0.00 | 1.00 | 0.00 | | Concatenated transcript | 0.37 | 1.00 | 0.00 | n/a | | Token-overlap retrieval | 0.37 | 1.00 | 0.00 | n/a | Reproduce with tensemem bench . Methodology: TenseBench https://github.com/tensemem/tensemem/blob/main/docs/tensebench.md . tensemem inspect --user u1 tensemem history --user u1 lives in tensemem search --user u1 "where did I live" 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. git clone https://github.com/tensemem/tensemem.git cd tensemem pip install "tensemem server " Terminal 1 — HTTP: tensemem --db ./studio.db serve --port 8080 Terminal 2 — Studio: cd studio && npm install && npm run dev | Extra | What you get | |---|---| | tensemem openai / anthropic / ollama / litellm | Hosted or local extract | | tensemem server | tensemem serve REST + Studio backend | | tensemem mcp | stdio or tensemem mcp --http | | tensemem langgraph | TenseMemStore put / search | | tensemem llamaindex | TenseMemChatMemory put / get | | tensemem local | FastEmbed vectors for search | Gemini 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. --token / TENSEMEM TOKEN is optional bearer auth on HTTP. Bind stays loopback unless you put a TLS proxy in front. 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. 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 uvx mkdocs serve previews the same pages locally. Default tests need no API keys and never call a model. From a git checkout: git clone https://github.com/tensemem/tensemem.git cd tensemem uv sync uv run ruff check . uv run ruff format --check . uv run pyright uv run pytest Studio optional, when you change studio/ : cd studio && npm ci && npm run typecheck && npm run build Live extract against a real model skipped unless both are set : uv sync --extra openai TENSEMEM LIVE=1 OPENAI API KEY=… uv run pytest -m live Model keys belong in the process that runs extract your script, or the tensemem serve terminal . They are never valid values for Studio Token . | Variable | Used for | |---|---| | GEMINI API KEY or GOOGLE API KEY | Gemini extract | | OPENAI API KEY | OpenAI extract / embeddings tensemem openai | | ANTHROPIC API KEY | Anthropic extract tensemem anthropic | | TENSEMEM LLM | Default extractor name for tensemem serve fake , gemini , openai , … | | TENSEMEM LITELLM MODEL | Override Gemini / LiteLLM model id | | TENSEMEM TOKEN | Optional bearer for tensemem serve / tensemem mcp --http not a model key | | TENSEMEM URL | Studio rewrite target default http://127.0.0.1:8080 | | TENSEMEM LIVE | Set to 1 with OPENAI API KEY to run pytest -m live | Ollama 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 . See 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.