I gave my agents a memory that refuses to remember things it can't prove — and can prove what it deleted

Reddit r/AI_Agents Tools

Summary

The author built a memory layer for AI agents called fireweed, which uses deterministic code to verify claims against evidence, preventing hallucinations and enabling auditable, provable memory erasure.

If you've shipped an agent with memory, you already know the failure: it remembers something the user never said. The model reads a conversation, decides what's worth keeping, and quietly writes an interpretation into long-term memory as if it were a fact. Three sessions later your agent "knows" something nobody ever told it, and you can't even find where it came from. The root cause is that we let the model both hallucinate and guard the record. Same component, both jobs. So I built the memory layer the other way around: the model doesn't get to decide what's remembered — it proposes, and deterministic code (no model, no prompt, nothing it can talk around) decides whether the proposal is allowed in. Your agent has to quote its source. If the claim says more than the quote does, it's refused: remember(claim="Priya joined Acme in 2019 under duress.", evidence="Priya Raman joined Acme in 2019 as a logistics analyst.") REFUSED (asserts_more_than_evidence) — the claim adds something the evidence does not say. claim : Priya joined Acme in 2019 under duress. evidence: Priya Raman joined Acme in 2019 as a logistics analyst. "Under duress" was never in the evidence, so it never enters memory. The agent can't smooth-talk its way past the gate, because the gate is a function, not a conversation. Facts that are grounded get stored with the byte range they came from: ADMITTED — Dana Kim has a cat named Pepper. grounding : grounded_verbatim receipt : bytes [0:71] of sha256:b410428a2b58… Which means months later you can trace any memory back to the exact source span — and if the source changed underneath it, that receipt fails the check. Your agent's memory can be audited instead of taken on faith. Ask it something outside what it knows and it abstains instead of confabulating: > What is Dana Kim's salary? ABSTAINED (unknown_predicate) — no claims ground "salary"; 2 claims about Dana Kim exist, grounding: named, pepper, cat, plays, weekends, basketball Next: ask about one of: named, pepper, cat, plays, weekends — or commit a claim grounding "salary". And forget(subject) returns a signed erasure certificate with exact closure — the person is gone, everyone else's facts survive. If you're building an agent that touches real users' data, that's the artifact behind "delete me, and prove you did." Not a soft delete you hope worked — a certificate. It's an MCP server, so it drops into whatever you're building your agent on: claude mcp add fireweed -- uvx fireweed-mcp or, for any MCP client: { "mcpServers": { "fireweed": { "command": "uvx", "args": ["fireweed-mcp"] } } } Now the part where I earn trust instead of asking for it. Two things are true at once. The idea — model proposes, code decides, receipts, provable erasure — is solid; I've hammered on it and it holds. The code is two days old on PyPI, and young in exactly the way two-day-old code is young. Here's how I know: the night before this post, I installed my own package like a stranger and drove it the way a real agent would, trying to break it. It lied to me in minutes. I told it to remember "Ada Lovelace wrote the first algorithm" — it said ADMITTED and had stored nothing. The write path was reporting success while silently dropping the fact: the worst possible bug in a memory system, right in the front door. The firewall recognized verbs by spelling (-s/-ed/-ing), so it had never heard of "wrote," "went," or "built" — nine of sixteen ordinary sentences were being thrown out as gibberish, and the survivors mostly passed by luck. ("Marcus Webb sold his bookshop" only made it because his ends in s.) Three launch-blocking bugs that night. Fixed all three, wrote tests so they can't come back, then cut the release you're installing. The harness that caught them — driving the installed server over stdio on Python 3.9–3.13, throwing malformed requests, 46KB payloads, path traversal, null bytes, corrupt store files, and three agents hammering one store at it — is what should have existed at 0.1.0. It exists now. So when you hit a bug (you will — recall especially is soft), that's not the thing falling apart. That's the loop working. It caught three the night before launch; it'll catch yours. The honestly weak part: recall. On a 410-question set where the answer is in memory, it still refuses ~37% of the time on a default install (~25% with the optional semantic encoder). I'd rather you hear that from me than discover it in your first ten minutes. The write path — what's allowed in, the receipts, the provable deletion — is the half that stands up. (I also retracted my own benchmark for this project a while back, after finding it was scoring a perfect result against an empty database. Public in the repo, raw data and all, if you want to judge how I handle being wrong.) Local-first the whole way down: zero dependencies, no API keys, no cloud, no model, no GPU. Nothing in it runs inference, so it doesn't care what powers your agent. Storage is an open format with a stdlib-only reader — your users' memory outlives this project. Licence, up front: FSL-1.1-ALv2 — source-available, not OSI open source, free for anything except building a competing product, converts to Apache-2.0 in 2028. Said here rather than left in the LICENSE file to feel like a gotcha. github.com/Starksood/fireweed-mcp I'll be in the comments all day. Wire it into an agent, break it, tell me how.
Original Article

Similar Articles