supermemory-dbresearch · 2026

supermemory research

A database designed for agent context.

Every agent gets its own database: one SQLite file with vector search, full-text, a fact graph, and time decay built into the index. Writes land in 0.6 ms. Idle agents cost nothing.

Agent memory is a weird workload. Millions of tenants, 95% of them idle at any moment, write-hot when active, small per tenant — and ranked by time as much as by similarity. Nothing prices that shape correctly: RAM-based vector databases charge for idleness, Postgres runs out of connections around 104 tenants, object-storage engines pay a ~900 ms cold tax on the long tail and disclaim write-heavy workloads in their own docs, and the memory pipelines put an LLM in the write path and bill per byte written.

So I'm building the database I think this workload deserves. Each namespace — a user, an agent, a project — is its own embedded SQLite database inside its own Cloudflare Durable Object: a SPANN vector index, BM25, metadata filters, and a typed fact graph in one file, one process, one transaction.

0.6 ms
Write ack — durable, immediately searchable
local, self-run
100% @ 5.6 ms
Recall@10, end-to-end over HTTP
10K vectors, warm
~270
Rows read per query, vs ~25,000 naïve
how rows-read billing stays cheap
$0
Idle agent; ~$0.33/mo per 1M vectors hot
my cloudflare bill, not a price

How it works

writesCheap by construction

An insert is 2–3 row writes into a durable buffer, then it returns. A background flush every 30 seconds does the actual indexing — assigns vectors to clusters, packs postings, splits and merges. It's an LSM. Fresh writes are searchable immediately, exactly, before they're ever indexed.

readsFilter, scan, rerank

Filters run before scoring, never after — a selective filter skips the ANN index entirely and scores exact, so filtered recall is 100% by construction. Everything else: scan compact codes across the probed clusters, rerank the survivors in full precision.

timeDecay inside the index

Every index entry carries its own timestamp. Decay is applied during the scan, so the candidate set itself is time-aware — not a rerank over whatever an oblivious index returned. Change the decay policy and the next query behaves as if the index was always built that way: zero reindexing. Reinforcing a memory is a 4-byte patch. And decay never deletes — forgetting is always explicit.

What's different

One database per agent. Isolation is physical, not a WHERE clause. Deleting a user means deleting a file. An idle agent is kilobytes on disk and zero compute.

Records, not rows. The API speaks memory: document | chunk | memory | page, upsert by your id, forget, forgetAfter TTL, reinforce. Replacing a record resets its decay clock — a rewritten memory is fresh.

A fact graph in the same file. Typed edges — updates, contradicts, merge, derives — next to the vectors, so "why does the agent believe this" is a query, not a join across two systems. No LLM in the write path: writes are deterministic sub-millisecond SQL.

Designed around the billing quantum. Cloudflare bills SQLite by rows read, so postings pack 32 entries per row: ~270 billed rows per query instead of ~25,000. Same idea as designing around the object-store GET — just a different quantum.

Honest numbers

I red-teamed every claim on this page. Blended, this is a ~1.3–2× system against the best general substrate for this workload — not 10×. The ~10× is real on exactly two axes, and both are architecture, not tuning: write acknowledgment (turbopuffer publishes 165 ms p50 for their object-storage WAL and their docs disclaim chatty writes; a local SQLite insert is three orders quieter) and long-tail wake-up (no networked cold path to fall out of — their published cold p50 is 400–900 ms).

What I haven't done yet, so you don't have to find out later: my benchmarks are self-run at 10K–100K scale on one dataset — only the 10K number is end-to-end. There's no backpressure story if a tenant writes faster than the flush can drain. The cold-tiering economics are designed, not wired. And a single namespace tops out around 1M vectors — if you need one big shared index, use turbopuffer; it's excellent at that.