Agent memory with a recall button.

Every memory knows its sources. Recall a bad source, and everything learned from it goes too.

go get github.com/govinargo-labs/cheesedb

Preview v0.1.0. Embedded, Go standard library only, Apache-2.0.

Press the button.

An agent learned from three sources. The weather tool turns out to be wrong. Pick a source, press Recall, then flip the rule to compare.

What the agent knows

or and A: user saidI live in Pune B: web pagelives in Pune C: weather toolPune: rain M1user lives in Pune M2Pune is rainy M3carry an umbrella

Tap a source to select it.

Rule

Reason

What is in the agent’s files

    The structure line (sources and legs) is never erased, so the history and the hash chain stay intact.

    
              

    Deleting the bad source isn’t enough.

    An agent builds memory from sources it can’t fully trust. When one turns out to be bad, everything the agent learned from it is still in memory, and the agent keeps acting on it.

    Wrong

    A tool returned bad data, and three memories were built on it.

    Malicious

    A web page planted false facts or instructions: memory poisoning and prompt injection.

    Private

    A user asked for their data to be deleted, including what the agent concluded from it.

    Every record has three parts.

    Erase has to remove text without losing history, so each record is split in a way that keeps the history.

    Header
    The record’s id and a hash that seals it to the record before it. Never erased, so the chain stays verifiable.
    Structure
    What kind of record it is and which sources it depends on. Never erased, so recall still works after an erase.
    Content
    The text itself, with a random salt. The only part erase removes.
    CheeseDB internals. Top: cheesedb.Open(dir) opens the store, then OpenAgent(token) returns an agent handle with AddLot, Remember, Recall, Get, Active, Compact and Verify. In memory, rebuilt by replay: the family tree, leg counters and an offset table. On disk, the store folder holds a LOCK file and one folder per agent with agent.key, sealed segment files with their seal files, and one active segment. Below, one record split into header, structure and content, linked to its neighbours by the hash chain.
    In memory, the engine keeps the family tree, leg counters and an offset table, all rebuilt from the log when an agent is opened. On disk, each agent is a folder of append-only segments. In the picture, source C has been recalled, so M2 and M3 are dead and M1 is alive.

    A few lines of Go.

    This is the same story as the demo above.

    s, err := cheesedb.Open("/data/cheesedb")   // a folder you choose, created 0700
    tok, err := s.CreateAgent("alice")          // Alice's token, shown once: keep it safe
    alice, err := s.OpenAgent(tok)              // the only way to reach Alice's data
    defer alice.Close()
    
    a, _ := alice.AddLot("user_message", []byte("I live in Pune"))
    b, _ := alice.AddLot("web_page", []byte("profile: lives in Pune"))
    c, _ := alice.AddLot("weather_tool", []byte("Pune: rain"))
    
    m1, _ := alice.Remember([]byte("user lives in Pune"), cheesedb.Leg{a}, cheesedb.Leg{b}) // from A, or from B
    m2, _ := alice.Remember([]byte("Pune is rainy"), cheesedb.Leg{c})
    m3, _ := alice.Remember([]byte("carry an umbrella"), cheesedb.Leg{m1, m2})             // needs M1 and M2
    
    removed, _ := alice.Recall(c, cheesedb.Fair, cheesedb.Untrusted) // the weather tool was wrong
    // removed == [m2, m3]; m1 survives because it does not depend on C

    Install the command-line tool with go install github.com/govinargo-labs/cheesedb/cmd/cheesedb@latest. It has one command per feature, including cheesedb find, which searches the raw bytes of the files to prove erased text is gone. Needs Go 1.26.5 or newer. Developed on macOS and tested on Linux in CI. Windows isn’t supported yet.

    Faster at recall and reads. Much slower to start.

    The same data on all four engines (including Neo4j, a graph database), with every write flushed to the physical disk before it is acknowledged.

    Recall that removes 10,000 memories

    CheeseDB5.5 ms
    SQLite235 ms
    PostgreSQL1.82 s
    Neo4j67 ms

    Log scale.

    Read one memory by id

    CheeseDB1.6 µs
    SQLite7.0 µs
    PostgreSQL44 µs
    Neo4j179 µs

    Log scale.

    1M memories per agentCheeseDBSQLitePostgreSQLNeo4j
    Recall that removes 10,000 memories5.5 ms235 ms1.82 s67 ms
    Recall that removes 20 memories4.8 ms7.1 ms10.5 ms6.8 ms
    Read one memory by id1.6 µs7.0 µs44 µs179 µs
    Durable write (one memory)4.7 ms5.3 ms6.6 ms6.3 ms
    Startup, ready for the first query667 ms0.1 ms2.7 ms1.0 ms¹
    Disk per memory314 B249 B546 B496 B
    RAM per memory while open205 Bpage cache onlyshared buffers onlyJVM heap and page cache

    Median of 3 runs on an Apple M1 Max (macOS, internal SSD), SQLite 3.53, PostgreSQL 17 and Neo4j Community 2026.10. PostgreSQL and Neo4j ran over TCP on localhost. Writes are close because the disk flush dominates for every engine. SQLite is smaller on disk and starts far faster. ¹ A new connection to a server that is already running; booting the Neo4j server took 6.5 to 8.2 s. The Neo4j model and queries are ours, and an expert may do better. Method and caveats are in BENCHMARKS.md.

    When to use it, and when not to.

    Use CheeseDB when

    • Your agents learn from sources you can’t fully trust.
    • You need to remove a source and everything declared to depend on it, however deep the chain goes.
    • Erased text must really be gone from the files.
    • You want each agent isolated by design, not by a filter in your code.
    • You want an embedded Go library with no server and no dependencies.

    Use something else when

    • You need queries or search. CheeseDB looks up by id only, so keep search in your vector store and check each result against CheeseDB.
    • Startup time matters with very long histories: about 0.7 s at 1M memories. Snapshots are planned.
    • An agent’s memory graph won’t fit in RAM: about 205 bytes per memory.
    • You need production guarantees today. This is a preview.

    A real run.

    The same story three ways, with a real LLM: a plain memory list, CheeseDB with Untrusted, and CheeseDB with Erase.

    Output of the cheesedb demo command. An agent learns from three sources, then the weather tool is removed three ways. A table compares whether the model still uses the bad data and whether the bad text is still in the files.
    With a plain list, the model still answers from the bad forecast. With Untrusted, it stops using it, but the text stays in the files. With Erase, the text is gone from the files too.

    Read this before you rely on it.

    Preview v0.1.0: not independently security reviewed.

    • The API and on-disk format may change before v1.0.
    • Tamper-evident, not tamper-proof. Anyone who can write the files can rebuild the hash chain. Keep the head fingerprint that Verify() returns somewhere else and compare it later.
    • Isolation stops mistakes and prompt injection, not the host program. Whoever holds the store or the folder can read every agent’s files.
    • Legs are declared by the caller. If a memory was really learned from a source but no leg says so, recalling that source won’t remove it.
    • Only Erase removes text from disk. Untrusted hides memories until you run Compact(). Backups and copies keep their data.
    • Damage near the end of an active log is cut off and reported in Report.DroppedBytes, so check it after opening.

    The full list is in the README and the threat model.

    Why cheese?

    Lot 4821 milk from farm 7

    “Lot” and “recall” are food-safety words. A cheese maker records which lots of milk went into which wheels, so when one lot turns out to be contaminated, they recall exactly the cheese made from it.

    CheeseDB does the same for what an agent remembers. The name also nods to the Swiss cheese model of safety: stack several layers of protection, and harm only happens when the holes in every layer line up.