supermemory-dbresearch · 2026
essay · supermemory research

What a database built for agents looks like

I'm building a database for agent memory. This post is the argument for why that should exist, what it looks like, and the number that would prove me wrong.

The workload

An agent accumulates memory the way a person does: constantly, cheaply, mostly without looking back. Writes dominate reads. The tenant count is enormous — every user of every app gets an agent — and at any moment 95% of them are doing nothing. Each tenant is small: kilobytes to a few hundred megabytes. And relevance is temporal: yesterday's fact usually beats March's, except the March fact that's the only one of its kind, which has to stay findable forever.

Price that on anything that exists and something breaks. RAM-based vector databases charge for the idle 95%. Postgres runs out of connections long before it runs out of rows. Object-storage engines — the right answer for big, cold search — pay 400–900 ms of roundtrips when a long-tail tenant wakes, and turbopuffer's own tradeoffs page disclaims exactly this write pattern. The memory pipelines meter writes themselves: Zep bills a credit per 350 bytes because an LLM sits in their ingestion path. Nobody's wrong. Every one of those systems is priced correctly for a different workload. That's what an unserved workload looks like.

Workloads earn databases

This keeps happening. Observability broke Elasticsearch, so Honeycomb built Retriever and Datadog built Husky. Eval traces broke Postgres-plus-warehouse at Braintrust, so they built Brainstore. OLTP narrowed to debit-credit for a whole class of business, so TigerBeetle collapsed the schema to two entities. Kafka's real cost turned out to be cross-AZ replication, so WarpStream deleted the brokers and Confluent paid ~$220M thirteen months later.

You earn a purpose-built database only when specialization lets you delete something a general engine cannot.

Two things keep that lineage honest. Every case has a headline number and an honest number — Brainstore's "80×" is a best case over a 2.5–4× median; TigerBeetle's "1000×" benchmarks independently around 6.6×. The credible builders publish both, which is why my landing page says 1.3–2× and not 10. And sometimes the specialization you're about to build is already on a shelf — plenty of companies thrive on stock ClickHouse today.

For memory, the twist is that the thing you delete isn't throughput. Per tenant, memory is small — 100K vectors fits in RAM on a $5 VPS, and any per-tenant performance claim for a custom engine is a Ferrari engine on a go-kart track. What gets deleted instead: the always-on cost, the tenant-leak risk, and memory hygiene as your application's problem. The industry already believes the substrate half of this — Turso names per-agent databases as the canonical use case, turbopuffer proved 250M+ namespaces is a business. The open question is whether memory semantics belong in the engine. That's the research project.

One database per agent

Why Durable Objects and not Postgres is the first question every database person asks, and the honest answer starts with a concession: pgvector would be simpler and gets ~99% recall out of the box. I rejected it over two constraints no shared cluster meets: idle tenants must cost nothing, and tenant count has to scale past where connection pools and shared-index tuning collapse. One namespace = one Durable Object = one SQLite file. Isolation is physical — a GDPR delete is deleting a file — the index is tuned per tenant, and the runtime hibernates to zero between conversations. The bet, plainly: the storage engine is not the differentiator; the memory lifecycle is, and this substrate makes storage cheap enough to spend everything on that. Equally plainly: it's a bet on one vendor's pricing model, and durability is single-region today.

Time as a fact, policy as a query

The part I care most about is thirteen bytes. Every entry in every posting carries a header — [vector_id][entity_id][t0][flags] — and that t0 is the record's decay clock, stored where the scan kernel can see it. At query time the namespace's decay policy becomes a multiplier applied during candidate selection, not as a rerank afterward. The difference isn't cosmetic: apply decay after selection and a decayed-but-correct top-K member never makes it out of the scan.

Because the index stores the timestamp as a fact and applies the policy at query time, nothing about the policy is persisted. Change the decay rate and the next query behaves as if the index had always been built that way — zero reindexing. Reinforcing a memory is a four-byte in-place patch. There's a floor at 0.1 so demotion is bounded at 10× — the ancient-but-unique memory stays findable, and exact decayed top-K stays provable with bounded over-fetch. The philosophy in one line: decay is relevance, deletion is consent, and reads never become writes.

Honestly scoped: turbopuffer ships rerank-stage decay expressions today and controls its own index, so scan-stage decay is maybe two quarters of work for them. If they build it — welcome. It's the right way to rank memory.

An index format inside someone else's database

That's what this is, on purpose. SQLite owns the page cache, the WAL, and recovery; I refuse to reimplement, worse, the things it demonstrably does well. What I own is the layout: postings packed 32 entries to a row because Cloudflare bills by rows read (~270 per query instead of ~25,000 — the same move as designing around the object-store GET, different quantum). The vectors table is WITHOUT ROWID, keyed so a cluster is one contiguous B-tree range. The flush is snapshot → pure planner → one transaction, with the invariants — no vector lost, no duplication, crash recovery — model-checked in TLA+.

The graph is an edges table, and that's fine

Eight typed relations — updates, contradicts, derives, merge — live next to the vectors, so "why does the agent believe X" is a traversal over provenance in the same file, same transaction. Zep pays a network hop to a rented Neo4j cluster on every retrieval; their published P95 is 148–168 ms. Mine is a table scan away from the postings.

Where they're genuinely ahead: Graphiti extracts entities and invalidates facts automatically, with bi-temporal validity I don't have yet. My engine creates zero edges on its own — you supply the sentences, it supplies the vocabulary and the traversal. That's the right trade for agents emitting structured facts (tool calls, events, decisions) and the wrong one for raw conversation. Zep is a memory pipeline that rents storage; this is the memory engine a pipeline like that should run on.

What I've measured, and what I haven't

The number I'll defend without qualification: 100% recall@10 at 5.6 ms p50, end-to-end over HTTP, at 10K vectors. Everything bigger — 98–99.6% at 28–50 ms at 100K — is local, single-thread, one dataset: promising evidence, not proof. There's no backpressure yet if a tenant outruns the flush. The cold-tiering economics are designed, not wired. A namespace ceilings around 1M vectors.

And the story that tells you how this project actually operates: for several weeks two adapter bugs silently corrupted every flush, and recall sat at 16.3% until my own harness caught it. The same harness later demoted my sign-bit quantizer and published a 0.52 worst case against my own 1-bit mode. Reporting against yourself is just how you build a database.

The number that kills it

The kill test

Run the same agent traces through decay+revision-aware recall and through naïve recency-boosted pgvector, and measure downstream task success — not retrieval metrics. If the memory semantics buy less than ~5%, the thesis is dead and what's left is a cost story that belongs on rented storage. I'm running this in weeks, not years, and publishing it either way.

Logs got Husky. Evals got Brainstore. Accounting got TigerBeetle. Streams got WarpStream. I think agent memory is next — one database per agent, with decay, revision, and forgetting as primitives. If the numbers hold.