{"slug": "show-hn-memharness-bi-temporal-memory-for-ai-agents-in-one-sqlite-file", "title": "Show HN: Memharness – Bi-temporal memory for AI agents, in one SQLite file", "summary": "A developer released Memharness, an open-source bi-temporal memory primitive for AI agents that stores facts with provenance in a single SQLite file without LLM calls. The tool enables agents to answer temporal queries like \"what did you believe on March 1st?\" and supports supersession-based corrections and provenance-scoped deletion, targeting use cases where agent history outgrows context windows or audit trails are needed.", "body_md": "**A bi-temporal, provenance-carrying memory primitive for AI agents.** One\nSQLite file. No LLM or network calls in the storage layer. Exposed to any agent\nvia MCP.\n\nMost agent memory is a bag of strings. memharness stores **facts**, and\ncombines three semantics that incumbents tend to split apart:\n\n**Bi-temporal**: every fact records*when it became true in the world*(`valid_from`\n\n/`valid_to`\n\n) separately from*when the agent learned it*(`tx_at`\n\n). So you can ask:*\"what did you believe on March 1st?\"***Supersession, never deletion**: corrections close the old fact and link it to its successor.*\"What did you think before I corrected you?\"*has an answer.**Provenance per fact**: every memory cites who said it, where, and when.*\"Why do you believe that?\"*has an answer. So does*\"forget everything from that session.\"*\n\nThe storage layer is deterministic: no LLM, no network, no background daemon. It's plain SQLite, so you can open the file with any client.\n\n*Run it yourself: cd examples && npm install && npm run demo*\n\nmemharness is not a magic accuracy upgrade, and it is honest about that. If your\nagent's memory is small and static and comfortably fits the context window, a\n`CLAUDE.md`\n\nfile (or just stuffing the history into the prompt) is simpler, and\non short histories full context will match or beat any external memory system.\n\nReach for memharness when:\n\n**History outgrows the window**: months of facts, many subjects, more than you want to (or can) paste into every prompt.** You need an audit trail**:*\"what did the agent believe when it made this decision?\"*(`as_of`\n\n),*\"what changed since Monday?\"*(`diff`\n\n),*\"why does it believe this?\"*(`why`\n\n). These are queries a bag of strings cannot answer.**You need provenance-scoped deletion**:*\"forget everything from that session/file/source\"*in one call (GDPR-shaped, not a string search).**Beliefs change over time**: corrections should supersede, not silently overwrite, so old reasoning stays explainable.\n\nHonest, and pointed at the thing memharness actually does differently: it is a deterministic, auditable storage layer rather than an extraction service.\n\n| Storage | LLM calls to write |\n`as_of` / `diff` / `why` |\nEmbeddable / self-host | |\n|---|---|---|---|---|\nmemharness |\none SQLite file | none | yes: bi-temporal + provenance | yes, it's a library |\n| mem0 | hosted / OSS service | yes (extraction pipeline) | partial / no | partial |\n| Zep / Graphiti | hosted graph | yes (LLM ingestion) | bi-temporal, but LLM-built | partial |\n| Letta / MemGPT | agent framework + DB | yes (agent-managed) | no | yes |\n| Anthropic memory tool | client-side files | model edits files | no (model picks) | yes |\nplain `CLAUDE.md` / files |\ntext files | none | no | yes |\n\nWhere the others win, plainly: mem0 and Zep do **automatic fact extraction**\nfrom raw conversation, which memharness deliberately does not (the write path\nstays model-free; a client or skill decides what is worth remembering). Plain\n`CLAUDE.md`\n\nneeds no install at all. memharness earns its place when you need the\ntemporal and provenance queries the others don't offer.\n\n| Package | What it is |\n|---|---|\n`@memharness/core` |\nTypeScript library: schema, migrations, write path, recall ranking. No model, no network. |\n`@memharness/mcp` |\nMCP server (stdio) exposing the seven tools to any MCP client. |\n`@memharness/embed` |\nOptional. A local embedding model for hybrid (semantic) recall. Not installed by default. |\n\nThe default install is small (SQLite plus the MCP SDK); the embedding model is\nopt-in, see [Hybrid recall](#optional-hybrid-recall).\n\n**Claude Code:**\n\n```\nclaude mcp add memharness -- npx -y @memharness/mcp\n```\n\n**Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`\n\n)\n**and Cursor** (`~/.cursor/mcp.json`\n\n) use the same JSON shape:\n\n```\n{\n  \"mcpServers\": {\n    \"memharness\": { \"command\": \"npx\", \"args\": [\"-y\", \"@memharness/mcp\"] }\n  }\n}\n```\n\n**Codex** (`~/.codex/config.toml`\n\n) uses TOML, not JSON:\n\n```\n[mcp_servers.memharness]\ncommand = \"npx\"\nargs = [\"-y\", \"@memharness/mcp\"]\n```\n\nThe database lives at `~/.memharness/memory.db`\n\n(override with `MEMHARNESS_DB`\n\n;\n`XDG_DATA_HOME`\n\nis honored on Linux). Nothing else is written unless you turn on\nthe optional [debug log](#optional-local-usage-log).\n\n- Add the server with one of the commands above, then\n**restart your client** so it picks up the new MCP server. - In a conversation, hand the agent a durable fact, e.g.\n*\"remember that I deploy this project with Fly.io.\"*It calls`remember`\n\n. - Later (or in a fresh session) ask\n*\"what do you know about how I deploy?\"*It calls`recall`\n\nand answers from memory. Correct it and it calls`revise`\n\n; the old belief becomes history, queryable with`as_of`\n\n/`why`\n\n/`diff`\n\n.\n\nNo API key, no signup, no network. The first `remember`\n\ncreates the SQLite file\nand that's the whole setup. To watch the tools work end to end without an agent,\nrun the demo: `cd examples && npm install && npm run demo`\n\n.\n\nBy default the agent decides when to call `recall`\n\n. To *push* relevant memory in\nat the start of every session instead (more reliable than hoping the model\nremembers to look), add a Claude Code **SessionStart hook** that runs the\nbundled `memharness-context`\n\ntool, whose stdout is injected into context:\n\n```\n{\n  \"hooks\": {\n    \"SessionStart\": [\n      { \"hooks\": [ { \"type\": \"command\",\n        \"command\": \"npx -y -p @memharness/mcp memharness-context --subject user\" } ] }\n    ]\n  }\n}\n```\n\nIt prints a compact dump of the most relevant current beliefs (and exits quietly\nif there's nothing yet), so the agent starts each session already knowing the\ndurable facts. Pass `--subject`\n\nmore than once to inject several entities.\n\n| Tool | What it does | The thesis it tests |\n|---|---|---|\n`remember` |\nstore an atomic fact with confidence + provenance | facts > blobs |\n`recall` |\nranked current beliefs; `as_of` returns beliefs at a past instant |\nbi-temporal |\n`revise` |\nsupersede a belief, keep history | supersession > deletion |\n`diff` |\nwhat changed since a date (learned/revised/retracted) | the audit demo |\n`why` |\nprovenance + full revision chain for a fact | trust / audit |\n`forget` |\ntombstone by id or by source (provenance-based deletion) | GDPR-shaped |\n`stats` |\ncounts, subjects, schema version | — |\n\n``` js\nimport { Memharness } from \"@memharness/core\";\n\nconst mem = Memharness.open(); // ~/.memharness/memory.db\n\n// Learn something now, then learn it was actually true earlier.\nconst { id } = mem.remember({\n  subject: \"user\",\n  fact: \"lives in Osaka\",\n  sourceRef: \"session-2026-06-09\",\n});\nmem.revise({ oldFactId: id, newFact: \"lives in Tokyo\", validFrom: \"2026-05-01\" });\n\nmem.recall({ query: \"lives\" }).facts[0].fact;   // \"lives in Tokyo\" (current belief)\nmem.diff({ since: \"2026-06-01\" });               // { learned, revised, retracted }\nmem.why(id);                                     // { fact, ancestors, descendants }\n```\n\n`recall`\n\nreturns a `RecallResult`\n\n(`{ facts: ScoredFact[]; asOf; truncated; usedFallback }`\n\n), not a bare string. `asOf`\n\ntime-travels: `mem.recall({ query: \"lives\", asOf: \"2026-04-15\" })`\n\nreturns what was believed *as held on that date*.\nThat honors transaction time, so a fact learned today is not visible to a query\nabout the past.\n\nRecall ranking is reciprocal-rank fusion over FTS5 BM25 (plus a vector rank when\n[hybrid recall](#optional-hybrid-recall) is enabled), times confidence, times\nrecency decay (90-day half-life, configurable), scored in SQL. An optional\n`maxTokens`\n\nbudget caps output for context windows. A substring fallback catches\npartial words and typos, in both FTS-only and hybrid modes.\n\nBy default, recall is FTS5 keyword search plus recency/confidence ranking: no\nmodel, fully offline. Hybrid recall adds a **semantic** leg via a local\nembedding model (BGE-small, ~130MB, downloaded once from the HuggingFace hub\nthen fully offline: no API key, no per-query network). Enable it in two steps:\n\n-\nInstall the optional embedding package alongside the server. With\n\n`npx`\n\n:\n\n```\nnpx -y -p @memharness/mcp -p @memharness/embed memharness-mcp\n```\n\n(or\n\n`npm i -g @memharness/embed`\n\nfor a global install). -\nSet\n\n`MEMHARNESS_HYBRID=1`\n\nin the server's environment.\n\nThe server then keeps stored facts embedded automatically: facts you `remember`\n\nbecome semantically searchable on the next `recall`\n\n, with no separate backfill\nstep. The first hybrid recall prints download progress to stderr while the model\nloads. If the package isn't installed, the server says so and stays FTS-only; it\nnever fails closed.\n\nAt the library level, recall is embedding-provider-agnostic: pass your own query\nvector to `recall({ queryVector })`\n\nand attach document vectors with\n`setEmbedding(...)`\n\n, from any model you like.\n\nTwo sessions, weeks apart. The agent learns a preference, the user later\ncorrects it, and a downstream question asks what the agent believed *at the\ntime*:\n\n```\n// June 9: the agent learns a deploy target and acts on it.\nconst { id } = mem.remember({\n  subject: \"project:acme\",\n  fact: \"deploys via Heroku\",\n  sourceRef: \"session-2026-06-09\",\n});\n\n// June 16: turns out the team moved to Fly back on June 1.\nmem.revise({\n  oldFactId: id,\n  newFact: \"deploys via Fly.io\",\n  validFrom: \"2026-06-01\",\n  sourceRef: \"session-2026-06-16\",\n});\n\nmem.recall({ subject: \"project:acme\" }).facts[0].fact; // \"deploys via Fly.io\"\n\n// \"Why did the CI config you wrote on June 9 target Heroku?\"\nmem.recall({ subject: \"project:acme\", asOf: \"2026-06-09\" }).facts[0].fact;\n//   \"deploys via Heroku\": what the agent honestly believed that day.\n\nmem.why(id);   // the full chain: Heroku, superseded by Fly.io, with sources.\nmem.diff({ since: \"2026-06-15\" });  // surfaces the Heroku -> Fly.io revision.\n```\n\nNo bag-of-strings memory can answer the `as_of`\n\nquestion, because it overwrote\nHeroku the moment it learned Fly.io.\n\nThe property suite is the heart of the project: for randomized sequences of\nremember/revise/forget, `recall({asOf: T})`\n\nmust equal the belief set produced\nby a naive, SQL-free replay of the event log, probed at every event\ntimestamp ±1ms. 10,000 cases run on every push to main.\n\nBenchmarked at 100k facts (10% revision chains, 2% retractions) on a developer\nlaptop (Apple Silicon): overall recall p95 **~1.3ms** against a 10ms budget,\nacross four query shapes (two-term keyword, keyword + subject, subject-only, and\n`as_of`\n\n+ keyword). `pnpm bench`\n\nseeds the database and asserts the budget, so\nthe number is reproducible rather than quoted.\n\nOne deliberate divergence from the original prototype: retraction stores a\ntimestamp (`retracted_at`\n\n), not a flag, so `as_of`\n\nqueries *before* the\nretraction still see history, which is what the prototype's docs promised but\nits SQL didn't deliver.\n\n```\npnpm install\npnpm test            # unit + behavior suites (property tests at 200 runs)\npnpm test:property   # 10k randomized property cases\npnpm bench           # seed 100k facts, assert recall p95 < 10ms\n```\n\nSchema migrations are forward-only, driven by `PRAGMA user_version`\n\n. Rows are\nnever deleted (`forget`\n\ntombstones), so `facts.id`\n\ndoubles as the insert\nsequence. All timestamps are canonical fixed-width UTC ISO 8601, making\nlexicographic comparison chronological.\n\nFor debugging or measuring your own usage, set `MEMHARNESS_DEBUG=1`\n\nand the\nserver appends an op-name and timestamp line (never fact content) to a\n`usage.log`\n\nnext to the database. It is off by default, fully local, and never\nnetworked.\n\nApache-2.0", "url": "https://wpnews.pro/news/show-hn-memharness-bi-temporal-memory-for-ai-agents-in-one-sqlite-file", "canonical_source": "https://github.com/las7/memharness", "published_at": "2026-06-18 05:43:48+00:00", "updated_at": "2026-06-18 06:22:55.561575+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "ai-infrastructure", "ai-research"], "entities": ["Memharness", "SQLite", "MCP", "Claude", "Cursor", "Codex", "Anthropic", "Mem0"], "alternates": {"html": "https://wpnews.pro/news/show-hn-memharness-bi-temporal-memory-for-ai-agents-in-one-sqlite-file", "markdown": "https://wpnews.pro/news/show-hn-memharness-bi-temporal-memory-for-ai-agents-in-one-sqlite-file.md", "text": "https://wpnews.pro/news/show-hn-memharness-bi-temporal-memory-for-ai-agents-in-one-sqlite-file.txt", "jsonld": "https://wpnews.pro/news/show-hn-memharness-bi-temporal-memory-for-ai-agents-in-one-sqlite-file.jsonld"}}