
Huygens: a SurrealDB graph as memory for agents
A coding agent remembers nothing from yesterday. Every session starts from scratch: you explain the project again, the decisions you already made together and why that other option was discarded. The obvious answer is to give it memory, and the interesting question is what shape that memory takes.
Huygens was my answer for two months: an MCP server over a typed graph in SurrealDB, so Claude Code, Codex or the agent of the moment could read from and write to the same memory. Today it's frozen. This post tells why I built it that way, what I learned about SurrealDB along the way, and what the data point was that made me stop it — which has nothing to do with the technology.
Why a graph, and why SurrealDB
It started with MongoDB and an ORM on top, taking for granted that the domain was document-based: notes with embeddings and hybrid search. During the data model design, the thing that reordered the project appeared: this isn't a document store, it's a topology. That one note supports another, refutes it or is blocked by it isn't document metadata: it's the information. And in Mongo that lives as a schema-less array of references, with the rules held up by hand in the code.
The pivot was to SurrealDB, and I chose it over the usual suspects for a concrete reason in each case. Neo4j is the canonical graph database, but it was born before vectors mattered and carries them as an add-on. FalkorDB is fast and aims at AI, but the engine doesn't force a relationship to be of a specific type. Memgraph solved the problem with a license and an enterprise weight that made no sense for a single user. SurrealDB gave all three things in the same schema: typed relationships the engine validates, native vectors and change history.
The domain is topological; holding the invariants in code was fragile.
What SurrealDB does well, and what it costs
The best thing, by far, is that relationships have a schema. You declare that a relationship goes from this type to that one and the engine rejects everything else. In a memory written by an agent that's not a convenience: it's the difference between a queryable graph and a pile of made-up edges that look plausible. It's exactly the same lesson I took from the closed vocabulary in the knowledge corpora, except here the database enforces it instead of a custom validator.
The cost is paid in three currencies. The first is maturity: the ecosystem is young and the driver changed incompatibly between major versions while the project was alive. The second is that its query language is a niche one, and that matters more than usual here — models know SQL and Cypher much better, so the very agent that was going to use the memory wrote worse queries against it. I mitigated it by encapsulating everything behind MCP tools, which is what I ended up doing anyway.
The third was the one I didn't see coming. Two of the capabilities I had chosen the engine for —querying the past and reading the change log— depend on the storage engine underneath, and with the default one they simply didn't work: the log always came back empty. There was an entire decision document implemented against a feature that didn't exist in that configuration. It was fixed by migrating to another storage engine with explicit versioning, but the lesson came earlier: with a young engine, the feature sheet belongs to the engine, not to your deployment.
The two ideas that were worth it
The first is separating evidence from interpretation. What was literally said goes raw into an inbox and stays there, intact. The synthesis on top of that —what I understood, what I decided— is something else, with its own place, and points back to the capture it came from. When the writer is an agent, that boundary is the only thing that later lets you tell what happened apart from what the model interpreted as happening.
The second is that the agent doesn't write to the graph: it proposes. Every mutation goes through a visible proposal that someone approves before anything is touched, and the graph records which proposal each edge came from. That enabled a check I'm still proud of: the live graph must be exactly the result of replaying the decision log from scratch, and there's a test that verifies it field by field.
- capture during the day
- inboxraw, intact
- deliberate processing session
- approved narrative report
- visible mutation proposal
- commit to the graph
The data point that stopped everything
One night in June, the volume holding all of Huygens' memory was deleted and recreated empty. Notes, blocks, captures, proposals, edges: everything. A month later, the project's container wasn't even running on my work machine.
What matters isn't the loss. It's that I didn't notice. No project stalled for lack of querying the graph, no commitment broke, no decision was made worse. A system that called itself "trusted memory" lost one hundred percent of its content and the perceived cost was zero.
That says more than any architecture critique: the daily loop never closed. Huygens never became the place I look before deciding. And no engine fixes that.
The audit, with numbers
With that suspicion hanging over me, I counted the system instead of opining about it. For a single-user personal memory there were almost twelve thousand lines of production code between server and panel, thirty-six tools exposed to the agent and fifteen tables. That's the surface area of a product with a team behind it, not of a personal utility.
And looking at where that volume sat revealed the real diagnosis: it wasn't one system, it was three glued together. A knowledge memory, a complete productivity system on top —today's tasks, due dates, deferrals, states— and on top of that a layer that taught the method, with rules for not being a nuisance. Each layer was well built. The mistake was one of order: I built the third on top of the second on top of the first without any of them having proven daily use yet.
The code confirmed it mercilessly. The productivity layer tests are solid and their content is made up: not a single case that mimics a real day. The piece the documentation itself flagged as the bottleneck —breaking a project down into the next concrete action— wasn't even an executable tool, just a text served to the agent, without a single test. The memory layer, on the other hand, did have traces of real use: seven-hundred-some inherited edges that had to be recovered and re-traced.
And the temptation to flee to Markdown
Around that time the argument was going around that all of this is unnecessary and that a wiki of Markdown files the model navigates on its own is enough. It's a good critique and I partly share it: a vector database that rediscovers the world with every question, instead of accumulating already-compiled knowledge, is a bad deal.
But moving to Markdown at that point would have been the same mistake looking at itself in the mirror: choosing architecture before having evidence of the habit that architecture is meant to sustain. Huygens bet that the habit would arrive with the tool, and it didn't. Migrating on faith leaves you, three months later, with a Markdown repository as dead as the database, only cheaper to have built. Cheaper is already an argument in favor; it isn't the same as having solved the problem.
What I did and what survives
Not rewriting anything. Freezing Huygens —which cost nothing, because the database was already empty and never reached the cluster— and setting up in parallel the minimal file-based version, operated by the agent with plain read and write. With one condition: the exit criterion written before starting, not thirty days in. The code, frozen as-is, is still public in the Huygens repository.
If a month from now there are living pages I consult before deciding, the habit exists, Markdown is enough and Huygens gets archived without drama. If a month from now the wiki is as dead as the database ended up, then the problem was never the architecture, and the question to ask isn't an engineering one.
Four things travel no matter what, and none of them needs an engine: separating evidence from interpretation; nothing consolidating without someone's approval, even if the wall is a pull request instead of software; capturing without ceremony and processing deliberately; and every compiled page being able to point to the capture it came from.
What I take away
About SurrealDB: that for a typed-relationship domain it delivers what it promises, and that its price isn't performance but youth — expect the engine's feature sheet not to describe your deployment, and expect your own agent to write worse in a language it has barely seen.
And about agent memory, something that applies to a client as much as to me: the hard part isn't storage. It's that there is a moment in the day when someone looks there before deciding. I built thirty-six tools for a loop that wasn't closed, and the proof it wasn't is that I lost all of the memory and didn't notice. Now, when someone asks me for a knowledge base for their agents, the first question is no longer which engine: it's show me where that knowledge lives today and who looks at it.
Personal memory MCP: graph in SurrealDB, autonomous worker in Agno and typed errors end to end between TypeScript and Python.
TypeScriptMore from the lab

Let the knowledge graph be built by the person who knows the domain

How to build your own knowledge graph with quartz-okf

LLMOps on your own cluster with LiteLLM, Langfuse, and ArgoCD

Coder: remote development environments with devcontainers for the whole team

Postiz: scheduling social media posts from your own server
