Inspiration
I wanted a personal brain that clocks out.
Cognee already is one, and their scoreboard is real: 0.93 human-like correctness and 0.84 F1 on their HotPotQA head-to-head, and 79% on BEAM at 100k tokens against a Llama-4 RAG baseline of 32.3%. I wanted that memory, and I wanted it to scale down to zero, not only up.
Cognee keeps the graph in a long-lived Python process on Kuzu or Neo4j and LanceDB. You pay for the hour, not the question. One bad deploy takes the whole brain down. It does not fan out and it does not go to zero. And a Cloudflare container that boots it loses the disk when it sleeps, so the graph does not persist either.
So the ask was narrow: the same memory, with no process billing me for sleeping. The joke in the pitch is that I rebuilt it in about twenty minutes. The rest of this is what "rebuilt" actually meant.
What it does
cloudnee is a memory system that lives entirely on Cloudflare bindings. Nothing is provisioned while nobody is asking.
remember(page)— a Workflow hashes the Markdown in R2, extracts, writes the graph as rows, embeds, and exits.readCurrent— a phone number or a name reads D1 and stops. No vector index in the path.recall(query)— Vectorize top-K (about 100), then a triplet score, then D1 drops superseded rows and private paths, and the top 5 reach the model.improve(edges)— a 1–5 rating is queued and an hourly cron appliesold + alpha × (rating − old).
It implements five ideas from Cognee 1.6.0: a stable entity id, one extraction per chunk, triplet ranking, a feedback average, and a permission check.
The identity work is the part that matters for a real business. Punctuation-stripped names merge on their own, so Mrs. Tan and Mrs Tan are one person. A phone number does not merge on its own — it becomes the same person only because the caller writes an alias row in the same request, so 6591234567 opens Mrs Tan's file. Functional relations are superseded in SQL: the old price 180 stays as history with a superseded_by, and the answer says 160. audience: public never returns a path under customers/.
It runs at cloudnee.vasanth.cloud, and a Strands TypeScript agent uses it at strands.vasanth.cloud, where /brain renders the live graph.
How we built it
The Worker binds D1 for entities, edges, and aliases; two Vectorize indexes (384 dimensions, cosine) for entity names and edge text, both metadata-indexed on orgId, kind, and the row id; R2 for the Markdown page; Workers AI for embeddings (@cf/baai/bge-small-en-v1.5); a Workflow (cloudnee-cognify) that does the extraction so the model wait does not burn Worker CPU; and an hourly cron that flushes the feedback queue.
The ranking is Cognee's: score = (source distance + target distance + edge distance) × (2 − importance), with the feedback blend off by default, exactly as Cognee ships it. Default importance is 0.5, a missing distance uses a 6.5 penalty that is not blended with feedback, and the smallest score wins.
The agent is the Strands TypeScript SDK in the same isolate, behind nodejs_compat. I wrote a MemoryStore adapter — search() calls recall, add() calls remember — and dropped it into memoryManager, which gives the agent add_memory and search_memory plus per-turn injection. Over HTTP the same adapter is two fetches.
There is also a local library path: Node's built-in SQLite stands in for D1 (same SQL), and an in-process vector index stands in for Vectorize, including hiding fresh points for 60 seconds the way a real upsert can lag. That is what the tests and the benchmark run against: 22 tests, 0 failures, and nine acceptance jobs for a fictional HVAC desk. Apache-2.0, with the Cognee formula credit in NOTICE.
Challenges we ran into
Python Strands does not install on the Workers Python runtime. pydantic-core has no emscripten wheel. Leave the name unpinned and the install "succeeds" as a 5 KB stub at version 0.0.1, which is worse than failing.
The harness wants a filesystem. node:fs will not bundle into an isolate. The TypeScript SDK does, at 2.3 MB (436 KB gzip), and I confirmed a full tool loop finishing inside workerd on 21 September. So the loop lives in the Worker, and a container exists only for the one tool that needs a shell — and that still needs Docker on the laptop to build the image.
Two bugs that only exist on real Cloudflare. Asking Vectorize for topK: 100 with returnMetadata: "all" returns VECTOR_QUERY_ERROR 40025, because "all" caps topK at 50 — so recall 500'd despite passing locally as "indexed" with metadata indexes. And replaceEdges opened a transaction with db.exec("BEGIN"), which hung the cognify write step forever on D1 and left workflow instances sitting in "running"; the fix is D1's batch.
A fresh write can be ahead of the index. Vectorize lags the row, so if the answer waits for the index, the other chat still says 180. The read goes to D1, and the vector is only a candidate list.
Extraction is lossy and needs a subject. A first-person sentence with no subject extracted zero edges. A vague note produced the junk triplet Vasanth lives_in Vasanth's home. The adapter now injects the # Name heading.
Accomplishments that we're proud of
The nine acceptance jobs all pass, and they specify behavior rather than accuracy. Four words still open her file. Two filenames are one person. "HVAC" finds the aircon page through the ontology alias. East plus mornings plus an open visit selects the right person out of three. The old price stays and the answer says 160. A used rule outranks an unused one (1.35 ahead of 1.5). An edited page is the next read. A second chat sees 160 before Vectorize has caught up. And the public website never receives the customer's file.
Verified end to end against the deployed Worker: remember → cognify → the D1 rows Mrs Tan lives_in Bedok and Mrs Tan quoted_price $160, recall ranking them at 29.25, and GET /v1/current?who=6591234567 returning both through the phone alias. The graph view paints the live counts, and a second Strands agent with no shared history answered "your favourite coffee is kopi-c" from the graph alone.
And on the point of the whole thing: idle is $0. There is no process to keep warm and no volume to mount.
What we learned
Retrieval incompleteness is the failure mode, not the reasoning step. An independent testbed (arXiv 2601.07978, LoCoMo) split the systems into two clusters — mem0, plain RAG, and full context at 77–81%, graph systems at 55–56% — and the authors put the gap on retrieval, not on the model. That is exactly why the Markdown page stays the record and the current fact is read from SQL. The vector is a hint; it never wins an argument.
A single judge score is also not the story. LightRAG leads the human-like judge at 0.96 and collapses to 0.09 F1. One number, either way, is a brochure.
And the boring one: not provisioning anything is a feature you can hand to a customer, not just a cost line.
What's next for cloudnee
- Contradiction detection. The column exists and the pass is off, matching Cognee's default. Turning it on is the next real memory feature.
- Page search. Chunk summaries were deliberately skipped; page retrieval is a separate job from the graph today.
- More than one SQL hop. Recall is the top few triplets plus a single hop. Cognee can walk further, and some questions need it.
- Read-your-writes over HTTP.
rememberreturns 202 while the Workflow runs, so a new fact is not immediately searchable over the API. The immediate path isGET /v1/current; the API should return it too. - Better entity merge beyond normalized-name equality and 0.85 cosine on name vectors.
- The container path for shell tools, once we are not building images on a laptop without Docker.
- Wire it into a real desk. cloudnee came out of LintLabs' work on biskt, an AI workspace for Singapore SMBs, where the same shape applies: customer facts distilled into Markdown a person can edit, a phone number that resolves to a name, and a price history that keeps its old row.
Built With
- ts
Log in or sign up for Devpost to join the conversation.