<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: SurrealDB</title>
    <description>The latest articles on DEV Community by SurrealDB (surrealdb).</description>
    <link>https://dev.to/surrealdb</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F4795%2F4255a3ff-99a6-436d-8e87-0ddb7cf48ad3.png</url>
      <title>DEV Community: SurrealDB</title>
      <link>https://dev.to/surrealdb</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/surrealdb"/>
    <language>en</language>
    <item>
      <title>Graph engineering is missing a graph</title>
      <dc:creator>Mark Gyles</dc:creator>
      <pubDate>Thu, 13 Aug 2026 13:08:08 +0000</pubDate>
      <link>https://dev.to/surrealdb/graph-engineering-is-missing-a-graph-6b4</link>
      <guid>https://dev.to/surrealdb/graph-engineering-is-missing-a-graph-6b4</guid>
      <description>&lt;p&gt;Author: &lt;a href="https://x.com/tobiemh" rel="noopener noreferrer"&gt;Tobie Morgan Hitchcock&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Graph engineering is having a moment.&lt;/p&gt;

&lt;p&gt;The definition going round is a good one, and I am not going to try to redefine it. Complex agents increasingly look like graphs: nodes do the work, edges decide what happens next, state moves between them. That is real, it matches what production actually looks like, and the people saying it are right.&lt;/p&gt;

&lt;p&gt;Almost nobody makes the next observation. Those agents are still fed context from flat vector chunks, a separate document store, a relational database and some ephemeral session memory. While the execution architecture became a graph, the data architecture underneath it did not.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Graph engineering is not just about graphing the agents. You also have to graph the context they operate on.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Or, less politely: you cannot graph-engineer the agent and leave its memory as a pile of chunks.&lt;/p&gt;

&lt;p&gt;This is my attempt to say what graph engineering has to mean if it is going to outlive the meme.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rename cycle
&lt;/h2&gt;

&lt;p&gt;We have been renaming the same struggle for a few years. Prompt engineering optimised instructions. Context engineering optimised what goes into the window. Harness engineering, loop engineering, and now graph engineering optimise how agents work together.&lt;/p&gt;

&lt;p&gt;I do not mind the labels. They appear when builders hit a wall. This wall is real. One agent loop in one chat window stops scaling the moment the work looks like a team: parallel reviewers, a fixer, a security check with veto power, a human gate before anything irreversible. That structure is a graph. It always was.&lt;/p&gt;

&lt;p&gt;Every one of those renames moved up the stack, and every one of them left the data layer where it was.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two graphs. Say which one you mean.
&lt;/h2&gt;

&lt;p&gt;When someone says graph engineering, they could mean one of three things and rarely say which:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A graph of loops&lt;/strong&gt;, which is really a claim about control flow: what you get when one loop stops being enough.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An execution graph.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A context graph.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The loop graph folds into the execution graph, so two are left standing, and most threads blur even those. Sometimes people mean both. They almost never say so.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fet6cf1xni6qvbzb0e475.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fet6cf1xni6qvbzb0e475.png" alt="execution graph" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The execution graph
&lt;/h3&gt;

&lt;p&gt;What runs next, in what order, with what rights. It determines what your agents &lt;strong&gt;do&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Nodes are work: a classifier, a tool call, a coding agent, a reviewer. Edges are transitions: success, fail, escalate, fan out. State is the scratchpad the system mutates. Verifiers sit on a separate path when the stakes are high. Stop rules exist so the thing does not burn tokens until the card declines.&lt;/p&gt;

&lt;p&gt;This is a state machine with probabilistic nodes inside it. LangGraph made the case earliest and loudest, and Google's ADK, Mastra, CrewAI, Microsoft's agent frameworks, or a boring job runner you wrote yourself all live on the same plane. Useful, necessary for a lot of production agents, and on its own not sufficient.&lt;/p&gt;

&lt;h3&gt;
  
  
  The context graph
&lt;/h3&gt;

&lt;p&gt;What is true, what is related, what was true when, and who said so. It determines what your agents &lt;strong&gt;know&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here the nodes are entities, documents, events, users and assets, and the edges between them carry properties of their own: since, confidence, source. Provenance, time, permissions and hybrid retrieval all matter, because the world is not only triples and it is not only embeddings.&lt;/p&gt;

&lt;p&gt;Classic knowledge graphs and GraphRAG both sit on this plane. It gets a paragraph in most threads and a whiteboard in almost none.&lt;/p&gt;

&lt;p&gt;So:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The execution graph engineers the graph of work. The context graph engineers the graph of knowledge.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most teams have engineered the first and are still running the second as a pile of chunks in a vector store.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where they meet: the turn
&lt;/h2&gt;

&lt;p&gt;Every serious agent turn is three moves: read, think, write. Read means traverse relations, pull vectors, load history and permissions. Think means the model reasons over a complete packet, not three partial dumps from three systems that disagree with each other. Write means decisions, new entities, and memories land somewhere the next turn can actually see them.&lt;/p&gt;

&lt;p&gt;If your execution graph is elegant and your write path is "maybe the vector DB, maybe the graph DB, maybe a JSON blob in object storage," the whiteboard never became system truth. You choreographed forgetting.&lt;/p&gt;

&lt;p&gt;That is the definition I use:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Graph engineering is how you design the execution graph of agent work and the context graph of durable knowledge, then keep them consistent across every turn.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The meme covers the execution graph. Production dies on the other two.&lt;/p&gt;

&lt;h2&gt;
  
  
  Loops, graphs, context. Stack them.
&lt;/h2&gt;

&lt;p&gt;A loop is fine when one goal, one verifier, and one stop condition cover the job.&lt;/p&gt;

&lt;p&gt;An execution graph earns its keep when you need parallel specialists, explicit escalations, or policy gates.&lt;/p&gt;

&lt;p&gt;Context engineering is what you do on every node either way: put the right facts, tools, and state in front of the model at the right time. Widening the context window does not solve a wrong-context problem, it just raises the price of getting it wrong.&lt;/p&gt;

&lt;p&gt;Harnesses and frameworks host all of this. I am not here to sell you an orchestrator. We did not build SurrealDB to replace your control plane.&lt;/p&gt;

&lt;p&gt;We built it because the existing data planes under those nodes were a mess.&lt;/p&gt;

&lt;h2&gt;
  
  
  GraphRAG is one chapter, not the book
&lt;/h2&gt;

&lt;p&gt;People swap the words, but they should not.&lt;/p&gt;

&lt;p&gt;GraphRAG is a retrieval pattern: extract structure from a corpus, then use the graph, and often community summaries, to ground generation. It is a good technique, and one node in a larger system can run it just fine. You can also run GraphRAG with almost no multi-agent workflow at all. Microsoft's own documentation makes the honest version of the argument: vector-only retrieval struggles when answering a question requires connecting information across relationships.&lt;/p&gt;

&lt;p&gt;If graph engineering is going to mean anything durable, it is how the whole system moves, remembers, and stays correct. Retrieval is necessary, but it is not the architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retrieval is not three searches and a re-ranker
&lt;/h2&gt;

&lt;p&gt;Almost everyone ships the same pattern. Fire a vector search. Fire a keyword search. Maybe fire a graph query. Take fifty from each, throw the pile at a re-ranker, keep what floats, stuff it in the prompt.&lt;/p&gt;

&lt;p&gt;That pipeline has a structural flaw, and it is not latency. Each leg searches blind to the constraints the other legs know about. The vector index does not know this question is about one customer's contracts; it knows only that a question-shaped vector is near some passage-shaped vectors. So it returns fifty candidates and hopes. The keyword leg does the same. The graph leg returns a neighbourhood with no idea which parts are relevant to the sentence that was asked. Then a re-ranker, which can see the text and none of the structure, is asked to reconcile three lists that were each built without the others' information.&lt;/p&gt;

&lt;p&gt;You over-fetch because that is the only defence against three blind legs. Over-fetching grows your token bill. Then you pay a second time, because the pile you carried back is mostly padding, and padding in the context window is not free: it costs money on the way in and accuracy on the way out, since a longer prompt gives the model more chances to ground its answer in the wrong sentence.&lt;/p&gt;

&lt;p&gt;Run the similarity inside the traversal rather than beside it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuw6xvlko0vnmp8106qq8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuw6xvlko0vnmp8106qq8.png" alt="three blind legs" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If embeddings are a field on the entity, and full-text indexes sit on the same rows, and relations are edges between those same rows, then one query expresses the whole thing: start from what the question is about, walk the relations that matter, and score by similarity and text relevance as you go, with permissions and time bounds applied in the same pass. Roughly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;stated_by&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;source&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;provenance&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;entity&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;works_at&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;acme&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;valid_until&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;NONE&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;|&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The structure prunes before similarity scores, and similarity ranks within what the structure allows. You are not searching a corpus and hoping the answer's neighbourhood comes with it. You are searching a neighbourhood you already have reason to believe in.&lt;/p&gt;

&lt;p&gt;That buys two things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fewer tokens.&lt;/strong&gt; You stop over-fetching, because a constrained candidate set does not need a wide net to be safe. You carry back a small precise set instead of three large ones minus whatever the re-ranker threw away, and you stop paying for the same fact three times because three stores each returned their own copy of it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Higher accuracy.&lt;/strong&gt; Every filter that was inexpressible in the vector leg (this entity, this scope, this time window, this permission, currently true rather than superseded) becomes expressible, because it is evaluated in the same statement as the similarity. Constraints you could only apply after ranking are now applied before it.&lt;/p&gt;

&lt;p&gt;Our own numbers say the same thing from the other direction. When we were first developing Spectron, our memory layer, we tried the obvious thing: reduce each extracted fact to a short sentence, embed it, and add it as another dense leg in the hybrid mix. It fixed roughly a hundred benchmark questions and broke roughly as many again, landing slightly negative and inside the noise. Meanwhile the two changes that clearly won were both structural: routing from a resolved entity into its typed facts, and expanding a hit into its conversational neighbours. Both worth a couple of points on their own.&lt;/p&gt;

&lt;p&gt;The lesson we took: reducing a typed fact to a sentence and matching it by cosine throws away exactly the structure that made extracting it worthwhile. A similarity list also cannot abstain. It always returns its k, so when the right answer is not there it returns something else with total confidence, and something else lands in the prompt. Structure can return nothing, which is frequently the correct answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  A context graph is not an extraction job
&lt;/h2&gt;

&lt;p&gt;The obvious objection: fine, &lt;em&gt;so I bolt a graph database onto my stack and run an extractor over my corpus.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You can. What you get is a pile of assertions in a vocabulary the model improvised one turn at a time, and the difference shows up months later as retrieval that cannot find things you know are in there.&lt;/p&gt;

&lt;p&gt;Here are the numbers from one conversation we ingested end to end. Of the attribute keys the extractor produced, &lt;strong&gt;85% were used exactly once&lt;/strong&gt;: 236 distinct keys across 325 facts, an average of 1.4 uses per key. Action verbs were nearly as bad, 80% used once. The two most common verbs in the whole set were &lt;code&gt;shared_image&lt;/code&gt; and &lt;code&gt;shared&lt;/code&gt;, describing the same act, counted separately.&lt;/p&gt;

&lt;p&gt;Now consider what that does to a query. &lt;code&gt;WHERE key = 'residence'&lt;/code&gt; finds nothing if this turn's extraction called it &lt;code&gt;lives_in&lt;/code&gt;, and the next one called it &lt;code&gt;home_city&lt;/code&gt;, and a third called it &lt;code&gt;current_residence&lt;/code&gt;. The structured path, the one that was supposed to make the graph worth building, stays dark. Retrieval falls through to similarity, and you are back to the pattern in the previous section, paying for a graph you cannot query.&lt;/p&gt;

&lt;p&gt;The interesting part is the control case. One relation family in that same measurement was &lt;strong&gt;not&lt;/strong&gt; fragmented: 4.3 uses per label instead of 1.4, only 40% singletons. It was the one family where the extraction prompt already showed the model the vocabulary the system had established, with an instruction to reuse an existing label and only mint a new one when nothing fits. Same model, same text, same run. The only difference was whether the graph told the extractor what it already knew.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F114530s6m70r4jsod51u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F114530s6m70r4jsod51u.png" alt="same model" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A context graph is a loop with itself, not a function over text.&lt;/strong&gt; Feed the established vocabulary back. Canonicalise predicates at write time so two spellings of one idea land in one bucket. Resolve mentions against entities that already exist, rather than minting a new node for every phrasing, because the same customer becoming five nodes means every traversal lies.&lt;/p&gt;

&lt;p&gt;Some hard-won specifics, since this is where the work actually is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There is no correct base form a priori.&lt;/strong&gt; We tried folding inflections and the naive rules are landmines: strip a trailing &lt;code&gt;e&lt;/code&gt; and &lt;code&gt;care&lt;/code&gt; merges with &lt;code&gt;car&lt;/code&gt;; undouble a consonant and &lt;code&gt;roll&lt;/code&gt; merges with &lt;code&gt;role&lt;/code&gt;; strip a trailing &lt;code&gt;s&lt;/code&gt; and &lt;code&gt;news&lt;/code&gt; becomes &lt;code&gt;new&lt;/code&gt;. Any stemmer aggressive enough to bucket reliably produces non-words that then surface in your API responses. What worked was narrower and duller: only ever fold onto a spelling this graph already uses, never toward an invented lemma.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Semantic merging is a judgement about meaning, not a normalisation.&lt;/strong&gt; &lt;code&gt;home_city&lt;/code&gt; and &lt;code&gt;city_of_residence&lt;/code&gt; probably mean the same thing. "Use metric units" and "always answer in metric" probably do too. Fold them wrongly and you have discarded something a user explicitly said. We left that undone deliberately, and said so, rather than pretending a string function had settled it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time is two axes and they are not the same axis.&lt;/strong&gt; When a fact was stated is not when the thing happened. If you store one and render it as the other, "when did X happen" gets answered with when it was mentioned, confidently and wrongly. Relative phrases are worse: "last week" is a window, not an instant, and letting the answering model do calendar arithmetic at read time from whatever dates happened to be in its context is exactly the failure you are trying to remove. Resolve at write time, against the source's own date, and store what was actually stated. Store nothing when nothing was stated, so that "no event time" and "the event time equals the ingest time" stop being the same value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dates do not belong in the text you embed.&lt;/strong&gt; We had resolved windows interpolated into the fact sentence that was both the keyword document and the embedded vector. Those date tokens say nothing about what the fact means, and they pollute the retrieval key for every query. Dates live in columns. Renderers read columns. Text and columns cannot disagree if there is only one copy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Half of what extraction produces is not linked to anything.&lt;/strong&gt; In our measurement, 52% of action objects were free text rather than a resolved entity: the graph recorded that somebody did something to a string. That string is invisible to traversal. Extraction reported success. The graph got nothing it can walk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An edge nothing reads is not memory.&lt;/strong&gt; We shipped an alias edge, wrote a careful writer for it, and then pulled it, because no retrieval path traversed it. It was structure with no consequence.&lt;/p&gt;

&lt;p&gt;All of this decides whether the context graph means anything. Meaning cannot be extrapolated from a graph that was never normalised, because the meaning was discarded before the write. And none of it is a one-time import: entity resolution and temporal invalidation are standing jobs, running every turn, or "what is true" decays into "what was true in March."&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually breaks
&lt;/h2&gt;

&lt;p&gt;Skip the forty-agent overnight graph. That is how you max a bill and learn nothing.&lt;/p&gt;

&lt;p&gt;Here is what fails.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;State lives only in the transcript.&lt;/strong&gt; You cannot debug it, audit it, or resume it cleanly if that is the only place it lives. IDs, permissions, and intermediate facts need a home outside the prompt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Everything is agentic.&lt;/strong&gt; Money movement, PII, deletes, external side effects: those edges should be code, not model judgement. Give the model freedom where exploration actually pays off, and encode the paths you already know are correct.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verifiers share the same spoiled context.&lt;/strong&gt; Agents on the same model, reading the same flawed packet, agree with each other at industrial scale, which is why verifier context needs to be separated and drawn from evidence outside the graph: tests that ran, money that moved, a human weighing in before the expensive push. Organised nonsense is still nonsense.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You drew a DAG and production needed cycles.&lt;/strong&gt; Retries, clarification, a human pausing and resuming the flow: these are not a design smell, they are how work actually works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your context graph is a pile of triples with no ontology.&lt;/strong&gt; Model the domain before you extract at scale. Schema is what stops you from lying to yourself about what the graph actually means, not overhead for its own sake.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Extraction is stateless.&lt;/strong&gt; Every turn invents its own vocabulary because nothing showed it the vocabulary that already exists. This is the single cheapest thing to fix and the one most people have not fixed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The graph is built once and never maintained.&lt;/strong&gt; Extraction is the easy day. Entities need resolving, or the same customer becomes five nodes and every traversal lies. Facts need expiry, or superseded beliefs sit next to current ones and both look true.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retrieval is three searches and a re-ranker.&lt;/strong&gt; Each leg is blind to the others' constraints, so you over-fetch to compensate, pay for the same fact three times, and hand a re-ranker the job of guessing what the query planner should have known.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Write-back is optional.&lt;/strong&gt; The agent learns a preference, a failed approach, a new link between entities. If that write is not durable and visible under the same permissions as the read, the next node is guessing again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No stop rule.&lt;/strong&gt; Parallel agents without token, time, and tool budgets will happily burn through a budget without producing anything you can point to.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape everyone ships
&lt;/h2&gt;

&lt;p&gt;Most "graph engineered" demos still look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6m3oihziwgc4zxyzbd6x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6m3oihziwgc4zxyzbd6x.png" alt="five separate systems" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Four taxes, every time.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Context leaks at the seams. Relationships, history, and metadata fragment whenever they cross a boundary.&lt;/li&gt;
&lt;li&gt;The same fact enters the prompt three times, and you pay for it three times.&lt;/li&gt;
&lt;li&gt;No query can be planned across the whole picture, so every retrieval is a wide guess narrowed after the fact.&lt;/li&gt;
&lt;li&gt;Ops multiplies: five configs, five monitors, five failure modes, and the glue quietly becomes the product.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You want the context graph and the durable data to be one thing. A context graph that cannot hold your application records, your documents, your vectors and your temporal state is a copy of your real data that you will spend the next year syncing.&lt;/p&gt;

&lt;p&gt;This is why I flinch when graph engineering is reduced to "use a graph database." A graph-only store solves a real slice, but agents need documents, vectors, time, session state, and above all the ability to ask one question that is simultaneously a traversal, a similarity search and a filter. Split those across stores and that question stops being expressible at any price.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this looks like inside a bank
&lt;/h2&gt;

&lt;p&gt;Most of the graph-engineering conversation right now is about routing and reliability: how do I get five agents to hand work to each other without falling over. That is a startup's version of the problem.&lt;/p&gt;

&lt;p&gt;Ask the same question inside a bank, an insurer or a global enterprise and the requirements change shape entirely. Every enterprise conversation I have had this year has landed on some version of the same list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shared context, not per-agent context.&lt;/strong&gt; Twenty agents across four teams need the same view of the same customer. If each one has its own vector store, you do not have twenty agents, you have twenty inconsistent opinions with an org chart.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Permissions that travel with the data.&lt;/strong&gt; Not permissions checked at the API gateway and then discarded. If retrieval cannot enforce who may see which fact inside the query itself, then every prompt is a potential exfiltration path and every new agent is a new review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Provenance carried on the facts themselves.&lt;/strong&gt; Who said this, in which document, on what date, and is that source still authoritative. "The model said so" is not a defensible answer when the fact ends up in a regulatory filing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auditable retrieval.&lt;/strong&gt; Not just what the agent answered, but what it read to get there, and why those rows and not others. A re-ranker's opinion over three fused lists is close to unexplainable. A query with predicates in it explains itself.&lt;/p&gt;

&lt;p&gt;Underneath all of it sits temporal truth: what did we believe on the day the decision was made, not what do we believe now. In a regulated firm the record exists to answer that question, which means facts need validity intervals and reads need to resolve as of a point in time rather than as of now. And the graph needs a boundary in both directions, with control over what is allowed into it and over what is allowed out of it into a prompt.&lt;/p&gt;

&lt;p&gt;None of that is served by a prettier flowchart, and none of it is served by a vector store with a metadata filter bolted on. It is served by the context graph being a real database: transactional, permissioned, temporal, and auditable. The current discourse is not covering this, and it is what enterprises are buying.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we set out to build
&lt;/h2&gt;

&lt;p&gt;I am biased. I will say that up front.&lt;/p&gt;

&lt;p&gt;We built SurrealDB as a multi-model engine: documents, graphs, vectors, time-series, full-text, relational, and auth in one system with one query language over them. The reason has nothing to do with graphs being fashionable. It is that the useful query is almost never one shape. It is "things related to this, that resemble that, that were true then, that this user may see," and every system that stores those four facts separately can only answer it by approximation and reassembly.&lt;/p&gt;

&lt;p&gt;On the context side that means schema as ontology. Edges carry confidence, time, and source as fields rather than afterthoughts. Vectors live on the entities they came from, so similarity is an operator inside a traversal rather than a separate service you join against afterwards. Traversal, similarity, full-text, and filters resolve in one statement, planned together, instead of three round trips and a sync cron you forgot about until it pages you at 2am. Write-back lands transactionally under the same permission model you read from, which matters, but it is the floor rather than the pitch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcs3ab6rfyn31gmykll51.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcs3ab6rfyn31gmykll51.png" alt="Where the layers actually sit" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Spectron is the memory layer we built on that engine: entity extraction, predicate normalisation, entity resolution, temporal facts, and hybrid retrieval that is one query rather than three. Most of the engineering effort there has gone into what happens between the text arriving and the graph existing, rather than into retrieval algorithms. When the session ends, the context window forgets, but the system should not.&lt;/p&gt;

&lt;p&gt;We are also building the comparison, because I would not take my word for it either: the same multi-agent application twice, once on an orchestrator plus a vector store plus a relational database plus a session cache, and once on the same orchestrator with a single context graph underneath. Same agents, same tasks, same model. Measured on context accuracy, tokens consumed, latency, network hops and lines of glue.&lt;/p&gt;

&lt;p&gt;The control plane stays yours. We are complementary on purpose. The question I would ask is not whose orchestrator you use, but whether your retrieval can express a traversal and a similarity search in the same breath, and whether anything understood your data before it became a graph.&lt;/p&gt;

&lt;h2&gt;
  
  
  A checklist I would actually use
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Scope
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;One recurring job with a real success metric, not a general assistant.&lt;/li&gt;
&lt;li&gt;Someone owns the ontology, someone owns the workflow, and early on that can be the same person.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Execution graph
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Label nodes: code / single LLM call / tool / full agent.&lt;/li&gt;
&lt;li&gt;Edges only where work actually flows.&lt;/li&gt;
&lt;li&gt;A verifier path with fresh context for high-risk output.&lt;/li&gt;
&lt;li&gt;A human gate on irreversible actions.&lt;/li&gt;
&lt;li&gt;Explicit stops and budgets.&lt;/li&gt;
&lt;li&gt;Cycles where retries and clarification are real.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Before the context graph exists
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Entity and relation types written down before bulk extraction.&lt;/li&gt;
&lt;li&gt;Extraction shown the vocabulary the graph already uses, with an instruction to reuse it.&lt;/li&gt;
&lt;li&gt;Predicates canonicalised at write time, folding only onto spellings the graph already has.&lt;/li&gt;
&lt;li&gt;Mentions resolved to existing entities, with the unresolved rate measured rather than assumed.&lt;/li&gt;
&lt;li&gt;Event time and mention time stored as separate columns, resolved at write time, absent when nothing was stated.&lt;/li&gt;
&lt;li&gt;Nothing in your embedded text that is not part of what the fact means.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Context graph
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Provenance on facts and edges.&lt;/li&gt;
&lt;li&gt;Temporal validity, and a job that actually retires superseded beliefs.&lt;/li&gt;
&lt;li&gt;A retrieval path that constrains before it ranks, explainable in one sentence.&lt;/li&gt;
&lt;li&gt;Write-back after every turn for memories, entities, decisions.&lt;/li&gt;
&lt;li&gt;Permissions enforced inside retrieval, not at the gateway.&lt;/li&gt;
&lt;li&gt;Graph statistics on a dashboard: predicates per fact, unresolved mentions, duplicate entities. If you cannot see fragmentation, you will not fix it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Reality checks
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Outside-the-graph evidence for ship decisions.&lt;/li&gt;
&lt;li&gt;Evals that include multi-hop and time questions.&lt;/li&gt;
&lt;li&gt;Cost and latency budgets per node type.&lt;/li&gt;
&lt;li&gt;Replayable state when something fails.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Grow the graph when the work forces you, not when a meme does.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would ignore
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Renaming your orchestrator and calling it strategy.&lt;/li&gt;
&lt;li&gt;Forty agents overnight.&lt;/li&gt;
&lt;li&gt;Treating GraphRAG, context graphs, and execution graphs as the same slide.&lt;/li&gt;
&lt;li&gt;Bigger context windows as a substitute for structure.&lt;/li&gt;
&lt;li&gt;A better re-ranker as a substitute for a query that could have been planned properly.&lt;/li&gt;
&lt;li&gt;Holy wars about anyone's database. The architectural gap is enough, and the market does not need another feud.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;Graph engineering became the new buzzword because one chatty loop is not enough for real work. Good. Draw the execution graph. Be honest about where the model decides and where code decides.&lt;/p&gt;

&lt;p&gt;Then do the half most threads skip. Understand the data before you write it: one vocabulary, resolved entities, real event times, facts that expire. Put the vectors on those entities rather than in another system, so retrieval is one query that traverses and ranks together instead of three searches arguing through a re-ranker. Make permissions and provenance properties of the data rather than of the API in front of it.&lt;/p&gt;

&lt;p&gt;The label will get renamed again. It always does, and I would not build a roadmap on the word. But the two graphs are not a naming fashion. Every serious agent system has an execution graph and a context graph whether or not anyone drew the second one, and the gap between them is where the accuracy goes, where the token bill comes from, and where the audit fails.&lt;/p&gt;

&lt;p&gt;Frameworks draw the graph of work. Something has to hold the graph of knowledge, keep it true, and survive the write-back. That is the problem we set out to solve with SurrealDB, and with Spectron, the memory layer we built on top of it. If your agent topology is getting serious and your context layer is still five separate systems you are hoping stay in sync, that is the seam to fix.&lt;/p&gt;

</description>
      <category>graphengineering</category>
      <category>ai</category>
      <category>database</category>
      <category>agents</category>
    </item>
    <item>
      <title>Introducing SurrealDB Studio</title>
      <dc:creator>Matthew McFadden</dc:creator>
      <pubDate>Mon, 10 Aug 2026 18:31:32 +0000</pubDate>
      <link>https://dev.to/surrealdb/introducing-surrealdb-studio-2133</link>
      <guid>https://dev.to/surrealdb/introducing-surrealdb-studio-2133</guid>
      <description>&lt;p&gt;Today we're excited to announce the release of &lt;strong&gt;SurrealDB Studio&lt;/strong&gt;: the new app for working with your SurrealDB instances and Spectron contexts, and the most powerful way to interact with your data yet. &lt;/p&gt;

&lt;p&gt;SurrealDB Studio takes over from Surrealist as the official app for SurrealDB, carrying on its mission to provide a full visual experience for working with your SurrealDB instances and Spectron contexts. Whether you are connecting to self hosted instances, or deploying managed cloud instances, SurrealDB Studio provides a clean and intuitive interface to accomplish anything.&lt;/p&gt;

&lt;p&gt;While Studio has been built from scratch, it is designed to pick up where Surrealist left off. Your data will copied over automatically the first time you open &lt;a href="https://studio.surrealdb.com/" rel="noopener noreferrer"&gt;studio.surrealdb.com&lt;/a&gt; or the SurrealDB Studio app.&lt;/p&gt;

&lt;p&gt;Let's explore what SurrealDB Studio has to offer! 🎉&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Have you used Surrealist?&lt;/strong&gt;&lt;br&gt;
You already know the basics of what SurrealDB Studio is built upon. Feel free to skip ahead to the highlights section to see what's new for you.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What is SurrealDB Studio?
&lt;/h2&gt;

&lt;p&gt;SurrealDB Studio is the official app for working with everything in the SurrealDB ecosystem. From &lt;a href="https://surrealdb.com/platform/surrealdb" rel="noopener noreferrer"&gt;SurrealDB&lt;/a&gt; instances to &lt;a href="https://surrealdb.com/spectron" rel="noopener noreferrer"&gt;Spectron&lt;/a&gt; contexts, SurrealDB Studio gives you a visual way to do everything you would otherwise do from a terminal, client SDK, or MCP server, such as running queries, reading and editing your records, storing and retrieving memories, and designing your schemas.&lt;/p&gt;

&lt;h3&gt;
  
  
  What you can do with it
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Write and run queries&lt;/strong&gt; in SurrealQL and view the results as a table, as a graph you can click your way through, or as a live stream of events that updates the moment your data changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explore your data&lt;/strong&gt; by browsing and filtering your records, following the links between them, and editing any record you find.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design your schema&lt;/strong&gt; as a diagram you can rearrange, then click any table to edit its fields, indexes, events, and permissions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manage access&lt;/strong&gt; by setting up users and access methods at every level of your database.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Define functions, parameters, APIs, and analyzers&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manage your SurrealDB instances&lt;/strong&gt;, from deploying a new one to watching its metrics, restoring a backup, and managing your billing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build and deploy Spectron contexts&lt;/strong&gt;, including the memories and documents your agents are built on.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Try SurrealDB Studio in the browser, no setup required
&lt;/h3&gt;

&lt;p&gt;SurrealDB Studio is the official dashboard of SurrealDB. You don't need a database to get started since it can run them for you in your browser. You can also load one of our sample datasets and start querying within seconds. There is nothing to install and nothing to configure, which makes it a great way to learn about SurrealDB and Spectron, or test an idea before building it.&lt;/p&gt;

&lt;p&gt;On desktop, SurrealDB Studio can also start a local instance on your own machine at the press of a button, so you never have to open a terminal to get one running.&lt;/p&gt;

&lt;h3&gt;
  
  
  Who SurrealDB Studio is for
&lt;/h3&gt;

&lt;p&gt;Whether you are writing your first SurrealQL query, building an agent on Spectron, or looking after an enterprise-scale fleet of production instances on behalf of a team, SurrealDB Studio is designed to be the single place you do it all from.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature highlights
&lt;/h2&gt;

&lt;h3&gt;
  
  
  All of your data in one place
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.surrealdb.com%2Fzyqvtm0l1f1xut2mky9ipetu.auto" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.surrealdb.com%2Fzyqvtm0l1f1xut2mky9ipetu.auto" alt="An image of SurrealDB Studio showing a query tab opened next to a schema tab" width="1794" height="875"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In an age of AI agents and the increasing need for context, how you represent and interact with data matters more than ever. Most database tools lock you into fixed screens that can't adapt to your needs. SurrealDB Studio breaks that constraint. Every query, table, and schema resource opens in its own tab, letting you dynamically organize and visualize your data however works best for your work.&lt;/p&gt;

&lt;p&gt;Each tab does one specific thing. One query, one table of records, one schema diagram. Open as many as you like, rename them, drag them into whatever order suits you, or split the screen up to four ways. Compare the results of two queries side by side, or keep your schema open while you write queries next to it. Click a record and it opens in a tab of its own next to your work, rather than a panel sliding over the top of it.&lt;/p&gt;

&lt;p&gt;Your tabs stay where you put them, so you can close the app and pick up exactly where you left off when you return.&lt;/p&gt;

&lt;h3&gt;
  
  
  An editor that knows your schema
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.surrealdb.com%2F4hwmremvd091kqxbesv78tsl.auto" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.surrealdb.com%2F4hwmremvd091kqxbesv78tsl.auto" alt="An image of a SurrealDB Studio query tab showing tab completion on field names" width="1108" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;SurrealDB Studio's query editor reads your schema and knows exactly what is in your database. When you start typing, it suggests your tables and the fields inside them, your functions and parameters, as well as SurrealQL's built-in functions. Each suggestion comes with a short description of what it is and what it expects, so you don't need to have the docs open in another window. Hover over anything in your query to read more about it on the spot.&lt;/p&gt;

&lt;p&gt;It also checks your work as you go. If you misspell a field, reference a table that doesn't exist, or compare two things that could never match, SurrealDB Studio underlines it the moment you type it instead of leaving you to discover it in an error message after you run the query.&lt;/p&gt;

&lt;h3&gt;
  
  
  Navigation that stays in context
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.surrealdb.com%2F5csxzdi8dz4e1di62049pud1.auto" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.surrealdb.com%2F5csxzdi8dz4e1di62049pud1.auto" alt="An image of the organisation selector in SurrealDB Studio" width="1582" height="824"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you first open SurrealDB Studio, you will land directly in an organisation. This gives you a one click method to access all of your instances, contexts, connections, and organisation settings with ease.&lt;/p&gt;

&lt;p&gt;If you work across more than one organisation, the dropdown in the toolbar switches between them instantly and the whole app follows along. Instances, team settings, and billing all belong to whichever organisation you're currently in, so there's nothing to reconnect and no scrolling through a long list looking for the right database.&lt;/p&gt;

&lt;p&gt;This change also means custom connections are now stored on an organisation, allowing them to be easy categorised across different projects. While they remain local for now, you will soon be able to easily share connections between team members, or keep them local if you desire.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;The quickest way to get started with using SurrealDB Studio is by using the web app at &lt;a href="https://studio.surrealdb.com" rel="noopener noreferrer"&gt;studio.surrealdb.com&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;In case you would rather have SurrealDB Studio as a proper desktop app, you can &lt;a href="https://surrealdb.com/studio" rel="noopener noreferrer"&gt;download it for macOS, Windows, and Linux&lt;/a&gt;. The desktop version adds a few things the browser can't do, like running a local database for you, or easily working with multiple windows.&lt;/p&gt;

&lt;p&gt;To get started, simple sign in with your SurrealDB account and you'll be ready to deploy and manage instances, query your Spectron context, and run your local databases.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Reaching version 1.0 is just the start. Here is some of what we have planned: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Full agent integration:&lt;/strong&gt; Connect SurrealDB Studio with your agent of choice and drive the interface through MCP, generate visualisations on-demand, and push tabs directly in your chats.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your configuration, every device:&lt;/strong&gt; Sign in anywhere and find your connections, saved queries, and tab layout exactly as you left them, whether you're on your laptop, your desktop, or a browser on someone else's machine. No config files to copy around and no exports required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sharing with your team:&lt;/strong&gt; Send someone a link to a query or a whole tab, instead of pasting SurrealQL into a chat window and hoping it survives.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Organisation connections:&lt;/strong&gt; Automatically sharing connections to self hosted SurrealDB instances between your team members.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New kinds of tabs:&lt;/strong&gt; Such as notebooks that mix notes, queries, and their results into one document, and dashboards that allow you to assemble your own charts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation without leaving the app:&lt;/strong&gt; Open the documentation in a tab right beside what you're working on.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What does this mean for Surrealist?
&lt;/h3&gt;

&lt;p&gt;The Surrealist Desktop app will no longer receive feature updates going forward. While the majority of functionality in Surrealist is already available in SurrealDB Studio, there are still some minor features actively being ported over.&lt;/p&gt;

&lt;p&gt;Your existing Surrealist data will be automatically migrated to SurrealDB Studio on launch, meaning you can continue where you left off seamlessly. While the Surrealist app will continue to work, cloud related functionality will be restricted to the SurrealDB Studio app at a future point in time, so we recommend migrating to Studio in due course.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is SurrealDB Studio Desktop available on Linux based systems?
&lt;/h3&gt;

&lt;p&gt;Absolutely! While we used to offer Surrealist Desktop for Linux, the user experience was poor for many users due to bad performance and frequent rendering issues. SurrealDB Studio eliminates these issues entirely by adopting a different technology and bringing Linux support up to par with Windows and MacOS.&lt;/p&gt;

&lt;p&gt;Going forward the Linux app will receive the same level of support as our other desktop apps, and will continue to be a great tool for working with SurrealDB.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need a SurrealDB account to use Studio?
&lt;/h3&gt;

&lt;p&gt;In order to support the ongoing experience SurrealDB Studio has to offer, you'll need to sign in to your SurrealDB Account. This allows us to offer an intuitive unified workflow and makes collaboration seamless. Additionally Studio will continue to introduce additional collaborative features so you can share connections, queries, and tabs with your team members seamlessly.&lt;/p&gt;

&lt;p&gt;We recognize that this isn't viable for every organization or use case. For this reason we're working on an alternative experience for Studio focus on self-hosted use which we'll have more to share about soon.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let us know what you think
&lt;/h2&gt;

&lt;p&gt;We'd love to hear your thoughts. &lt;a href="https://discord.com/invite/surrealdb" rel="noopener noreferrer"&gt;Join us on Discord&lt;/a&gt; to ask questions, share what you're building, or tell us what you'd like to see next.&lt;/p&gt;

&lt;p&gt;Get started for free today at &lt;a href="https://studio.surrealdb.com" rel="noopener noreferrer"&gt;studio.surrealdb.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>news</category>
      <category>database</category>
      <category>web</category>
      <category>surrealdb</category>
    </item>
    <item>
      <title>Empower your agents with the new SurrealDB MCP</title>
      <dc:creator>Matthew McFadden</dc:creator>
      <pubDate>Wed, 05 Aug 2026 14:12:49 +0000</pubDate>
      <link>https://dev.to/surrealdb/empower-your-agents-with-the-new-surrealdb-mcp-m10</link>
      <guid>https://dev.to/surrealdb/empower-your-agents-with-the-new-surrealdb-mcp-m10</guid>
      <description>&lt;p&gt;Exactly a year ago we released SurrealMCP, a server you had to install and run in order to connect agents to your SurrealDB instance. Today, we're excited to announce the next evolution in agent intelligence with the new hosted SurrealDB MCP server.&lt;/p&gt;

&lt;p&gt;With this new SurrealDB MCP server, all you need is to add one URL to your AI tool of choice, sign in with your SurrealDB account, and your assistant can work on your instances or contexts alongside you. Whether you want to deploy a new instance, spin up a new Spectron context, execute queries against your graph, or ask questions about your data, the new SurrealDB MCP server has you covered.&lt;/p&gt;

&lt;p&gt;Let's dive into it! 🎉&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Adding the SurrealDB MCP to your agent
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.surrealdb.com%2Flwjyj808cd5u3rj8d4h4qcwc.auto" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.surrealdb.com%2Flwjyj808cd5u3rj8d4h4qcwc.auto" alt="An image of MCP servers in Cursor showing the SurrealDB MCP as ready with 90 tools available for use" width="1246" height="275"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Previously, you would need to add an MCP configuration for every instance or context that you wanted to connect to, and there was no way to provision instances or contexts with an agent. With the new hosted SurrealDB MCP server, you can do all of this and much more by adding a single URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;https://mcp.surrealdb.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can authorise the SurrealDB MCP Server either through signing in to your SurrealDB account, or for fine grained control, create a personal access token from the &lt;a href="https://account.surrealdb.com/tokens" rel="noopener noreferrer"&gt;account portal&lt;/a&gt;. That is the whole setup. Once connected, your agent now has the ability to interact with your SurrealDB Cloud resources.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Looking to setup your agent?&lt;/strong&gt; We have step-by-step guides for setting up with &lt;a href="https://surrealdb.com/docs/build/ai-agents/mcp/claude" rel="noopener noreferrer"&gt;Claude&lt;/a&gt; and &lt;a href="https://surrealdb.com/docs/build/ai-agents/mcp/cursor" rel="noopener noreferrer"&gt;Cursor&lt;/a&gt;.  For other configurations, please see our &lt;a href="https://surrealdb.com/docs/build/ai-agents/mcp" rel="noopener noreferrer"&gt;MCP docs&lt;/a&gt;. &lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy and manage resources just by asking
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.surrealdb.com%2Fvi053g9fdubbjyb8m9y363fn.auto" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.surrealdb.com%2Fvi053g9fdubbjyb8m9y363fn.auto" alt="An image of a chat conversation in Cursor showing a prompt asking to create a new SurrealDB instance followed by an agent creating one" width="931" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everything you would normally click through in the SurrealDB Studio dashboard is available in your conversations. Deploy a new instance or context, expand your resources as you grow, or upgrade to the latest version of SurrealDB. When deploying resources, the MCP server can put together a configuration, give you a cost estimation, and wait for your confirmation to kick off.&lt;/p&gt;

&lt;p&gt;The same conversation can also cover the rest of the housekeeping such as inviting users to your organisation, managing their access level, checking your monthly spend, and updating your instances.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ask questions about your data and build with ease
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.surrealdb.com%2Fwttc9occ83dwwsy9gzo5em6q.auto" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.surrealdb.com%2Fwttc9occ83dwwsy9gzo5em6q.auto" alt="An image of a chat conversation in Cursor showing the user asking about sales for certain months and getting a response back from the agent with the results" width="927" height="902"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;br&gt;
Instance querying is only available on instances running SurrealDB 3.2.3 and above&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The usual way to get an AI assistant to look at your database is to copy a schema or a query into a chat window, then paste the results back out again. SurrealDB MCP removes this loop. Simply ask a question in plain language and your assistant finds the right database, writes the query, runs it, and shows you both the query it wrote and what came back.&lt;/p&gt;

&lt;p&gt;You don't have to describe the shape of your data first, because it can go and look. Ask how many orders you took last week grouped by status, and it will find the table, work out the fields, and write the aggregate itself. Ask what the &lt;code&gt;customer&lt;/code&gt; table actually says, and it will describe the fields and indexes back to you. As a bonus, it's often faster than opening SurrealDB Studio to check for yourself.&lt;/p&gt;

&lt;p&gt;It works the other way round too. While you're building something new, you can ask it to create a single table or an entire schema for an enterprise-level app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Memory that outlasts the conversation
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.surrealdb.com%2Fhbfuwtj6digznamc38w88se3.auto" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.surrealdb.com%2Fhbfuwtj6digznamc38w88se3.auto" alt="An image of the user asking an agent to save a memory to Spectron" width="921" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every new chat normally starts from nothing. A decision your team made three weeks ago is gone unless someone remembers to paste it back in. My favorite feature of SurrealDB MCP is that you can interact directly with &lt;a href="https://surrealdb.com/spectron" rel="noopener noreferrer"&gt;Spectron&lt;/a&gt;, our memory layer for AI agents, in order to keep a persistent brain related to your chats which can be shared with multiple users. &lt;/p&gt;

&lt;p&gt;Simply ask your agent to save a memory or give it documents to read, and it will search through them by meaning rather than by keyword and store it for you. Then, ask your agent to retrieve something later:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.surrealdb.com%2Fehzl5iaawsps0449fb56fbyy.auto" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.surrealdb.com%2Fehzl5iaawsps0449fb56fbyy.auto" alt="An image of the user asking an agent to retrieve a memory from Spectron, followed by the memory that it retrieved" width="912" height="389"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Let us know what you think
&lt;/h2&gt;

&lt;p&gt;We'd love to hear your thoughts! &lt;a href="https://discord.com/invite/surrealdb" rel="noopener noreferrer"&gt;Join us on Discord&lt;/a&gt; to ask questions, share what you're building, or tell us what you'd like your assistant to be able to do next.&lt;/p&gt;

&lt;p&gt;To learn even more about SurrealDB MCP, please check out our &lt;a href="https://surrealdb.com/docs/build/ai-agents/mcp" rel="noopener noreferrer"&gt;MCP docs&lt;/a&gt;. &lt;/p&gt;

</description>
      <category>database</category>
      <category>ai</category>
      <category>mcp</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Agentic retrieval for structured data with text-to-surql</title>
      <dc:creator>Martin Schaer</dc:creator>
      <pubDate>Tue, 04 Aug 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/surrealdb/agentic-retrieval-for-structured-data-with-text-to-surql-21k1</link>
      <guid>https://dev.to/surrealdb/agentic-retrieval-for-structured-data-with-text-to-surql-21k1</guid>
      <description>&lt;p&gt;RAG pipelines are commonly centred around processing unstructured data and indexing it with vectors or BM25. But when you have structured data, things change. You may still need semantic and full-text search, but the main challenge now is how to retrieve the structured data that sits in tables.&lt;/p&gt;

&lt;p&gt;Yes, you could create an agent tool with a query to the DB to –for example– &lt;em&gt;fetch the records in the products table that match a specific category&lt;/em&gt;. But then, you’ll need a new tool for each other query pattern, and unless you want your agent to have a very limited range of action, this is not the best solution.&lt;/p&gt;

&lt;p&gt;This is where &lt;strong&gt;text-to-surql&lt;/strong&gt; comes to help.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using a retrieval tool
&lt;/h2&gt;

&lt;p&gt;Give the agent tools that speak the language of your database, and you’ll support infinite types of questions from your users [1].&lt;/p&gt;

&lt;p&gt;Here's the flow:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftyqxx19kgyx355legwss.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftyqxx19kgyx355legwss.png" alt="diagram" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The agent, with the LLM doing the reasoning, decides when to hit the database using the retrieval tool, which generates a valid SurrealQL query, executes it, and returns structured results.&lt;/p&gt;

&lt;h2&gt;
  
  
  The SurrealQL generation tool
&lt;/h2&gt;

&lt;p&gt;SurrealQL is uniquely well-suited for this because it’s a multi-model query language. In a single query, you can traverse graph relationships, filter document fields, and run relational aggregations. That means your agent doesn't need to orchestrate across multiple databases or data layers. One tool, one query, one result.&lt;/p&gt;

&lt;p&gt;Let's walk through a real example first.&lt;/p&gt;

&lt;p&gt;The tool itself is straightforward to build: a prompt template that includes the schema, a few-shot examples of good SurrealQL queries, and a SurrealDB client to execute the result. We'll cover that later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User asks:&lt;/strong&gt; &lt;em&gt;"can you summarize the reviews of my top 3 best selling products?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The tool generates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LET $top = SELECT in.{id, name} AS product,
    math::sum(qty) AS total_sales
    FROM REL_PRODUCT_IN_ORDER
    GROUP BY product
    ORDER BY total_sales DESC
    LIMIT 3;

RETURN $top.map(
    |$p| {
        $p + {
            average_rating: (
                SELECT
                    product,
                    math::mean(score) AS avg_rating,
                    math::sum(1) AS review_count
                FROM review
                WHERE product = $p.product.id
                GROUP BY product
            )[0].avg_rating,
            review_count: (
                SELECT
                    product,
                    math::mean(score) AS avg_rating,
                    math::sum(1) AS review_count
                FROM review
                WHERE product = $p.product.id
                GROUP BY product
            )[0].review_count,
            sentiment_breakdown: (
                SELECT
                    sentiment,
                    math::sum(1) AS count
                FROM review
                WHERE product = $p.product.id
                GROUP BY sentiment
                ORDER BY count DESC
            ),
            recent_reviews: (
                SELECT
                    score AS rating,
                    created_at AS date,
                    text,
                    sentiment,
                    flow_sentiment
                FROM review
                WHERE product = $p.product.id
                ORDER BY created_at DESC
                LIMIT 10
            ),
        };
    }
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result returned to agent:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[
    {
        average_rating: 4.75f,
        product: {
            id: product:26,
            name: 'Yoga Mat Pro',
        },
        recent_reviews: [
            {
                date: d'2026-04-20T11:11:27.427351302Z',
                rating: 5,
                sentiment: 'possitive',
                text: "I bought this primarily for stretching and cool-down after lifting sessions rather than yoga proper. It does that job perfectly - thick enough that kneeling on a hard floor is comfortable, and it doesn't slide even on polished concrete. Rolled up it's compact enough to slip under my desk. No complaints whatsoever.",
            },
            {
                date: d'2026-04-20T11:11:27.427112844Z',
                rating: 4.5f,
                sentiment: 'possitive',
                text: "Non-slip is not an exaggeration - this mat grips the floor and my hands equally well even in sweaty hot yoga sessions. The 6mm thickness is the sweet spot between cushioning and stability for balance poses. TPE material doesn't have the chemical smell that cheaper PVC mats have. The carrying strap is a bit flimsy but functional.",
            },
        ],
        review_count: 2,
        sentiment_breakdown: [{ count: 2, sentiment: 'possitive' }],
        total_sales: 5,
    },
    {
        average_rating: 4.5f,
        product: {
            id: product:9,
            name: 'Canvas Tote Bag',
        },
        recent_reviews: [
            {
                date: d'2026-04-20T11:11:27.426732636Z',
                rating: 4.5f,
                sentiment: 'possitive',
                text: 'I use this as a daily carry and it holds everything - laptop, gym clothes, lunch, groceries. The interior zipper pocket is a lifesaver for keys and cards. Handles are reinforced and show no signs of wear after months of heavy use. The canvas has a slight stiffness that I actually like.',
            },
        ],
        review_count: 1,
        sentiment_breakdown: [{ count: 1, sentiment: 'possitive' }],
        total_sales: 3,
    },
    {
        average_rating: 5,
        product: {
            id: product:7,
            name: 'Linen Wrap Dress',
        },
        recent_reviews: [
            {
                date: d'2026-04-20T11:11:27.426633136Z',
                rating: 5,
                sentiment: 'possitive',
                text: 'This dress is exactly what summer dressing should be. The linen is lightweight and breathable, the wrap silhouette is flattering on multiple body types, and the tie actually stays put throughout the day. Ordered two in different colors.',
            },
        ],
        review_count: 1,
        sentiment_breakdown: [{ count: 1, sentiment: 'possitive' }],
        total_sales: 2,
    },
];
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent now has exact numbers and reviews it can cite with confidence and trace back to a specific query against a specific table. That's what auditability looks like in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built-in permissions
&lt;/h2&gt;

&lt;p&gt;SurrealDB's record- and field-level permissions and RBAC model mean that agents only see the data they're supposed to see – enforced at the database layer, not bolted on in application code. Multi-tenant agent deployments become straightforward: each agent session operates within the appropriate permission scope automatically.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://surrealdb.com/docs/learn/security" rel="noopener noreferrer"&gt;Learn more about SurrealDB’s security model.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to build it
&lt;/h2&gt;

&lt;p&gt;Here's how to put the pattern together from scratch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Define your schema and expose it to the agent&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The SurrealQL generation tool needs schema context to know what tables, fields, and indexes it can play with. It needs this information to infer where to get the data from to answer the user’s question.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-- TABLE: product
DEFINE TABLE product SCHEMAFULL;
DEFINE FIELD category ON product TYPE record&amp;lt;category&amp;gt;;
DEFINE FIELD description ON product TYPE string;
DEFINE FIELD embedding ON product TYPE array&amp;lt;float&amp;gt; | none;
DEFINE FIELD name ON product TYPE string;
DEFINE FIELD price ON product TYPE float;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The schema context can be dynamically generated, see snippet below. But once your schema is stable, to avoid extra calls to the DB you can hardcode it (which also gives you more control on what to include in the context) or at least cache it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-- example of how to dynamically generate your schema context
LET $db = INFO FOR DB;
$db.tables.values() +
$db.users.values() + 
$db.tables.keys().map(|$t| {
    LET $i = INFO FOR TABLE $t;
    $i.fields.?.values() + $i.indexes.?.values()
}).flatten().filter(|$v| !!$v);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2: Build the SurrealQL generation tool&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is a function your agent can call. At minimum it needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A system prompt with schema context and a few-shot examples of valid SurrealQL queries.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A SurrealDB client to execute the query and return results&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Optional but recommended: catch any errors when executing the query, and ask the LLM to fix them and try again. Without this retry logic within the tool, the agent may retry the tool altogether, but the LLM call that generates the SurrealQL won’t include the error as context, because it’s not a parameter of the tool. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Wire up the agent tool&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This pattern is framework-agnostic. It works with Pydantic AI agents, LangChain's tool-calling agents, LlamaIndex's ReAct agents, or a custom loop. The key is to provide the agent with a clear tool description so it knows when to use it, e.g. “Use this tool to answer questions about products, orders, reviews, or users”. In the following example, you can see a fine-tuned description that hints to LLM to leverage the vector embeddings when available. In my use case, this prevents the LLM from searching products or categories by matching keywords, preferring vector search instead [2].&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;query_db&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RunContext&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Deps&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Use this tool to answer questions about products, orders, reviews,
    or users.

    If required, you can do vector search against any table with an
    embeddings field.
    E.g: `WHERE embedding &amp;lt;|20,40|&amp;gt; fn::embed(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text to embed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;)`.

    Args:
        question: The user question.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;PROMPT_GEN_SURQL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
You are an expert in SurrealQL (surql, SurrealDB&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s query language).

Generate a valid surql query to get the information required to answer the user&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s prompt.

PROMPT: {prompt}

&amp;lt;schema&amp;gt;
{schema}
&amp;lt;/schema&amp;gt;

&amp;lt;best-practices&amp;gt;
{notes}
&amp;lt;/best-practices&amp;gt;

&amp;lt;examples&amp;gt;
{examples}
&amp;lt;/examples&amp;gt;
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Find more in &lt;a href="https://github.com/surrealdb/kaig/blob/main/examples/knowledge-graph/tools/query_db.py" rel="noopener noreferrer"&gt;query_db.py&lt;/a&gt; and the &lt;a href="https://github.com/surrealdb/kaig/blob/main/src/kaig/prompts/text_to_surql.py" rel="noopener noreferrer"&gt;prompt template&lt;/a&gt; in the &lt;a href="https://github.com/surrealdb/kaig" rel="noopener noreferrer"&gt;Kai G repo&lt;/a&gt;. If you are new to prompt engineering –or want a good refresher, I recommend watching &lt;a href="https://www.youtube.com/watch?v=ysPbXH0LpIE" rel="noopener noreferrer"&gt;Prompting 101&lt;/a&gt; by Anthropic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fine-tuning
&lt;/h2&gt;

&lt;p&gt;LLMs are getting better and better at writing complex queries, but they don’t get it right all the time. This is why the prompt and few-shot examples are so important. The ones I shared above were the result of a few manual iterations. You can borrow them for your project, but your specific use case (and model) will require different hints.&lt;/p&gt;

&lt;p&gt;Design your solution in a way that you have good observability to iterate quickly, and metrics to make sure you are moving forward. In my demo, I use Pydantic’s Logfire for observability, and store the results from the text-to-surql function and the query execution with a score. This allows me to capture queries that failed, identify the anti-pattern, and add a line to the tool prompt or a new example to avoid that mistake from surfacing again.&lt;/p&gt;

&lt;p&gt;Some examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;don't use `math::avg`, the correct one is `math::mean`.&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;always `OMIT` the `embedding` field from the final result to avoid large results.&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;vector::distance::knn() must be in SELECT to use in ORDER BY&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The next generation of production AI agents won't be distinguished by how good their embeddings are. They'll be distinguished by &lt;strong&gt;how precisely they can retrieve and present facts&lt;/strong&gt; - with full auditability back to the source.&lt;/p&gt;

&lt;p&gt;Agentic retrieval with a SurrealQL generation tool closes that gap. Instead of hoping a vector similarity search lands close enough, the agent reasons about your question, writes a precise query, and returns exactly the data you asked for. Every answer is traceable to a specific query against a specific table.&lt;/p&gt;

&lt;p&gt;SurrealDB makes this pattern practical: multi-model queries mean the agent handles graph traversals, document lookups, and relational aggregations in a single round-trip, while record-level permissions keep data access secure by default.&lt;/p&gt;

&lt;p&gt;If you're building agents that need to answer questions from structured data - and answer them &lt;strong&gt;correctly&lt;/strong&gt; - this is the architecture worth building toward.&lt;/p&gt;




&lt;h2&gt;
  
  
  Footnotes
&lt;/h2&gt;

&lt;p&gt;[1] You probably don’t want users to have such power. If you allow users to ask any question, they may ask “change the price of SKU-0042 to $0, and create an order with 10 of them ready to checkout”.&lt;/p&gt;

&lt;p&gt;[2] A full-text index could be a better idea, if what you are searching for can effectively be retrieved by lexical means, rather than semantically. And for some use cases &lt;a href="https://surrealdb.com/blog/a-real-world-example-of-hybrid-fusion-search-using-the-surrealdb-docs-search" rel="noopener noreferrer"&gt;reranked hybrid search&lt;/a&gt; could be the right choice. Consider all alternatives based on your use case, and what you are optimising for.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Ready to try it?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://surrealdb.com/cloud" rel="noopener noreferrer"&gt;Create a free SurrealDB Cloud instance&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://surrealdb.com/docs" rel="noopener noreferrer"&gt;Explore the SurrealQL docs&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://discord.gg/surrealdb" rel="noopener noreferrer"&gt;Join the SurrealDB Discord&lt;/a&gt; - new here? The #all-ai and #surrealql channels are the best places to get started.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Using SurrealDB to understand how Buffalo buffalo buffalo Buffalo buffalo</title>
      <dc:creator>Dave MacLeod</dc:creator>
      <pubDate>Thu, 09 Jul 2026 13:54:06 +0000</pubDate>
      <link>https://dev.to/surrealdb/using-surrealdb-to-understand-how-buffalo-buffalo-buffalo-buffalo-buffalo-c3l</link>
      <guid>https://dev.to/surrealdb/using-surrealdb-to-understand-how-buffalo-buffalo-buffalo-buffalo-buffalo-c3l</guid>
      <description>&lt;p&gt;&lt;em&gt;Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffalo.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That's technically a valid English sentence, one of the ones that &lt;a href="https://en.wikipedia.org/wiki/Buffalo_buffalo_Buffalo_buffalo_buffalo_buffalo_Buffalo_buffalo" rel="noopener noreferrer"&gt;has its own Wikipedia article&lt;/a&gt; to explain just how it works. What makes it work is that the word buffalo is also a (rarely used) verb to mean &lt;a href="https://en.wiktionary.org/w/index.php?title=buffalo&amp;amp;oldid=90796509#Translations_2" rel="noopener noreferrer"&gt;to bully or to fool someone&lt;/a&gt;. Here's an example of the verb in practice:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"If nonfiction is where you do your best writing, or your best teaching of writing, don't be &lt;em&gt;buffaloed&lt;/em&gt; into the idea that it's an inferior species."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The other thing that makes the sentence confusing is that the word buffalo can also be the plural of buffalo. Fortunately, you can also pluralise it with an s - which is what we will do in this post from here on.&lt;/p&gt;

&lt;p&gt;With a bit of rewriting and notes in parentheses, the sentence makes a &lt;em&gt;little&lt;/em&gt; more sense.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Buffaloes from Buffalo that buffaloes from Buffalo buffalo (trick) buffalo (trick) buffaloes from Buffalo.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If that still only makes just a bit of sense, don't worry - because we can demonstrate the situation using SurrealQL instead.&lt;/p&gt;

&lt;h1&gt;
  
  
  Buffaloes that buffalo Buffalo buffaloes
&lt;/h1&gt;

&lt;p&gt;To start: if we have Buffalo buffalo, that means that there should also be non-Buffalo buffalo: buffalo that don't come from the city of Buffalo. Let's make 50 of each. Two following two &lt;code&gt;CREATE&lt;/code&gt; statements will create them with a sequential ID starting from 1 and ending at 100.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE |buffalo:1..=50| SET from = "Buffalo";
CREATE |buffalo:51..=100| SET from = "Somewhere else";
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we need to get the buffaloes to interact with each other.&lt;/p&gt;

&lt;p&gt;Since the original sentence is talking about how a buffalo can buffalo another buffalo, that must mean that this sometimes happens and sometimes does not. To demonstrate this, let's go through each buffalo, randomly choose a buffalo (with the &lt;a href="https://surrealdb.com/docs/reference/query-language/functions/database-functions/rand#randenum" rel="noopener noreferrer"&gt;rand::enum()&lt;/a&gt; function) that is not the buffalo in question (&lt;a href="https://surrealdb.com/docs/reference/query-language/functions/database-functions/array#arraycomplement" rel="noopener noreferrer"&gt;array::complement()&lt;/a&gt; will do that), and then use &lt;a href="https://surrealdb.com/docs/reference/query-language/functions/database-functions/rand#randbool" rel="noopener noreferrer"&gt;rand::bool()&lt;/a&gt; to give it a 50% chance of buffaloing that buffalo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FOR $buffalo IN SELECT * FROM buffalo {
    // We could do this in a single line but let's go one step at a time
    LET $all_buffalo = SELECT VALUE id FROM buffalo;
    LET $except_this_one = $all_buffalo.complement([$buffalo.id]);
    LET $other_buffalo = rand::enum($except_this_one);
    // Now that we have another buffalo, we'll flip a coin
    // to see if it gets 'buffaloed'
    IF rand::bool() {
        RELATE $buffalo-&amp;gt;buffaloes-&amp;gt;$other_buffalo;
    };
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that set up, we can do a query to see which Buffalo buffaloes are buffaloing Buffalo buffaloes, and who they are. To do that, we can use a &lt;code&gt;WHERE&lt;/code&gt; clause to ensure that the buffalo in question is from Buffalo, and that the path &lt;code&gt;-&amp;gt;buffaloes-&amp;gt;buffalo.from&lt;/code&gt; (the buffalo(es) that are getting buffaloed) contains the word "Buffalo".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT 
    id, 
    from, 
    -&amp;gt;buffaloes-&amp;gt;buffalo AS buffaloes
FROM buffalo 
WHERE 
    from = "Buffalo" 
AND 
    "Buffalo" IN -&amp;gt;buffaloes-&amp;gt;buffalo.from;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because half of the buffaloes are from Buffalo and half of these have buffaloed another buffalo, the output should show...about 25? Not quite, because some unlucky buffaloes have been buffaloed by more than one buffalo. We can demonstrate that by doing a reverse graph query on the buffaloes to see which ones they are being buffaloed by, followed by a filter to only retain the ones that have been buffaloed more than once.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(SELECT &amp;lt;-buffaloes&amp;lt;-buffalo AS buffaloed_by FROM buffalo)
    .filter(
        |$b| $b.buffaloed_by.len() &amp;gt; 1
    );
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Buffaloing back
&lt;/h1&gt;

&lt;p&gt;Now it's time for justice to be done. Because the original sentence can actually be read as a decree.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"No buffalo from Buffalo shall be buffaloed by a buffalo from Buffalo without the chance to buffalo back. Henceforth, any buffalo from Buffalo that Buffalo buffalo buffalo shall also buffalo a Buffalo buffalo."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sounds fair.&lt;/p&gt;

&lt;p&gt;To enact this decree, we will need to have every buffalo that is buffaloed by a buffalo from Buffalo now also buffalo another buffalo from Buffalo.&lt;/p&gt;

&lt;p&gt;To start, we'll use much of the same query as above but turn it around. And this time we won't use rand::bool() because the buffalo from Buffalo in question that has been buffaloed now has a 100% chance of buffaloing another buffalo from Buffalo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FOR $buffalo IN SELECT VALUE
    id
FROM buffalo 
WHERE 
    from = "Buffalo" 
AND 
    "Buffalo" IN &amp;lt;-buffaloes&amp;lt;-buffalo.from {
         LET $all_buffalo = SELECT VALUE id FROM buffalo;
         LET $except_this_one = $all_buffalo.complement([$buffalo.id]);
        LET $other_buffalo = rand::enum($except_this_one);
        RELATE $buffalo-&amp;gt;buffaloes-&amp;gt;$other_buffalo;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Visualising all the buffaloing
&lt;/h1&gt;

&lt;p&gt;We can finish this off by visualising the data to see all of the buffaloing that is going on, whether actively (buffaloing a buffalo) or passively (being buffaloed by a buffalo). The following two queries will show who is buffaloing and who they are being buffaloed by, depending on whether they are from Buffalo or not.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT 
    id, 
    buffaloes-&amp;gt;buffalo AS buffaloes, 
    &amp;lt;-buffaloes&amp;lt;-buffalo AS buffaloed_by
FROM buffalo 
WHERE from = "Buffalo";

SELECT 
    id, 
    buffaloes-&amp;gt;buffalo AS buffaloes,
    &amp;lt;-buffaloes&amp;lt;-buffalo AS buffaloed_by
FROM buffalo 
WHERE from != "Buffalo";
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The raw output of these queries is easy enough to read, but it's tough to get a top-level idea of what is going on here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[
    {
        buffaloed_by: [
            buffalo:76
        ],
        buffaloes: NONE,
        id: buffalo:3
    },
    {
        buffaloed_by: [
            buffalo:45,
            buffalo:18
        ],
        buffaloes: NONE,
        id: buffalo:4
    },
    {
        buffaloed_by: [],
        buffaloes: NONE,
        id: buffalo:5
    },
//...
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where Surrealist's graph visualisation comes into play. To use it, just click on the "Combined" button next to the button used to run a query and change it to "Graph". As long as a query contains the id of each record, it will be able to visually show which records are connected to which other ones.&lt;/p&gt;

&lt;p&gt;You can also click on "Stray edges" to display records that aren't connected, which is what we will do here.&lt;/p&gt;

&lt;p&gt;The output will differ every time these queries are executed and every time this functionality is used to generate a visual output in Surrealist, but for the most part you will notice that there is a lot more going on between the buffaloes from Buffalo than those from somewhere else. That makes sense, because the second RELATE operation that we used was only for, and between, buffaloes from Buffalo.&lt;/p&gt;

&lt;p&gt;The first query between buffaloes from Buffalo should look somewhat busy with connections between quite a few, and a number of stray edges floating around in the middle.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs12x8u7raohc4262r49g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs12x8u7raohc4262r49g.png" alt=" " width="800" height="747"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The second query is a bit less so, with fewer connections between these buffaloes and a lot more stray edges.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkdfr65enmku65bvvobmj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkdfr65enmku65bvvobmj.png" alt=" " width="800" height="747"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Learn more
&lt;/h1&gt;

&lt;p&gt;Interested in learning more? Here are some more links and blog posts to get started.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://surrealdb.com/blog/visualising-your-data-with-surrealists-graph-view" rel="noopener noreferrer"&gt;Visualising your data with Surrealist's graph view&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://surrealdb.com/blog/enhance-your-musical-skills-with-surrealists-graph-view" rel="noopener noreferrer"&gt;Enhance your musical skills with Surrealist's graph view&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://surrealdb.com/blog/agentic-retrieval-for-structured-data-with-text-to-surql" rel="noopener noreferrer"&gt;Agentic retrieval for structured data with text to surql&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And for another technically correct mind-bending sentence, check out the page on &lt;a href="https://en.wikipedia.org/wiki/James_while_John_had_had_had_had_had_had_had_had_had_had_had_a_better_effect_on_the_teacher" rel="noopener noreferrer"&gt;James while John had had had had had had had had had had had a better effect on the teacher&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Ready to try it?
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://surrealdb.com/cloud" rel="noopener noreferrer"&gt;Create a free SurrealDB Cloud instance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://surrealdb.com/docs" rel="noopener noreferrer"&gt;Explore the SurrealQL docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://discord.gg/surrealdb" rel="noopener noreferrer"&gt;Join the SurrealDB Discord&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>database</category>
    </item>
    <item>
      <title>Detecting connections as they form: An introduction to streaming graph pattern matching</title>
      <dc:creator>Dave MacLeod</dc:creator>
      <pubDate>Thu, 09 Jul 2026 12:58:27 +0000</pubDate>
      <link>https://dev.to/surrealdb/detecting-connections-as-they-form-an-introduction-to-streaming-graph-pattern-matching-3n21</link>
      <guid>https://dev.to/surrealdb/detecting-connections-as-they-form-an-introduction-to-streaming-graph-pattern-matching-3n21</guid>
      <description>&lt;h1&gt;
  
  
  Detecting connections as they form: An introduction to streaming graph pattern matching
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;Originally published by &lt;a href="https://edgebound.hashnode.dev/detecting-connections-as-they-form-an-introduction-to-streaming-graph-pattern-matching" rel="noopener noreferrer"&gt;Cyril Scetbon&lt;/a&gt; on 13 April, 2026&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In my &lt;a href="https://edgebound.hashnode.dev" rel="noopener noreferrer"&gt;&lt;strong&gt;previous posts&lt;/strong&gt;&lt;/a&gt;, I explored how &lt;strong&gt;SurrealDB&lt;/strong&gt; blends document, graph, relational, and vector models into a single engine, and how you can combine it with &lt;a href="https://edgebound.hashnode.dev/building-a-smart-knowledge-agent-with-surrealdb-and-rigrs" rel="noopener noreferrer"&gt;&lt;strong&gt;Rig.rs&lt;/strong&gt;&lt;/a&gt; to build LLM-native agents in Rust. I've written about SurrealDB more than once because I genuinely believe it's one of the most exciting databases out there right now — and the more I use it, the more I find myself reaching for it as the obvious foundation whenever I need something flexible, expressive, and Rust-native. Its graph capabilities — &lt;code&gt;RELATE&lt;/code&gt;, record IDs, graph traversals — kept nagging at me with a question: what if you could &lt;strong&gt;continuously watch a graph as it grows&lt;/strong&gt; and fire off actions the moment a meaningful pattern completes?&lt;/p&gt;

&lt;p&gt;That question sent me down a rabbit hole. This post is the first in a series where I'll walk you through the concept, the configuration I'd want for such a tool, the actual implementation, and where things could go from there.&lt;/p&gt;

&lt;p&gt;Let's start at the beginning. 🔍&lt;/p&gt;

&lt;h2&gt;
  
  
  🕸️ &lt;strong&gt;What is a labeled property graph?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;If you've read my &lt;a href="https://edgebound.hashnode.dev/surrealdb-the-game-changing-database-youve-been-waiting-for" rel="noopener noreferrer"&gt;&lt;strong&gt;SurrealDB deep-dive&lt;/strong&gt;&lt;/a&gt;, you already know that SurrealDB natively models data as a &lt;strong&gt;labeled property graph&lt;/strong&gt;. But let's make it concrete for this series.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;labeled property graph&lt;/strong&gt; has two building blocks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vertices&lt;/strong&gt; — entities with a type and properties. Think &lt;code&gt;person:clint_eastwood&lt;/code&gt; with a &lt;code&gt;name&lt;/code&gt; field, or &lt;code&gt;movie:unforgiven&lt;/code&gt; with a &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;year&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edges&lt;/strong&gt; — directed, typed relationships between vertices. Think &lt;code&gt;person:clint_eastwood -&amp;gt; acted_in -&amp;gt; movie:unforgiven&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Edges are first-class citizens: they have their own type (e.g., &lt;code&gt;acted_in&lt;/code&gt;, &lt;code&gt;directed&lt;/code&gt;) and can carry their own properties (e.g., the &lt;code&gt;role&lt;/code&gt; a person played).&lt;/p&gt;

&lt;p&gt;This model is perfect for representing the kind of messy, interconnected real-world data that relational tables struggle with — social networks, supply chains, knowledge graphs, movie databases.&lt;/p&gt;

&lt;p&gt;If you want to get a feel for how expressive SurrealDB's query language gets when working with this kind of data, take a look at the &lt;a href="https://surrealdb.com/docs/surrealql/datamodel/idioms#idioms" rel="noopener noreferrer"&gt;&lt;strong&gt;idioms page&lt;/strong&gt;&lt;/a&gt; in the docs. It's a compact showcase of what SurrealQL can do — and honestly, reading through it is a big part of what got me thinking about building this in the first place.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚡ &lt;strong&gt;What is streaming graph pattern matching?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;graph pattern&lt;/strong&gt; is a template describing a subgraph you care about. For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Find any person who &lt;strong&gt;both acted in and directed&lt;/strong&gt; the same movie."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In a static graph, you'd run a query once and get your results. But what if the graph is &lt;strong&gt;continuously growing&lt;/strong&gt;? Edges arrive one by one as events stream in. You want to know &lt;strong&gt;the moment&lt;/strong&gt; that pattern becomes complete — not minutes later, not after a full scan.&lt;/p&gt;

&lt;p&gt;That's &lt;strong&gt;streaming graph pattern matching&lt;/strong&gt;: detecting when a pattern is satisfied in real time, incrementally, as new edges are added.&lt;/p&gt;

&lt;p&gt;The classic use cases are things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fraud detection&lt;/strong&gt; — flag the moment a money flow completes a suspicious cycle&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recommendation engines&lt;/strong&gt; — create a "you might also like" edge the second two users share enough common favorites&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Knowledge graph enrichment&lt;/strong&gt; — derive new facts automatically as raw data arrives&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event correlation&lt;/strong&gt; — detect that a sequence of system events matches a known failure signature&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The challenge is doing this &lt;strong&gt;without re-scanning the entire graph&lt;/strong&gt; every time a single edge lands.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎬 &lt;strong&gt;A running example: the movie database&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Throughout this series I'll use a movie dataset. It's a flat CSV file where each row is tagged with an &lt;code&gt;Entity&lt;/code&gt; type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Entity,tmdbId,movieId,name,Work,role
Person,190,,Clint Eastwood,,
Person,192,,Morgan Freeman,,
Movie,,33,Unforgiven,,
Join,190,33,,Acting,William Munny
Join,192,33,,Acting,Ned Logan
Join,190,33,,Directing,
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things to note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Person&lt;/code&gt; rows describe people.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Movie&lt;/code&gt; rows describe films.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Join&lt;/code&gt; rows describe relationships — acting roles, directing credits — linking people to movies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This flat structure is typical of real-world export formats. The goal is to lift it into a proper graph and then continuously match patterns against it.&lt;/p&gt;

&lt;p&gt;The pattern I want to detect: &lt;strong&gt;a person who both acted in and directed the same movie&lt;/strong&gt;. Clint Eastwood fits. Morgan Freeman does not.&lt;/p&gt;

&lt;h2&gt;
  
  
  🗂️ &lt;strong&gt;Imagining the configuration&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;If I were to design a tool for this, I'd want the entire thing controlled by a single YAML configuration file. Two top-level sections: &lt;code&gt;sources&lt;/code&gt; to describe how raw data becomes a graph, and &lt;code&gt;patterns&lt;/code&gt; to express what I'm looking for in that graph. Let me walk through both in detail.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sources — loading the graph
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;sources&lt;/code&gt; section describes where your data comes from and how to turn each row into graph elements. Each source entry points to a file and defines a &lt;code&gt;format&lt;/code&gt; block with two optional scripts: &lt;code&gt;filter&lt;/code&gt; and &lt;code&gt;load&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For the scripting language, I'd reach for &lt;a href="https://vector.dev/docs/reference/vrl/" rel="noopener noreferrer"&gt;&lt;strong&gt;VRL&lt;/strong&gt;&lt;/a&gt; — Vector Remap Language — a safe, sandboxed expression language originally built for the Vector observability pipeline. It's expressive enough for real data wrangling, and it could be extended with a handful of custom functions to express graph operations directly.&lt;/p&gt;

&lt;p&gt;Here's what a complete &lt;code&gt;sources&lt;/code&gt; block would look like for the movie dataset:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;sources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# --- Source 1: Load Person vertices ---&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;file&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$movie_file&lt;/span&gt;          &lt;span class="c1"&gt;# resolved at runtime via --variable movie_file=...&lt;/span&gt;
    &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;msg.Entity == "Person"&lt;/span&gt;   &lt;span class="c1"&gt;# only process rows where Entity is "Person"&lt;/span&gt;
      &lt;span class="na"&gt;load&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;.id = record_id!("person", [msg.tmdbId])   # build a typed record ID: person:190&lt;/span&gt;
        &lt;span class="s"&gt;.name = msg.name&lt;/span&gt;
        &lt;span class="s"&gt;+ @                            # mark this object as a vertex to be created&lt;/span&gt;

  &lt;span class="c1"&gt;# --- Source 2: Load Movie vertices ---&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;file&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$movie_file&lt;/span&gt;
    &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;msg.Entity == "Movie"&lt;/span&gt;
      &lt;span class="na"&gt;load&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;.id = record_id!("movie", [msg.movieId])   # e.g. movie:33&lt;/span&gt;
        &lt;span class="s"&gt;.title = msg.name&lt;/span&gt;
        &lt;span class="s"&gt;+ @&lt;/span&gt;

  &lt;span class="c1"&gt;# --- Source 3: Acting edges ---&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;file&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$movie_file&lt;/span&gt;
    &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;msg.Entity == "Join" &amp;amp;&amp;amp; msg.Work == "Acting"&lt;/span&gt;
      &lt;span class="na"&gt;load&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;.id = record_id!("acted_in", [msg.tmdbId, msg.movieId, msg.role])&lt;/span&gt;
        &lt;span class="s"&gt;.role = msg.role&lt;/span&gt;
        &lt;span class="s"&gt;.person.id = record_id!("person", [msg.tmdbId])&lt;/span&gt;
        &lt;span class="s"&gt;.movie.id  = record_id!("movie",  [msg.movieId])&lt;/span&gt;
        &lt;span class="s"&gt;+ @person-&amp;gt;@-&amp;gt;@movie    # create the edge; the root object becomes the edge record&lt;/span&gt;

  &lt;span class="c1"&gt;# --- Source 4: Directing edges ---&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;file&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$movie_file&lt;/span&gt;
    &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;msg.Entity == "Join" &amp;amp;&amp;amp; msg.Work == "Directing"&lt;/span&gt;
      &lt;span class="na"&gt;load&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;.person.id = record_id!("person", [msg.tmdbId])&lt;/span&gt;
        &lt;span class="s"&gt;.movie.id  = record_id!("movie",  [msg.movieId])&lt;/span&gt;
        &lt;span class="s"&gt;+ @person-&amp;gt;directed-&amp;gt;@movie    # anonymous edge — no edge record object&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let me unpack each key concept.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;filter&lt;/code&gt; — Row-level gating
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="py"&gt;filter:&lt;/span&gt; &lt;span class="n"&gt;msg.Entity&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"Person"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a VRL boolean expression evaluated against each incoming row. If it returns &lt;code&gt;false&lt;/code&gt;, the row is skipped entirely. The raw CSV row is available as &lt;code&gt;msg&lt;/code&gt;. Multiple sources can read the &lt;strong&gt;same file&lt;/strong&gt; with different filters — that's intentional. The file would be read once and fanned out to all matching sources in parallel.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;record_id!()&lt;/code&gt; — Typed identifiers
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="n"&gt;.id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record_id&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"person"&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="n"&gt;msg.tmdbId&lt;/span&gt;&lt;span class="ss"&gt;])&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="py"&gt;Produces:&lt;/span&gt; &lt;span class="nl"&gt;person&lt;/span&gt;&lt;span class="dl"&gt;:&lt;/span&gt;&lt;span class="m"&gt;190&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This custom VRL function would build a SurrealDB-style record ID from a table name and a list of key components. If you pass multiple components, they're combined into a composite key. The result is a strongly typed identifier like &lt;code&gt;person:190&lt;/code&gt; or &lt;code&gt;acted_in:[190, 33, "William Munny"]&lt;/code&gt;. This ensures that re-ingesting the same data is idempotent — the same row always produces the same ID.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;+ @&lt;/code&gt; — Creating a vertex
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="n"&gt;.id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record_id&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"person"&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="n"&gt;msg.tmdbId&lt;/span&gt;&lt;span class="ss"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;.name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;msg.name&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;          &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="s2"&gt;"upsert me as a vertex"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;@&lt;/code&gt; symbol refers to the &lt;strong&gt;current root object&lt;/strong&gt; — the thing you've been building up with &lt;code&gt;.field = value&lt;/code&gt; assignments. Prefixing it with &lt;code&gt;+&lt;/code&gt; marks it for creation in the graph.&lt;/p&gt;

&lt;p&gt;One important thing to be explicit about: &lt;strong&gt;nested objects are not pushed automatically&lt;/strong&gt;. If your root object has a &lt;code&gt;.person&lt;/code&gt; sub-object, doing &lt;code&gt;+ @&lt;/code&gt; only persists the root — &lt;code&gt;.person&lt;/code&gt; is ignored. To push a nested object as its own vertex, you have to say so explicitly with &lt;code&gt;+ @person&lt;/code&gt;. This keeps the behaviour predictable and avoids accidentally creating vertices you didn't intend.&lt;/p&gt;

&lt;p&gt;You can also push &lt;strong&gt;multiple nested objects&lt;/strong&gt; in the same script — &lt;code&gt;+ @movie&lt;/code&gt;, &lt;code&gt;+ @genre&lt;/code&gt;, and so on — each as a separate vertex, containing only its own fields.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;+ @from-&amp;gt;edge-&amp;gt;@to&lt;/code&gt; — Creating an edge
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="n"&gt;.person.id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record_id&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"person"&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="n"&gt;msg.tmdbId&lt;/span&gt;&lt;span class="ss"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;.movie.id&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record_id&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"movie"&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt;  &lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="n"&gt;msg.movieId&lt;/span&gt;&lt;span class="ss"&gt;])&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;directed&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;movie&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a directed edge between two vertices. The vertices themselves don't have to be created in the same source entry — they can come from a different one. What matters is that &lt;code&gt;@person&lt;/code&gt; and &lt;code&gt;@movie&lt;/code&gt; resolve to objects with a valid &lt;code&gt;.id&lt;/code&gt; field at execution time.&lt;/p&gt;

&lt;p&gt;Worth knowing: an edge can be written to the database even if the vertices it points to don't exist yet. The edge record is created regardless, but any pattern that tries to traverse it will come back empty until both endpoints are actually present. This is why source order doesn't have to be strict — edges landing before their vertices are harmless, they just won't produce matches until the graph is complete enough to satisfy the pattern.&lt;/p&gt;

&lt;p&gt;If you want the &lt;strong&gt;edge itself to carry properties&lt;/strong&gt; (like a role name), you use the root object as the edge record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="n"&gt;.id&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record_id&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"acted_in"&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="n"&gt;msg.tmdbId&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg.movieId&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg.role&lt;/span&gt;&lt;span class="ss"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;.role&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;msg.role&lt;/span&gt;
&lt;span class="n"&gt;.person.id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record_id&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"person"&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="n"&gt;msg.tmdbId&lt;/span&gt;&lt;span class="ss"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;.movie.id&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record_id&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"movie"&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt;  &lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="n"&gt;msg.movieId&lt;/span&gt;&lt;span class="ss"&gt;])&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;movie&lt;/span&gt;    &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;middle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"use the root object as the edge record"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Direction is explicit: &lt;code&gt;-&amp;gt;&lt;/code&gt; means left-to-right, &lt;code&gt;&amp;lt;-&lt;/code&gt; means right-to-left. You can chain them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;acted_in&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;directed&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;movie&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="n"&gt;Two&lt;/span&gt; &lt;span class="n"&gt;edges&lt;/span&gt; &lt;span class="n"&gt;created&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;single&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Patterns — Matching the graph
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;patterns&lt;/code&gt; section is where things get interesting. Each pattern entry has a &lt;code&gt;match&lt;/code&gt; field containing a small DSL that describes the subgraph you're looking for.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;patterns&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|-&lt;/span&gt;
      &lt;span class="s"&gt;/&lt;/span&gt;
        &lt;span class="s"&gt;m:movie&amp;lt;-acted_in&amp;lt;-p:person-&amp;gt;directed-&amp;gt;m:movie&lt;/span&gt;
        &lt;span class="s"&gt;+ p-&amp;gt;acted_and_directed-&amp;gt;m&lt;/span&gt;
      &lt;span class="s"&gt;/ -&amp;gt; m as movieId, m.title as movie, p as personId, p.name as actor&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let me break this apart piece by piece.&lt;/p&gt;

&lt;h4&gt;
  
  
  Aliases and join conditions
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="py"&gt;m:&lt;/span&gt;&lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;acted_in&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="py"&gt;p:&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;directed&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="py"&gt;m:&lt;/span&gt;&lt;span class="n"&gt;movie&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;m:movie&lt;/code&gt; means: &lt;em&gt;match any vertex from the&lt;/em&gt; &lt;code&gt;movie&lt;/code&gt; &lt;em&gt;table and call it&lt;/em&gt; &lt;code&gt;m&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;p:person&lt;/code&gt; means: &lt;em&gt;match any vertex from the&lt;/em&gt; &lt;code&gt;person&lt;/code&gt; &lt;em&gt;table and call it&lt;/em&gt; &lt;code&gt;p&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;same alias used in multiple places means the same vertex&lt;/strong&gt;. Here &lt;code&gt;m&lt;/code&gt; appears on both sides — so the movie that &lt;code&gt;p&lt;/code&gt; acted in must be &lt;strong&gt;the exact same movie&lt;/strong&gt; that &lt;code&gt;p&lt;/code&gt; directed. That's your join condition, expressed naturally.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This single-line form is just a convenience. You can spread the same pattern across multiple lines and the meaning is identical — each line describes one edge, and shared aliases still enforce the join. All three of these are equivalent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="py"&gt;p:&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;acted_in&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="py"&gt;m:&lt;/span&gt;&lt;span class="n"&gt;movie&lt;/span&gt;
&lt;span class="py"&gt;p:&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;directed&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="py"&gt;m:&lt;/span&gt;&lt;span class="n"&gt;movie&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="py"&gt;m:&lt;/span&gt;&lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;acted_in&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="py"&gt;p:&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;
&lt;span class="py"&gt;m:&lt;/span&gt;&lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;directed&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="py"&gt;p:&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="py"&gt;m:&lt;/span&gt;&lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;acted_in&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="py"&gt;p:&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;
&lt;span class="py"&gt;p:&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;directed&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="py"&gt;m:&lt;/span&gt;&lt;span class="n"&gt;movie&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pick the direction that reads most naturally for the relationship you're describing. The engine doesn't care — it resolves the aliases and finds the join either way.&lt;/p&gt;

&lt;p&gt;This is the heart of the pattern language. You describe structure; the engine figures out the join.&lt;/p&gt;

&lt;h4&gt;
  
  
  Actions — Deriving new edges
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;acted_and_directed&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the full pattern is satisfied, this line fires. It creates a new &lt;code&gt;acted_and_directed&lt;/code&gt; edge between the matched &lt;code&gt;p&lt;/code&gt; and &lt;code&gt;m&lt;/code&gt; vertices.&lt;/p&gt;

&lt;p&gt;Duplicate prevention is built into the ID scheme: the derived edge gets a composite record ID based on the two endpoint vertices — something like &lt;code&gt;acted_and_directed:[person:190, movie:33]&lt;/code&gt;. That ID is always the same for the same pair, so re-triggering the match (say, because new edges arrived and the checker ran again) produces the exact same record rather than a second one. The operation is naturally idempotent.&lt;/p&gt;

&lt;h4&gt;
  
  
  Output projections
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="err"&gt;/&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;movieId&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m.title&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;personId&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p.name&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;actor&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the closing &lt;code&gt;/&lt;/code&gt;, you declare what gets emitted when a match is found. You can project full vertices (&lt;code&gt;m as movieId&lt;/code&gt;) or individual fields (&lt;code&gt;m.title as movie&lt;/code&gt;). Omit this section entirely and all matched aliases come back.&lt;/p&gt;

&lt;h4&gt;
  
  
  More pattern expressiveness
&lt;/h4&gt;

&lt;p&gt;The DSL could support more than simple linear paths. A few things worth being able to express:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multiple edge types&lt;/strong&gt; — match either &lt;code&gt;rated&lt;/code&gt; or &lt;code&gt;disliked&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="py"&gt;p:&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rated&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;disliked&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="py"&gt;m:&lt;/span&gt;&lt;span class="n"&gt;movie&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Wildcard edge&lt;/strong&gt; — match any edge type between two vertices:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="py"&gt;p1:&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="py"&gt;p2:&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Edge filters&lt;/strong&gt; — only match edges that satisfy a property condition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="py"&gt;p:&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;directed&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;award&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Oscar"&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="py"&gt;m:&lt;/span&gt;&lt;span class="n"&gt;movie&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Vertex filters&lt;/strong&gt; — only match vertices satisfying a condition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="py"&gt;p:&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;birth_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2000-01-01"&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;acted_in&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="py"&gt;m:&lt;/span&gt;&lt;span class="n"&gt;movie&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Multiple independent patterns&lt;/strong&gt; in a single block — all must be satisfied for the action to fire:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="err"&gt;/&lt;/span&gt;
  &lt;span class="py"&gt;p:&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;acted_in&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="py"&gt;m:&lt;/span&gt;&lt;span class="n"&gt;movie&lt;/span&gt;
  &lt;span class="py"&gt;p:&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;directed&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="py"&gt;m:&lt;/span&gt;&lt;span class="n"&gt;movie&lt;/span&gt;
  &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;acted_and_directed&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;
&lt;span class="err"&gt;/&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;m.title&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p.name&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;actor&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🔄 &lt;strong&gt;What would it look like to run this?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;At a high level, the tool would:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read the configuration file&lt;/strong&gt; and resolve any &lt;code&gt;$variable&lt;/code&gt; placeholders passed on the command line.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stream data from each source into the graph&lt;/strong&gt; — filtering rows, transforming them into vertices and edges via the &lt;code&gt;load&lt;/code&gt; scripts, and persisting everything to the database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Watch the graph for pattern completions&lt;/strong&gt; — as edges land, the tool continuously checks whether any of the declared patterns are now fully satisfied.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fire actions and print results immediately&lt;/strong&gt; — the moment a match is found, the &lt;code&gt;+&lt;/code&gt; edges are created and the projected output is flushed to stdout right away, without waiting for ingestion to finish.&lt;/p&gt;

&lt;p&gt;This last point matters: matches don't accumulate and print at the end. They appear as soon as they're found, interleaved with ongoing ingestion. If you're streaming a large dataset, you'd start seeing output long before the last row is processed.&lt;/p&gt;

&lt;p&gt;For the movie example, as soon as both the &lt;code&gt;acted_in&lt;/code&gt; and &lt;code&gt;directed&lt;/code&gt; edges involving Clint Eastwood and &lt;em&gt;Unforgiven&lt;/em&gt; are present, the pattern is satisfied and a line appears on stdout. Morgan Freeman only acted, so he never completes the pattern — no false match, no output for him.&lt;/p&gt;

&lt;p&gt;The system is also tolerant of edges arriving out of order. If a &lt;code&gt;directed&lt;/code&gt; edge lands before the corresponding &lt;code&gt;acted_in&lt;/code&gt; edge exists yet, the check is retried automatically once the missing piece shows up. You don't have to pre-sort your data or reason about arrival order.&lt;/p&gt;

&lt;p&gt;The key design intent is that you never have to write a query. You declare the shape of what you're looking for and let the tool figure out when it exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧠 &lt;strong&gt;Why this configuration style?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A few deliberate design choices worth calling out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One file per source, multiple sources per file.&lt;/strong&gt; The same CSV can produce vertices, edges, and genre tags all at once — by duplicating the source entry with different filters. No preprocessing step required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VRL for transformations.&lt;/strong&gt; It's expressive enough to handle type coercions, array splits, conditional logic, and nested structures, while being safe and sandboxed. No need to invent yet another mini-language for data wrangling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aliases as join keys.&lt;/strong&gt; Using the same alias name to enforce vertex identity across pattern lines is a small syntax choice that turns out to be remarkably readable. The pattern says what you mean.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Declarative actions.&lt;/strong&gt; The &lt;code&gt;+&lt;/code&gt; syntax for derived edges mirrors the &lt;code&gt;+&lt;/code&gt; syntax in the &lt;code&gt;load&lt;/code&gt; scripts. Once you understand one, you understand the other.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Idempotent by construction.&lt;/strong&gt; Derived edges get a deterministic ID from their endpoints, so running the same match twice is harmless. No deduplication logic to write, no risk of polluting the graph with duplicate relationships.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Results stream, they don't batch.&lt;/strong&gt; Output is flushed to stdout the moment a match is confirmed. You can pipe the output, process it downstream, or just watch it in a terminal — it behaves like any other streaming Unix tool.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  👉 &lt;strong&gt;TL;DR&lt;/strong&gt;
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Streaming graph pattern matching&lt;/strong&gt; is about detecting when a meaningful subgraph completes — incrementally, as edges arrive, without scanning the whole graph.&lt;/p&gt;

&lt;p&gt;A YAML config maps raw data to graph elements via VRL, and a declarative DSL expresses the patterns you care about. When a pattern matches, derived edges are created automatically.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the next post, I'll show you the actual implementation: the tech stack, how the pieces fit together under the hood, and what the database looks like once everything is loaded. Stay tuned. 🛠️&lt;/p&gt;

</description>
      <category>database</category>
    </item>
    <item>
      <title>Introducing Scale: SurrealDB Cloud, built for high availability and scale</title>
      <dc:creator>Mark Gyles</dc:creator>
      <pubDate>Thu, 02 Jul 2026 15:53:29 +0000</pubDate>
      <link>https://dev.to/surrealdb/introducing-scale-surrealdb-cloud-built-for-high-availability-and-scale-2ib</link>
      <guid>https://dev.to/surrealdb/introducing-scale-surrealdb-cloud-built-for-high-availability-and-scale-2ib</guid>
      <description>&lt;p&gt;Author: &lt;a href="https://x.com/tobiemh" rel="noopener noreferrer"&gt;Tobie Morgan Hitchcock&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Today we're launching Scale, a new tier of SurrealDB Cloud built for the workloads you can't afford to have go down.&lt;/p&gt;

&lt;p&gt;Our first tier, Start, was designed for building and shipping fast. Scale is designed for what happens next: production traffic, uptime commitments, and the kind of resilience your users never notice because there are always available nodes. It's the tier for teams running SurrealDB as the scalable context layer behind real applications and AI agents in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get with Scale
&lt;/h2&gt;

&lt;p&gt;Scale is about one thing: keeping your database available and consistent under real-world conditions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Highly-available, fault-tolerant clusters.&lt;/strong&gt; Scale runs your database as a multi-node cluster designed to survive node and infrastructure failures without dropping writes or losing consistency. A single point of failure is no longer a single point of downtime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multiple availability-zone deployment.&lt;/strong&gt; Your cluster is distributed across multiple availability zones, so even the loss of an entire zone doesn't take your database with it. Traffic keeps flowing while the cluster recovers in the background.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Horizontal scale.&lt;/strong&gt; As demand grows, Scale grows with it. Add capacity by scaling out across nodes rather than being capped by the size of a single machine. Start with three nodes, and keep adding to scale your application or agent's needs.&lt;/p&gt;

&lt;p&gt;See more information about SurrealDB Cloud Scale architecture &lt;a href="https://surrealdb.com/docs/manage/cloud/architecture#scale" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built on SurrealDS
&lt;/h2&gt;

&lt;p&gt;Scale is powered by &lt;a href="https://surrealdb.com/platform/surrealds" rel="noopener noreferrer"&gt;SurrealDS&lt;/a&gt;, SurrealDB's distributed storage engine and the foundation that makes all of this possible.&lt;/p&gt;

&lt;p&gt;SurrealDS is a new generation distributed storage architecture, rethought from first principles. Instead of coupling storage to compute on a single box or to a proprietary cloud tier, SurrealDS embeds consensus directly in SurrealDB nodes and separates the two layers cleanly. Here's what that architecture gives you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture overview
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Compute and storage separation.&lt;/strong&gt; Scale compute for QPS and storage for capacity independently, so you provision for the dimension that's actually under pressure.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No single leader.&lt;/strong&gt; Each availability-zone node writes locally, writes scale horizontally, and transactions commit once a quorum acknowledges them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Multi-write nodes.&lt;/strong&gt; Every write node in the cluster can accept and coordinate transactions - there's no bottleneck routing all writes through one leader.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reduced operational overhead.&lt;/strong&gt; Consensus is embedded directly in SurrealDB nodes, eliminating external coordination services.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fewer network dependencies.&lt;/strong&gt; A single broadcast replaces multi-hop coordination, reducing latency.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Lower latency than leader-based replication.&lt;/strong&gt; With no single leader and each AZ node writing locally, transactions avoid the extra round trips of traditional leader-based systems.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Future roadmap
&lt;/h2&gt;

&lt;p&gt;Several SurrealDS capabilities are on the near-term roadmap and will land in the Scale tier as they ship:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Object-storage backed.&lt;/strong&gt; Persist transactional data directly to commodity object storage - storage costs drop and capacity scales far beyond any single disk.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cross-region replication.&lt;/strong&gt; Data flows through object storage rather than between nodes, significantly reducing data-transfer costs while keeping regions in sync.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Instant recovery.&lt;/strong&gt; A crashed node restores from object storage, with recovery time almost instant regardless of dataset size.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Instant branching.&lt;/strong&gt; Clone a petabyte-scale database in seconds for development, testing, or experimentation.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Get started
&lt;/h2&gt;

&lt;p&gt;If you're running SurrealDB in production, or getting ready to, Scale gives you the availability and fault-tolerance your workloads need, on the storage architecture no one else has.&lt;/p&gt;

&lt;p&gt;Explore the tier in &lt;a href="https://surrealdb.com/cloud" rel="noopener noreferrer"&gt;SurrealDB Cloud&lt;/a&gt;, and dig into the engine underneath at &lt;a href="https://surrealdb.com/platform/surrealds" rel="noopener noreferrer"&gt;SurrealDS&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>surrealdb</category>
      <category>surrealdbcloud</category>
      <category>cloud</category>
      <category>database</category>
    </item>
    <item>
      <title>Bring SurrealDB to your Replit Agent</title>
      <dc:creator>itsezc</dc:creator>
      <pubDate>Thu, 02 Jul 2026 15:23:55 +0000</pubDate>
      <link>https://dev.to/surrealdb/bring-surrealdb-to-your-replit-agent-57i</link>
      <guid>https://dev.to/surrealdb/bring-surrealdb-to-your-replit-agent-57i</guid>
      <description>&lt;p&gt;Replit Agent turns a prompt into a working app. SurrealDB is the one database behind that app, covering documents, graph, vectors, and SQL in a single engine. The piece that connects them is the Model Context Protocol (MCP).&lt;/p&gt;

&lt;p&gt;Every SurrealDB instance running 3.1 or later can expose a first-party MCP server: a typed tool surface that AI agents call to inspect your schema and run queries safely. Replit Agent supports any MCP server as a connector, and adds them with a single click. Put the two together and Replit can build directly from the data you already have in SurrealDB.&lt;/p&gt;

&lt;p&gt;This is vibe coding for teams that already own their data. Instead of letting an agentic coding tool improvise a throwaway backend, you point Replit at SurrealDB and build on a database you already trust, with the agent reading your real schema as it goes.&lt;/p&gt;

&lt;p&gt;This guide is a hands-on quickstart. By the end you will have SurrealDB 3.1 running with its MCP server enabled, connected to a Replit project, and you will have prompted Replit Agent to generate a real interface on top of your data.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll need
&lt;/h2&gt;

&lt;p&gt;You need a few things before you start, and none of them take more than a few minutes to set up.&lt;/p&gt;

&lt;p&gt;You need SurrealDB 3.1.0 or later. You also need a way to reach your instance from the internet, because Replit's connectors talk to your MCP server over HTTP and the server needs a public URL. A SurrealDB Cloud instance gives you one out of the box; for a local instance you can use a tunnel, which we cover below. You need a Replit account. Finally, you need some data. Even a handful of records is enough to see the workflow end to end, and we create a small schema below so you have something to build against.&lt;/p&gt;

&lt;p&gt;It is worth noting how this differs from Replit's built-in database. Replit Agent provisions a managed Postgres instance for the app it builds, which it owns. With SurrealDB over MCP, you own the database. Replit connects to a SurrealDB instance you already run, reads its schema as context, and can query or mutate it through a typed, permissioned tool surface. This is the right pattern when SurrealDB is your system of record and you want to build interfaces on top of it rather than spin up a new backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  How SurrealDB, MCP, and Replit fit together
&lt;/h2&gt;

&lt;p&gt;There are three moving parts to understand before the steps.&lt;/p&gt;

&lt;p&gt;SurrealDB 3.1 exposes a typed tool surface for AI agents. Rather than handing an agent a raw SQL console, the MCP server presents twelve defined tools: &lt;code&gt;query&lt;/code&gt;, &lt;code&gt;select&lt;/code&gt;, &lt;code&gt;create&lt;/code&gt;, &lt;code&gt;insert&lt;/code&gt;, &lt;code&gt;upsert&lt;/code&gt;, &lt;code&gt;update&lt;/code&gt;, &lt;code&gt;delete&lt;/code&gt;, &lt;code&gt;relate&lt;/code&gt;, &lt;code&gt;info&lt;/code&gt;, &lt;code&gt;list&lt;/code&gt;, &lt;code&gt;use&lt;/code&gt;, and &lt;code&gt;run&lt;/code&gt;. Each tool carries annotations (&lt;code&gt;read_only_hint&lt;/code&gt;, &lt;code&gt;destructive_hint&lt;/code&gt;, &lt;code&gt;idempotent_hint&lt;/code&gt;) so an MCP client like Replit knows which operations are safe and which mutate data. The server also publishes self-describing schema resources at URIs like &lt;code&gt;surrealdb://schema/ns/{ns}/db/{db}/table/{table}&lt;/code&gt;, so the agent discovers your tables and fields instead of guessing.&lt;/p&gt;

&lt;p&gt;You can run that MCP server two ways. Locally, &lt;code&gt;surreal mcp&lt;/code&gt; runs as a stdio subcommand, which suits IDE integrations on your own machine. For a remote client like Replit, the server is exposed over HTTP at &lt;code&gt;/mcp&lt;/code&gt;, sitting behind SurrealDB's existing authentication middleware. We use the HTTP path here, because Replit needs to reach your server over the network.&lt;/p&gt;

&lt;p&gt;Replit Agent treats MCP servers as connectors. Replit ships a curated list of MCP servers that install in one click, and lets you add any other server, including your SurrealDB instance, from the Integrations pane. All MCP traffic passes through Replit's security scanner, which inspects tool definitions and planned executions and blocks anything it judges unsafe before it runs. Once your SurrealDB endpoint is connected, the agent pulls your live schema and data into context while it builds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install or upgrade to SurrealDB 3.1
&lt;/h2&gt;

&lt;p&gt;If you are already on a 3.1.x release you can skip ahead. Otherwise, pick whichever install path matches your setup. Each pins to a specific version; drop the &lt;code&gt;--version&lt;/code&gt; flag to always get the newest stable.&lt;/p&gt;

&lt;p&gt;macOS (Homebrew), then upgrade in place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;surreal upgrade --version 3.1.5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Linux and macOS install script, which auto-detects your architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -sSf https://install.surrealdb.com | sh -s -- --version 3.1.5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Windows (PowerShell), then upgrade in place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;surreal upgrade --version 3.1.5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docker, to pull and run the exact image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run --rm --pull always -p 8000:8000 surrealdb/surrealdb:v3.1.5 start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;surreal upgrade&lt;/code&gt; command can swap any existing install, whether Homebrew, install script, or manual binary, to the version you specify. Patch releases on the 3.1 line are drop-in upgrades.&lt;/p&gt;

&lt;p&gt;If you are on SurrealDB Cloud, you can upgrade your instance in place from the Surrealist app, and you already have a public HTTPS endpoint, which makes the networking in Step 4 considerably simpler.&lt;/p&gt;

&lt;p&gt;Verify your version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;surreal version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Start your instance and create some data
&lt;/h2&gt;

&lt;p&gt;For a local run, start a server with authentication enabled. The MCP HTTP endpoint sits behind the same auth middleware as the rest of SurrealDB, so credentials matter here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;surreal start \
  --user root --pass pass \
  rocksdb://mydata.db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This starts SurrealDB on &lt;code&gt;http://localhost:8000&lt;/code&gt; with a persistent RocksDB store and a root user. In production you would scope down to a namespace and database user rather than root, which we cover under permissions later.&lt;/p&gt;

&lt;p&gt;Now give Replit something to build against. Use the CLI's &lt;code&gt;surreal sql&lt;/code&gt; command to open a session, or use Surrealist, the visual query tool, and define a small schema. We model a simple product catalog with reviews, which is enough to show off SurrealDB's record links.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-- Pick a namespace and database to work in
USE NS shop DB catalog;

-- A products table
DEFINE TABLE product SCHEMAFULL;
DEFINE FIELD name        ON product TYPE string;
DEFINE FIELD price       ON product TYPE number;
DEFINE FIELD in_stock    ON product TYPE bool DEFAULT true;
DEFINE FIELD created_at  ON product TYPE datetime DEFAULT time::now();

-- A reviews table that links back to a product
DEFINE TABLE review SCHEMAFULL;
DEFINE FIELD product ON review TYPE record&amp;lt;product&amp;gt; REFERENCE;
DEFINE FIELD rating  ON review TYPE int ASSERT $value IN 0..=5;
DEFINE FIELD body    ON review TYPE string;

-- Seed a few records
CREATE product SET name = "Aeropress", price = 39.95;
CREATE product SET name = "Gooseneck Kettle", price = 64.00;
CREATE product SET name = "Burr Grinder", price = 129.00, in_stock = false;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;SCHEMAFULL&lt;/code&gt; table means SurrealDB enforces the field definitions, which is what you want when an AI agent is going to read and write the table. The schema becomes the contract the agent builds against. Because SurrealDB publishes that schema as an MCP resource, Replit reads these field types directly rather than inferring them from sample rows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Enable the MCP server over HTTP
&lt;/h2&gt;

&lt;p&gt;In 3.1, the MCP HTTP surface is served at &lt;code&gt;/mcp&lt;/code&gt; on the same port as the rest of the HTTP API, behind authentication. A handful of environment variables let you tune its limits. The defaults are sensible, but it is worth knowing they exist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SURREAL_HTTP_MAX_MCP_BODY_SIZE&lt;/code&gt;: maximum request body size (default 4 MiB)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SURREAL_MCP_QUERY_TIMEOUT_SECS&lt;/code&gt;: per-query timeout (default 60 s)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SURREAL_MCP_MAX_RESULT_BYTES&lt;/code&gt;: maximum result payload (default 256 KiB)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SURREAL_MCP_RUN_MAX_ARGS&lt;/code&gt;: maximum arguments to the run tool (default 64)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SURREAL_MCP_PARAMS_MAX_KEYS&lt;/code&gt;: maximum bound parameters per call (default 256)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, to allow larger result payloads while keeping a tight query timeout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SURREAL_MCP_MAX_RESULT_BYTES=1048576 \
SURREAL_MCP_QUERY_TIMEOUT_SECS=30 \
surreal start --user root --pass pass rocksdb://mydata.db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For PowerShell users on Windows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;SURREAL_MCP_MAX_RESULT_BYTES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1048576"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;SURREAL_MCP_QUERY_TIMEOUT_SECS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"30"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;surreal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--pass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rocksdb://mydata.db&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If instead you want to wire SurrealDB into a local IDE rather than Replit, run the stdio variant, &lt;code&gt;surreal mcp&lt;/code&gt;, and point your editor's MCP client at that subcommand. For Replit, stick with the HTTP endpoint.&lt;/p&gt;

&lt;p&gt;A good sanity check before involving Replit is to confirm the &lt;code&gt;/mcp&lt;/code&gt; route is reachable and that requests without valid credentials are rejected, which tells you the auth middleware is doing its job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Make your instance reachable
&lt;/h2&gt;

&lt;p&gt;Replit runs in the cloud, so it needs a public URL for your MCP endpoint. You have two clean options.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1: SurrealDB Cloud.&lt;/strong&gt; If your data lives in a SurrealDB Cloud instance, you already have a public HTTPS endpoint with managed TLS and authentication. Your MCP URL is simply that instance's address with the &lt;code&gt;/mcp&lt;/code&gt; path appended. This is the lowest-friction path and the one we recommend for anything beyond local experimentation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 2: A tunnel to a local instance.&lt;/strong&gt; For local development, expose &lt;code&gt;http://localhost:8000&lt;/code&gt; through a tunneling service that gives you a temporary public HTTPS URL, such as ngrok or Cloudflare Tunnel. Your MCP URL is then &lt;code&gt;https://&amp;lt;tunnel-host&amp;gt;/mcp&lt;/code&gt;. This works well for trying things out, but treat the URL as ephemeral and never point it at production data.&lt;/p&gt;

&lt;p&gt;Either way, you end up with a single value to hand to Replit: an HTTPS URL ending in &lt;code&gt;/mcp&lt;/code&gt;, plus the credentials needed to authenticate against it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Security note:&lt;/strong&gt; The MCP endpoint inherits SurrealDB's authentication and permissions. Before you expose anything, create a dedicated database user scoped to just the namespace and database you want Replit to touch, rather than handing over root. SurrealDB enforces record-level and field-level permissions on every query the agent runs, so a properly scoped user cannot read or write beyond what you have granted, even if the agent asks it to.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Connect SurrealDB to Replit
&lt;/h2&gt;

&lt;p&gt;Now switch over to Replit. Because MCP is a standard, you add SurrealDB the same way you would any custom server.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open your app in the Project Editor and open the Integrations pane (or go to replit.com/integrations).&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;Add new integration&lt;/strong&gt;, then add a custom MCP server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server URL:&lt;/strong&gt; paste your MCP endpoint, for example &lt;code&gt;https://your-instance.surrealdb.cloud/mcp&lt;/code&gt; or your tunnel URL ending in &lt;code&gt;/mcp&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication:&lt;/strong&gt; Replit supports OAuth dynamic client registration where a server offers it, or custom headers for static tokens. For a SurrealDB instance, use a custom header with key &lt;code&gt;Authorization&lt;/code&gt; and value &lt;code&gt;Bearer &amp;lt;token&amp;gt;&lt;/code&gt; for your scoped database user. Reserve no-auth endpoints for a throwaway local demo, never for real data.&lt;/li&gt;
&lt;li&gt;Authorize the connection. Replit's security scanner inspects the server's tools, and the connection persists across your apps once approved.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Build an app on your SurrealDB data with Replit
&lt;/h2&gt;

&lt;p&gt;With the connector live, Replit Agent calls SurrealDB's tools to discover your schema and read your records, then generates UI that is wired to that data.&lt;/p&gt;

&lt;p&gt;Start by pulling your data into context with a prompt like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Using my SurrealDB connector, list the tables in the shop/catalog database and show me the fields on the product table.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This prompts Replit to call the &lt;code&gt;list&lt;/code&gt; and &lt;code&gt;info&lt;/code&gt; tools and read the schema resource, then report back what it found. Behind the scenes, &lt;code&gt;use&lt;/code&gt; selects the &lt;code&gt;shop&lt;/code&gt; namespace and &lt;code&gt;catalog&lt;/code&gt; database, and the schema resource at &lt;code&gt;surrealdb://schema/ns/shop/db/catalog/table/product&lt;/code&gt; tells Replit that &lt;code&gt;product&lt;/code&gt; has &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;price&lt;/code&gt;, &lt;code&gt;in_stock&lt;/code&gt;, and &lt;code&gt;created_at&lt;/code&gt; with their exact types.&lt;/p&gt;

&lt;p&gt;Once it understands your schema, ask it to build:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Build a product catalog page that shows every product from SurrealDB as a card with its name, price, and stock status. Add a filter to show only in-stock items.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Replit reads your live products via the &lt;code&gt;select&lt;/code&gt; tool, scaffolds a front end, and binds the components to the real fields. Because the agent knows &lt;code&gt;in_stock&lt;/code&gt; is a boolean from the schema, the filter it builds is correct on the first pass rather than a guess.&lt;/p&gt;

&lt;p&gt;You can go further and let it traverse relationships, which uses SurrealDB's graph capabilities:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Add a detail view for each product that lists its reviews, the rating and body, pulled from the review table that links to the product.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here SurrealDB's record links do the heavy lifting. A SurrealQL query like &lt;code&gt;SELECT *, &amp;lt;~review.{ rating, body } AS reviews FROM product&lt;/code&gt; fetches each product together with its reviews in one round trip. The &lt;code&gt;&amp;lt;~&lt;/code&gt; operator walks incoming record references the same way graph queries do, and Replit issues exactly that through the &lt;code&gt;query&lt;/code&gt; tool.&lt;/p&gt;

&lt;p&gt;When you ask Replit to write data, such as adding a form to submit a new review, the agent reaches for the &lt;code&gt;create&lt;/code&gt; or &lt;code&gt;insert&lt;/code&gt; tool. Because those tools carry the non-read-only annotations, Replit knows they mutate state and can surface a confirmation before anything is written. Your SurrealDB permissions remain the backstop: if the connected user lacks &lt;code&gt;CREATE&lt;/code&gt; permission on &lt;code&gt;review&lt;/code&gt;, the write fails at the database regardless of what the agent attempts.&lt;/p&gt;

&lt;h2&gt;
  
  
  SurrealDB MCP tools reference
&lt;/h2&gt;

&lt;p&gt;Knowing what each tool does tells you exactly what you can ask Replit to do with your data.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;query&lt;/code&gt;&lt;/strong&gt;: run arbitrary SurrealQL. The most powerful tool, and the one behind any complex read or graph traversal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;select&lt;/code&gt;&lt;/strong&gt;: read records from a table, optionally filtered.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;create&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;insert&lt;/code&gt;&lt;/strong&gt;: add new records, with insert geared toward bulk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;upsert&lt;/code&gt;&lt;/strong&gt;: create or update depending on whether the record exists.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;update&lt;/code&gt;&lt;/strong&gt;: modify existing records.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;delete&lt;/code&gt;&lt;/strong&gt;: remove records, which is a destructive operation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;relate&lt;/code&gt;&lt;/strong&gt;: create graph edges between records, SurrealDB's relational strength.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;info&lt;/code&gt;&lt;/strong&gt;: describe the structure of a namespace, database, or table.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;list&lt;/code&gt;&lt;/strong&gt;: enumerate available namespaces, databases, or tables.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;use&lt;/code&gt;&lt;/strong&gt;: select the namespace and database to operate within.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;run&lt;/code&gt;&lt;/strong&gt;: invoke a defined function on the server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The read-only tools (&lt;code&gt;select&lt;/code&gt;, &lt;code&gt;info&lt;/code&gt;, &lt;code&gt;list&lt;/code&gt;) are safe to let an agent call freely. The mutating tools (&lt;code&gt;create&lt;/code&gt;, &lt;code&gt;insert&lt;/code&gt;, &lt;code&gt;upsert&lt;/code&gt;, &lt;code&gt;update&lt;/code&gt;, &lt;code&gt;delete&lt;/code&gt;, &lt;code&gt;relate&lt;/code&gt;) are the ones the hint annotations flag, and the ones your database permissions should govern most tightly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production considerations for SurrealDB with Replit
&lt;/h2&gt;

&lt;p&gt;A few things are worth getting right before you let real users near a SurrealDB-backed Replit app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scope the connected user tightly.&lt;/strong&gt; Create a database-level user with only the permissions the app needs. SurrealDB's permission model is enforced on every query, including record-level and field-level &lt;code&gt;SELECT&lt;/code&gt;, &lt;code&gt;CREATE&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, and &lt;code&gt;DELETE&lt;/code&gt; permissions, so the agent is constrained by the database, not just by good behavior.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use the limits.&lt;/strong&gt; The MCP environment variables exist to keep a chatty agent from overwhelming your instance. Set a sensible &lt;code&gt;SURREAL_MCP_QUERY_TIMEOUT_SECS&lt;/code&gt; and &lt;code&gt;SURREAL_MCP_MAX_RESULT_BYTES&lt;/code&gt; for your workload.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prefer Cloud or a stable endpoint over a dev tunnel.&lt;/strong&gt; Tunnels work for trying this out, but they are ephemeral. For anything persistent, a SurrealDB Cloud instance, or your own properly secured deployment, is the right home.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep your token out of shared links.&lt;/strong&gt; A Replit install link with an &lt;code&gt;Authorization&lt;/code&gt; header baked in carries a real credential. Generate it for your own use and rotate the token if it leaks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this leaves you
&lt;/h2&gt;

&lt;p&gt;You now have a SurrealDB instance whose schema and data are first-class context for Replit Agent, exposed through a typed, permissioned, observable MCP surface. The same connection that lets Replit build a catalog page would let it scaffold an admin dashboard, a customer-facing storefront, or an internal tool, all reading from and writing to the one database you control.&lt;/p&gt;

&lt;p&gt;That is the shape of the SurrealDB and Replit workflow: SurrealDB is the system of record and the source of truth for structure, and Replit is the interface layer that builds on top of it. MCP is the standard that makes the two speak the same language, with no bespoke integration to maintain on either side. It is what turns vibe coding and agentic coding from a demo trick into a workflow you can run against your own data.&lt;/p&gt;

&lt;p&gt;From here, a few good next steps are to define a SurrealDB function and invoke it from Replit via the &lt;code&gt;run&lt;/code&gt; tool, so business logic lives in the database where the agent can reuse it; to model a richer graph with &lt;code&gt;RELATE&lt;/code&gt; and ask Replit to build views that traverse it; and to move from a dev tunnel to a Cloud instance and tighten your user permissions for a real deployment.&lt;/p&gt;

</description>
      <category>surrealdb</category>
      <category>replit</category>
      <category>mcp</category>
      <category>agents</category>
    </item>
    <item>
      <title>What's new in Surrealist 3.9</title>
      <dc:creator>Matthew McFadden</dc:creator>
      <pubDate>Tue, 23 Jun 2026 17:10:31 +0000</pubDate>
      <link>https://dev.to/surrealdb/whats-new-in-surrealist-39-2eb2</link>
      <guid>https://dev.to/surrealdb/whats-new-in-surrealist-39-2eb2</guid>
      <description>&lt;p&gt;We're excited to announce the release of Surrealist 3.9! This version introduces a complete design overhaul, a new datasets browser and data manager, an improved record inspector, and much more. Let's dive into what's new 🎉&lt;/p&gt;

&lt;h2&gt;
  
  
  Highlights
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Complete design overhaul
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fprhvvd2rexjz4b8e7sry.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fprhvvd2rexjz4b8e7sry.webp" alt="Surrealist overview page" width="800" height="478"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Surrealist 3.9 introduces the most significant visual refresh since Surrealist 2.0. From the new sidebar and topbar to the fully revamped light theme, the entire interface has been redesigned to feel fresher, cleaner, more consistent, and easier to navigate.&lt;/p&gt;

&lt;p&gt;Namespace and database selection has been unified into a single selection menu, making it faster to switch context without the need to navigate separate menus. Additionally, the new search input makes it easy to find what you need, even in large deployments.&lt;/p&gt;

&lt;p&gt;Connection and instance settings have also been completely redesigned, and the overview page now includes search for both connections and organisations. These changes make it easier to find what you are looking for no matter how many instances you manage.&lt;/p&gt;

&lt;h3&gt;
  
  
  New datasets browser and data manager
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9h38frn627jwqmkw09b8.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9h38frn627jwqmkw09b8.webp" alt="Surrealist data manager page" width="800" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Working with imports and exports of your database and trying out new features with official SurrealDB demo datasets has never been easier! Surrealist 3.9 introduces a data manager and dedicated datasets browser to make managing bulk data in your database faster and more intuitive. &lt;/p&gt;

&lt;p&gt;Additionally, it is now easier than ever to discover, browse, and apply official SurrealDB datasets to your database or Sandbox instance so that you can learn about and try new SurrealDB features in an interactive manner without needing to scroll through docs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Improved record inspector
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm6h2jhd38btzozz6pxbo.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm6h2jhd38btzozz6pxbo.webp" alt="Surrealist record inspector" width="800" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The record inspector has been significantly enhanced to give you a deeper view of your data. The new&amp;nbsp;References&amp;nbsp;and&amp;nbsp;Live&amp;nbsp;tabs make it easier to explore record relationships and monitor real-time changes, while the new actions menu gives you more control over how you interact with individual records.&lt;/p&gt;

&lt;p&gt;These improvements make the record inspector a more capable tool for day-to-day data exploration whether you are tracing graph relationships, inspecting linked records, or watching live query results update in real time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full changelog
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Improved designer view to use ALTER statements in 3.x instances&lt;/li&gt;
&lt;li&gt;Overhauled Surrealist design

&lt;ul&gt;
&lt;li&gt;Improved brand icons&lt;/li&gt;
&lt;li&gt;Redesigned light theme&lt;/li&gt;
&lt;li&gt;Redesigned sidebar and topbar&lt;/li&gt;
&lt;li&gt;Added a toggle sidebar button to the topbar&lt;/li&gt;
&lt;li&gt;Moved settings button to a menu item in the topbar when logged out and account menu when logged in&lt;/li&gt;
&lt;li&gt;Unified the namespace and database selectors into a single selection menu&lt;/li&gt;
&lt;li&gt;Completely redesigned connection and instance settings&lt;/li&gt;
&lt;li&gt;Implemented new search functionality for connections and organisations on the overview page&lt;/li&gt;
&lt;li&gt;Revamped and re-enabled API Docs view&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Implemented a new datasets browser and data manager (&lt;a href="https://github.com/surrealdb/surrealist/issues/1224" rel="noopener noreferrer"&gt;#1224&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Improved the record inspector with new references and live tabs, and new actions&lt;/li&gt;
&lt;li&gt;Implemented VIM mode setting (&lt;a href="https://github.com/surrealdb/surrealist/issues/106" rel="noopener noreferrer"&gt;#106&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Implemented the ability to toggle tables in the designer (&lt;a href="https://github.com/surrealdb/surrealist/issues/372" rel="noopener noreferrer"&gt;#372&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Implemented default namespace and database functionality for 3.x instances&lt;/li&gt;
&lt;li&gt;Implemented namespace and database comment editor&lt;/li&gt;
&lt;li&gt;Added namespace and database comments to selection menu and database settings page&lt;/li&gt;
&lt;li&gt;Added search inputs to the namespace and database selection lists&lt;/li&gt;
&lt;li&gt;Added designer view LOD settings for zoom-based table simplification (&lt;a href="https://github.com/surrealdb/surrealist/issues/1222" rel="noopener noreferrer"&gt;#1222&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Added pagination to the newsletter drawer&lt;/li&gt;
&lt;li&gt;Fixed title bar overlapping drawers on Windows and Linux&lt;/li&gt;
&lt;li&gt;Fixed authentication redirect on session expiry&lt;/li&gt;
&lt;li&gt;Fixed Windows install art not showing for new installations&lt;/li&gt;
&lt;li&gt;Fixed Surrealist mini not allowing datasets other than surreal-deal-store&lt;/li&gt;
&lt;li&gt;Fixed an error where imports always showed success, even when they failed&lt;/li&gt;
&lt;li&gt;Fixed 2.x sample data datasets being applied to 3.x databases&lt;/li&gt;
&lt;li&gt;Fixed explorer returning no rows when sorting on empty columns (&lt;a href="https://github.com/surrealdb/surrealist/issues/1194" rel="noopener noreferrer"&gt;#1194&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Fixed interface and designer zoom not applying on all platforms (&lt;a href="https://github.com/surrealdb/surrealist/issues/1188" rel="noopener noreferrer"&gt;#1188&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Clean up stale graph connections after deleting edge tables (&lt;a href="https://github.com/surrealdb/surrealist/issues/1147" rel="noopener noreferrer"&gt;#1147&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Fixed an issue where clicking a table in designer view would cause a crash (&lt;a href="https://github.com/surrealdb/surrealist/issues/1238" rel="noopener noreferrer"&gt;#1238&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Fixed an issue causing large conection urls to cause UI overflow in connection cards (&lt;a href="https://github.com/surrealdb/surrealist/issues/1225" rel="noopener noreferrer"&gt;#1225&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Fixed broken documentation links (&lt;a href="https://github.com/surrealdb/surrealist/issues/1217" rel="noopener noreferrer"&gt;#1217&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Fixed passing URLs through desktop launcher (&lt;a href="https://github.com/surrealdb/surrealist/pull/1179" rel="noopener noreferrer"&gt;#1179&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Fixed Designer exports including lower LOD levels&lt;/li&gt;
&lt;li&gt;Fixed designer not showing links for complex ids (&lt;a href="https://github.com/surrealdb/surrealist/issues/1063" rel="noopener noreferrer"&gt;#1063&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Fixed UUID records not resolving correctly in graph (&lt;a href="https://github.com/surrealdb/surrealist/issues/974" rel="noopener noreferrer"&gt;#974&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Fixed clipboard writes failing in some browsers (&lt;a href="https://github.com/surrealdb/surrealist/issues/994" rel="noopener noreferrer"&gt;#994&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Fixed GraphQL view authentication (&lt;a href="https://github.com/surrealdb/surrealist/issues/1101" rel="noopener noreferrer"&gt;#1101&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We hope you enjoy these new features and improvements! As always, we appreciate your feedback and suggestions for future releases. &lt;a href="https://discord.com/invite/surrealdb" rel="noopener noreferrer"&gt;Join the SurrealDB Discord&lt;/a&gt; to engage with the community and receive support.&lt;/p&gt;

&lt;p&gt;Get started for free today at &lt;a href="https://app.surrealdb.com/c/sandbox/query" rel="noopener noreferrer"&gt;app.surrealdb.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>database</category>
      <category>surrealdb</category>
      <category>news</category>
      <category>web</category>
    </item>
    <item>
      <title>SurrealDB is now available on the Nebius AI Cloud Marketplace</title>
      <dc:creator>Mark Gyles</dc:creator>
      <pubDate>Thu, 18 Jun 2026 15:33:29 +0000</pubDate>
      <link>https://dev.to/surrealdb/surrealdb-is-now-available-on-the-nebius-ai-cloud-marketplace-34fj</link>
      <guid>https://dev.to/surrealdb/surrealdb-is-now-available-on-the-nebius-ai-cloud-marketplace-34fj</guid>
      <description>&lt;p&gt;Author: &lt;a href="https://x.com/tobiemh" rel="noopener noreferrer"&gt;Tobie Morgan Hitchcock&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We're excited to announce that SurrealDB is now officially available on the Nebius AI Cloud Marketplace.&lt;/p&gt;

&lt;p&gt;This launch reflects our commitment to meeting developers and AI teams where they build, and deepens our partnership with Nebius to make SurrealDB easier to adopt, deploy, and scale on one of the fastest-growing AI cloud platforms in the world.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Nebius?
&lt;/h2&gt;

&lt;p&gt;Nebius is a full-stack AI cloud built for the demands of modern AI workloads. Powered by the latest GPU and high-performance networking infrastructure, with managed platform, serverless AI, and storage built for the entire AI lifecycle, Nebius gives teams the infrastructure to train, tune, and serve models at scale.&lt;/p&gt;

&lt;p&gt;As an NVIDIA Reference Platform Cloud Partner with infrastructure across Europe and the US, Nebius pairs bare-metal performance with the operational simplicity and cost efficiency that AI teams need to move from experiment to production. It's a natural home for SurrealDB - giving you a unified data layer right alongside the compute powering your AI and agentic applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why SurrealDB?
&lt;/h2&gt;

&lt;p&gt;SurrealDB combines document, graph, and relational capabilities in one unified database - designed for agent memory, context layers, modern applications, real-time systems, and AI-powered workloads.With flexible schema, SurrealQL, and built-in real-time features, SurrealDB enables you to build semantic and context layers powered by knowledge graphs for agent memory and multi-agentic workflows. &lt;/p&gt;

&lt;p&gt;SurrealDB simplifies your technology stack, reduces Total Cost of Ownership and operational complexity, and allows you to ship products and features in days rather than weeks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get started
&lt;/h2&gt;

&lt;p&gt;SurrealDB is available now on the &lt;a href="https://console.nebius.com/project-e00j0yxxpr003gvmj7t4vd/applications/surrealdb" rel="noopener noreferrer"&gt;Nebius AI Cloud Marketplace&lt;/a&gt;. We can't wait to see what you build, join our &lt;a href="https://discord.com/invite/surrealdb" rel="noopener noreferrer"&gt;Discord community&lt;/a&gt; and let us know how you get on!&lt;/p&gt;

</description>
      <category>surrealdb</category>
      <category>database</category>
      <category>nebius</category>
      <category>ai</category>
    </item>
    <item>
      <title>SurrealDB 3.x by the numbers</title>
      <dc:creator>Mark Gyles</dc:creator>
      <pubDate>Fri, 29 May 2026 19:20:49 +0000</pubDate>
      <link>https://dev.to/surrealdb/surrealdb-3x-by-the-numbers-39ao</link>
      <guid>https://dev.to/surrealdb/surrealdb-3x-by-the-numbers-39ao</guid>
      <description>&lt;p&gt;Author: &lt;a href="https://x.com/tobiemh" rel="noopener noreferrer"&gt;Tobie Morgan Hitchcock&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  One engine, multi-workloads, full durability.
&lt;/h2&gt;

&lt;p&gt;You can explore the full results, methodology, and per-database breakdowns at &lt;a href="https://surrealdb.com/benchmarks" rel="noopener noreferrer"&gt;surrealdb.com/benchmarks&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we ran these
&lt;/h2&gt;

&lt;p&gt;Database benchmarks are notoriously easy to game, and difficult to get right. Different hardware, different durability settings, different client libraries, a workload that happens to suit one engine's indexing strategy - any of those will tilt the numbers. So we did three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Ran every database on the same hardware - an AMD Ryzen Threadripper 9970X (32C/64T), 128 GiB DDR5, NVMe storage, Ubuntu 24.04.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Used the same open-source harness - crud-bench - with each workload translated into each engine's native query language so no database is penalised for an unfamiliar dialect.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configured every engine for production-grade durability - fsync on, snapshot isolation, no in-memory shortcuts (except where explicitly noted for embedded comparisons).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We also went out of our way to give every database a fair shot. Rather than running each engine on its out-of-the-box defaults, we used&amp;nbsp;&lt;strong&gt;optimised configurations across the board&lt;/strong&gt;&amp;nbsp;- the same kind of tuning a production team would apply before going live. That meant raising connection and worker pool limits to match the 128-client load, sizing buffer pools, page caches, and shared memory to take advantage of the available 128 GiB of RAM, enabling parallel query execution and prepared-statement caching where supported, setting WAL and checkpoint intervals to values recommended by each project's own performance guides, and turning on the indexes and storage engines (InnoDB, WiredTiger, RocksDB-backed stores, etc.) that each database's documentation recommends for OLTP workloads. The goal was to make sure no engine was held back by a conservative default - if a database underperforms here, it isn't because we left it on its laptop-friendly starter config.&lt;/p&gt;

&lt;p&gt;Workloads run with 128 clients issuing 48 concurrent queries each, against datasets of a single table with 5 - 15 million rows of mixed-type records (strings, integers, floats, UUIDs, datetimes, booleans, large text fields, geospatial data, and nested objects and arrays).&lt;/p&gt;

&lt;h2&gt;
  
  
  About last time
&lt;/h2&gt;

&lt;p&gt;We owe a word on durability. The previous round of benchmark results ran with&amp;nbsp;&lt;code&gt;fsync&lt;/code&gt;&amp;nbsp;disabled for every engine - leaving each database's writes in the OS page cache rather than flushed to disk. Every database in the comparison ran with the same setting, so nothing was being "fudged" relative to the other engines, but we didn't make the setting explicit, and the headline numbers ended up describing a workload that most production deployments would not likely run.&lt;/p&gt;

&lt;p&gt;This round is different.&amp;nbsp;&lt;strong&gt;Every database in these benchmarks runs with full disk durability enabled&lt;/strong&gt;&amp;nbsp;-&amp;nbsp;&lt;code&gt;fsync&lt;/code&gt;&amp;nbsp;on, WAL flushed on every commit, no buffered writes hiding behind the page cache. The configuration files for each engine are checked into the &lt;a href="https://github.com/surrealdb/crud-bench" rel="noopener noreferrer"&gt;crud-bench repository&lt;/a&gt;&amp;nbsp;so anyone can audit them. The numbers above are what each engine sustains when every committed transaction is on disk before the client gets an acknowledgement. That's slower than the cache-friendly numbers you'll find in some marketing posts, ours included, but it's the only honest way to compare databases that are going to outlive a power outage.&lt;/p&gt;

&lt;h2&gt;
  
  
  &amp;nbsp;How far SurrealDB has come
&lt;/h2&gt;

&lt;p&gt;The biggest story is internal. Across three major releases, we've fundamentally rebuilt the query, parser, and storage layers:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo8rdhc9mhnypvtd651en.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo8rdhc9mhnypvtd651en.png" alt="how far SurrealDB has come" width="800" height="170"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Between SurrealDB 2.x and 3.x alone:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;31% faster mean CRUD throughput&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;58% faster batch operations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;11,894% faster non-indexed full-table scans&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;136% faster indexed queries&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tail latency improvements of 27% (CRUD), 32% (batches), 59% (indexed), and 99% (scans)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The scan number is not a typo. The SurrealDB 3.x query planner and storage engine eliminates the per-row decoding overhead that dominated earlier versions, which is why a workload that used to take minutes now completes in seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  How we compare to other databases
&lt;/h2&gt;

&lt;p&gt;SurrealDB is a durable, transactional, multi-model database, so the comparisons that matter most are against the primary databases people actually evaluate it against - Postgres for relational, MongoDB for document, Neo4j for graph. Here's how the same workload looks across those three categories, run on the same hardware with each engine on a tuned production-grade configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  vs. PostgreSQL (and MySQL)
&lt;/h3&gt;

&lt;p&gt;CRUD throughput (ops/s) and bulk-read metrics:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsrc8zkre8pfllgqwdrhp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsrc8zkre8pfllgqwdrhp.png" alt="vs. PostgreSQL and MySQL" width="800" height="285"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;SurrealDB is faster than Postgres on every write operation - roughly&amp;nbsp;&lt;strong&gt;1.5× faster creates, 1.3× faster updates, and 1.8× faster deletes&lt;/strong&gt;&amp;nbsp;- while Postgres still edges ahead on raw single-record reads. Against MySQL the gap widens dramatically: SurrealDB is&amp;nbsp;&lt;strong&gt;5 - 7× faster on writes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Averaged across creates, updates, and deletes,&amp;nbsp;&lt;strong&gt;SurrealDB delivers ~1.5× the write throughput of Postgres&lt;/strong&gt;&amp;nbsp;- the headline number the benchmarks page leads the Relational category with - and beats Postgres by ~1.5× on full-table counts. Postgres' query planner is 30 years old and still ahead on indexed predicate filtering; we're not pretending otherwise, and we're working on closing that gap in 3.1.&lt;/p&gt;

&lt;h3&gt;
  
  
  vs. MongoDB (and ArangoDB)
&lt;/h3&gt;

&lt;p&gt;CRUD throughput (ops/s) and bulk-read metrics:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fammier1thf3i9smmihcx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fammier1thf3i9smmihcx.png" alt="vs. MongoDB and ArangoDB" width="800" height="235"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the closest race. MongoDB still leads on single-record writes, while SurrealDB is&amp;nbsp;&lt;strong&gt;~1.3× faster on reads&lt;/strong&gt;&amp;nbsp;- and the picture flips on heavier workloads. On predicate filter scans against unindexed tables - the headline figure the benchmarks page uses for the Document category -&amp;nbsp;&lt;strong&gt;SurrealDB is roughly 2.7× faster than MongoDB&lt;/strong&gt;, with consistently lower mean and p99 latency. Against ArangoDB's document engine, SurrealDB is between&amp;nbsp;&lt;strong&gt;100× and 150× faster on writes&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  vs. Neo4j (and ArangoDB)
&lt;/h3&gt;

&lt;p&gt;CRUD throughput (ops/s) and bulk-read metrics:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4s2ttjs7y6alh38f9nev.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4s2ttjs7y6alh38f9nev.png" alt="vs. Neo4j and ArangoDB" width="800" height="260"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;SurrealDB outperforms Neo4j across every CRUD operation -&amp;nbsp;&lt;strong&gt;roughly 2 - 3.5× faster&amp;nbsp;on writes&lt;/strong&gt; and&amp;nbsp;&lt;strong&gt;1.5× faster on reads&lt;/strong&gt;&amp;nbsp;- while running the same graph traversals through the same engine that handles documents and tables.&lt;/p&gt;

&lt;p&gt;The gap on filtered scans is even more dramatic. Across indexed predicate filter queries - the headline metric the benchmarks page uses for the Graph category -&amp;nbsp;&lt;strong&gt;SurrealDB is roughly 35× faster than Neo4j&lt;/strong&gt;. No separate database, no separate query language, no separate operational story.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reference point: Redis and KeyDB
&lt;/h3&gt;

&lt;p&gt;For raw key-value throughput, we also ran SurrealDB's in-memory engine (with append-only persistence) against Redis and KeyDB:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvy0ei2goby1x5h5mza5f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvy0ei2goby1x5h5mza5f.png" alt="Redis and KeyDB" width="798" height="197"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's roughly&amp;nbsp;&lt;strong&gt;3× faster than Redis on writes, updates, and deletes&lt;/strong&gt;, while offering durable, snapshot-isolated transactions and a full query language Redis doesn't have. Redis still wins on large 1,000-record batch operations and edges ahead on single-record reads.&lt;/p&gt;

&lt;h3&gt;
  
  
  Embedded mode (vs. SQLite)
&lt;/h3&gt;

&lt;p&gt;SurrealDB's embedded engine runs the same SurrealQL on the same disk format as the server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F87t5kfwba1xj02u5v5cc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F87t5kfwba1xj02u5v5cc.png" alt="vs. SQLite" width="799" height="273"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Against SQLite, SurrealDB is roughly&amp;nbsp;&lt;strong&gt;85× faster on creates, 110× faster on updates, and 75× faster on deletes&lt;/strong&gt;, with single-record reads in the same ballpark. On predicate filter scans against unindexed tables it's around&amp;nbsp;&lt;strong&gt;6.5× faster&lt;/strong&gt;&amp;nbsp;than SQLite, and on indexed filter scans, around&amp;nbsp;3× faster.&lt;/p&gt;

&lt;p&gt;The full breakdowns - including p50, p95, and p99 latencies, batch sizes from 100 to 1,000 rows, indexed and non-indexed predicate filters, and full-text search - are on the &lt;a href="https://surrealdb.com/benchmarks" rel="noopener noreferrer"&gt;benchmarks page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing the remaining gaps
&lt;/h2&gt;

&lt;p&gt;The numbers above are an honest snapshot, not a finish line. There are still workloads where Redis, MongoDB, and Postgres beat us - large batch operations vs. Redis, single-record writes vs. Mongo, indexed predicate filtering vs. Postgres - and we know exactly where each gap comes from. Closing those gaps is the central focus of the SurrealDB 3.1 cycle.&lt;/p&gt;

&lt;p&gt;What we're actively working on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Batch path rewrites to bring 100-row and 1,000-row batched ops closer to Redis throughput, including better client-side pipelining and a leaner server-side batch executor.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A smarter query planner with cost-based optimisation, predicate pushdown into the storage engine, and richer index selectivity statistics - the work that gets us to parity with Postgres on indexed filter scans.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Storage layer improvements for the document workload - more compact in-place updates, sharper write amplification, and tighter integration between the key encoding and the document path resolver - which is where Mongo currently has the edge on single-record writes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Vector and graph traversal optimisations as those workloads land in the benchmark suite, so the multi-model story holds up at the same rigour as CRUD.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal isn't "fastest at one thing." It's to be&amp;nbsp;&lt;strong&gt;fastest, or competitive, across every workload that matters&lt;/strong&gt;, while keeping the one property no specialist engine can match: a single query language - SurrealQL - that handles relational, document, graph, key-value, time-series, vector, and full-text search data in the same database, with the same transactional guarantees, on the same disk format - and the same engine, whether you're running it embedded inside an application, on a single server, at the edge close to your users, or distributed across hundreds of nodes for horizontal scale. We don't think you should have to choose between Postgres, Mongo, Neo4j, and Redis - or between a database that runs on a developer laptop and one that runs across a global fleet. We think a single database should run all four shapes of workload at production speed, anywhere it needs to live, and the SurrealDB 3.x numbers above are the strongest evidence yet that it can.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why this matters for AI agents
&lt;/h3&gt;

&lt;p&gt;There's a reason we keep pushing on this combination of data models, and it's not historical accident.&amp;nbsp;&lt;strong&gt;Agent memory is multi-model by nature&lt;/strong&gt;.&amp;nbsp;A useful AI agent needs structured facts about the world (relational), semi-structured context and tool outputs (document), entity and event relationships (graph), embeddings for semantic recall (vector), keyword and BM25 retrieval over its corpus (full-text search), episodic and temporal context (time-series), and fast session and cache state (key-value) - and it needs all of that in a single transactionally consistent store, because the moment those shapes live in separate databases, you've built a glue-code problem that breaks every time the schema changes or the model is updated.&lt;/p&gt;

&lt;p&gt;Agents also need that memory&amp;nbsp;&lt;strong&gt;close to where they run&lt;/strong&gt;. An agent reasoning inside a browser, a phone, an in-vehicle system, or a per-tenant edge worker can't afford a round trip to a central database for every retrieval. The fact that SurrealDB runs as an embedded engine on the same disk format as the distributed server - with the same query language and the same transactional guarantees - is what makes it usable as the memory layer for agents that move fluidly between local, edge, and centralised deployments. Faster CRUD, faster scans, and faster indexed lookups aren't just abstract benchmark wins; they're how many tools an agent can call, how much context it can recall per turn, and how many concurrent agents a single host can support. That's the workload SurrealDB 3.x is built for, and it's the workload the next round of benchmarks - covering vector search, graph traversal, and full-text retrieval - will measure head-on.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;We intend to expand the benchmarks to also cover&amp;nbsp;&lt;strong&gt;CockroachDB, TiDB, MongoDB, and Aerospike&lt;/strong&gt;&amp;nbsp;for distributed comparisons, which we'll publish in a future round. We also plan to expand the workload set to include graph traversals vector search, and full-text search, two areas where the single-engine, multi-model design of SurrealDB shows its biggest advantages.&lt;/p&gt;

&lt;p&gt;Until then: the harness is open source, the results are reproducible, and we'd love for you to run them on your own hardware and tell us what you find.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://surrealdb.com/benchmarks" rel="noopener noreferrer"&gt;See the full benchmarks&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>surrealdb</category>
      <category>database</category>
      <category>benchmarks</category>
      <category>news</category>
    </item>
    <item>
      <title>SurrealDB 3.1: stability, DiskANN, and a new release process</title>
      <dc:creator>Mark Gyles</dc:creator>
      <pubDate>Thu, 28 May 2026 08:43:39 +0000</pubDate>
      <link>https://dev.to/surrealdb/surrealdb-31-stability-diskann-and-a-new-release-process-20cj</link>
      <guid>https://dev.to/surrealdb/surrealdb-31-stability-diskann-and-a-new-release-process-20cj</guid>
      <description>&lt;p&gt;Author: &lt;a href="https://github.com/tobiemh" rel="noopener noreferrer"&gt;Tobie Morgan Hitchcock&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Three months after 3.0 went GA, we're excited to announce that SurrealDB 3.1 is now available. This is the first minor release in the 3.x series. It builds on the foundations we shipped in 3.0 with a focus on stability, a second approximate-nearest-neighbour index in DiskANN, and a substantial round of security hardening. Alongside the release, we're also rolling out a change to how we develop and ship SurrealDB.&lt;/p&gt;

&lt;p&gt;The full list of changes is in the &lt;a href="https://surrealdb.com/releases/3.1.0" rel="noopener noreferrer"&gt;3.1 release notes&lt;/a&gt;. Below are the highlights.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's new in 3.1
&lt;/h2&gt;

&lt;h3&gt;
  
  
  DiskANN approximate-nearest neighbour index
&lt;/h3&gt;

&lt;p&gt;SurrealDB now ships DiskANN as a second ANN index type, sitting alongside HNSW. DiskANN trades a different set of memory and recall characteristics and is well suited to larger-than-memory vector workloads, which has been a recurring request from teams building production agent and search systems on SurrealDB.&lt;/p&gt;

&lt;p&gt;The introduction of DiskANN also drove an end-to-end overhaul of the ANN warm-lookup path. Both HNSW and DiskANN benefit from this work, with measurable improvements to warm-cache latency on the existing HNSW index for free.&lt;/p&gt;

&lt;p&gt;You can pick the index type at definition time with the new &lt;code&gt;DEFINE INDEX ... DISKANN&lt;/code&gt; syntax. See the release notes for the full set of options.&lt;/p&gt;

&lt;h3&gt;
  
  
  GraphQL: aliases, cursor pagination, and multi-model filtering
&lt;/h3&gt;

&lt;p&gt;SurrealDB v3.1 brings a significant upgrade to the GraphQL surface, making it more expressive, more predictable, and easier to integrate with modern GraphQL clients.&lt;/p&gt;

&lt;p&gt;The generated schema now follows Apollo conventions by default, with singular fetch, plural list queries, and &lt;code&gt;createX&lt;/code&gt; / &lt;code&gt;updateX&lt;/code&gt; / &lt;code&gt;deleteX&lt;/code&gt; mutations, so you always know what to expect without consulting per-database config. Fields and tables can now carry &lt;code&gt;GRAPHQL_ALIAS&lt;/code&gt; and &lt;code&gt;GRAPHQL_DEPRECATED&lt;/code&gt; clauses directly in their definitions, letting you decouple your SurrealQL identifiers from the names your API consumers see:&lt;/p&gt;

&lt;p&gt;Schema&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;DEFINE&lt;/span&gt; &lt;span class="n"&gt;FIELD&lt;/span&gt; &lt;span class="n"&gt;first_name&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="k"&gt;TYPE&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;
  &lt;span class="n"&gt;GRAPHQL_ALIAS&lt;/span&gt; &lt;span class="nv"&gt;"firstName"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;DEFINE&lt;/span&gt; &lt;span class="n"&gt;FIELD&lt;/span&gt; &lt;span class="n"&gt;legacy_score&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt; &lt;span class="k"&gt;TYPE&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
  &lt;span class="n"&gt;GRAPHQL_DEPRECATED&lt;/span&gt; &lt;span class="nv"&gt;"Use `rank` instead"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cursor pagination arrives in this release too. Each table now gets a &lt;code&gt;&amp;lt;plural&amp;gt;Connection&lt;/code&gt; query returning &lt;code&gt;edges&lt;/code&gt;, &lt;code&gt;pageInfo&lt;/code&gt;, and a lazily-evaluated &lt;code&gt;totalCount&lt;/code&gt;, so you only pay for the count when you ask for it. Offset pagination via &lt;code&gt;limit&lt;/code&gt; and &lt;code&gt;start&lt;/code&gt; remains fully supported alongside it.&lt;/p&gt;

&lt;p&gt;On the multi-model side, GraphQL queries can now reach across SurrealDB's full data model. Full-text search, vector similarity, and time-series aggregation are all directly queryable through the GraphQL API, unlocking AI-native and analytics-heavy workloads without touching SurrealQL.&lt;/p&gt;

&lt;p&gt;This release also closes a range of reported issues: stale schema caches after DDL changes, invalid filter identifiers on nested array fields, missing &lt;code&gt;id&lt;/code&gt; range and &lt;code&gt;in&lt;/code&gt; filters, and &lt;code&gt;count()&lt;/code&gt;-based predicates in &lt;code&gt;WHERE&lt;/code&gt; clauses, all resolved and covered by integration tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Improved in-memory performance
&lt;/h3&gt;

&lt;p&gt;SurrealDB's in-memory backend now uses a datastore based on optimistic lock coupling. This improves performance by allowing readers to acquire lock-free access, only retrying at the end if a modification has occurred in the meantime. This allows readers to proceed without blocking writers, and vice versa.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stability and correctness
&lt;/h3&gt;

&lt;p&gt;The bulk of the 3.1 work is bug fixes and stabilisation of the 3.0 line. This continues the stream of fixes that shipped in v3.0.1 through v3.0.5, with a substantial number of additional issues addressed across the query engine, indexing, and storage layers. If you upgraded from 2.x to 3.0 and hit any rough edges, there's a strong chance the relevant fix is in 3.1.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security hardening
&lt;/h3&gt;

&lt;p&gt;3.1 closes a batch of previously reported security vulnerabilities, alongside a set of previously undiscovered issues that we found internally. The undiscovered ones come from an increased investment in LLM-assisted security review, in line with broader industry practice. Mozilla wrote a good summary of this approach recently. The combined result is a notably larger security section in this release than in previous ones.&lt;/p&gt;

&lt;p&gt;The full security section is itemised in the release notes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enterprise: audit logging and slow-query pipeline
&lt;/h3&gt;

&lt;p&gt;Customers on the Enterprise tier get two new operational tools in 3.1:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A structured audit log capturing authentication events, schema changes, and other actions of interest to compliance and security teams.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A slow-query pipeline that surfaces queries exceeding a configurable threshold, complete with their plan and execution metadata, to help diagnose performance issues in production.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both have been on Enterprise customer wishlists for a while, and we're pleased to have them shipping in 3.1.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other quality of life additions
&lt;/h3&gt;

&lt;p&gt;Among the many changes mentioned in the 3.1 release notes, some of the changes that you won't want to miss are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A built-in MCP server for AI tools and IDEs,Unified OpenTelemetry metrics and logging pipeline,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Unified OpenTelemetry metrics and logging pipeline,W3C trace propagation across HTTP and WebSocket,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;W3C trace propagation across HTTP and WebSocket,Full ALTER coverage for every DEFINE statement,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Full ALTER coverage for every DEFINE statement,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;And continued executor and index improvements beyond the in-memory engine.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  A new release process
&lt;/h3&gt;

&lt;p&gt;3.1 is also the first release we've shipped under a new development workflow, and we want to be transparent about what's changed and why.&lt;/p&gt;

&lt;p&gt;For most of SurrealDB's history, we've developed in public on the main &lt;code&gt;surrealdb/surrealdb&lt;/code&gt; repository. When a security vulnerability needed addressing, we'd cut a temporary private fork, fix the issue there, cut a release, patch the SurrealDB Cloud fleet, and then make the release public. The intent was good. Development happened where the community could see it, and security fixes were embargoed only as long as they needed to be. In practice, though, this added real overhead to every security response. Cutting a fresh private fork for each issue, keeping it in sync with main, and merging the fix back when going public all took time we'd rather have spent on the fix itself.&lt;/p&gt;

&lt;p&gt;Starting with 3.1, we've moved all development to a private repository. The public &lt;code&gt;surrealdb/surrealdb&lt;/code&gt; repo remains the source of truth for releases, issues, and the source code that users read. Day-to-day commits now happen privately.&lt;/p&gt;

&lt;p&gt;The cadence looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;We develop and tag releases in the private repository.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When a release is ready, we announce it publicly and ship binaries and Docker images on the usual schedule.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Roughly 1 week after each release, we sync the private repository back to the public &lt;code&gt;surrealdb/surrealdb&lt;/code&gt; repo.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The week between release and public sync gives us a private window to triage any issues or vulnerabilities reported against the new version before the corresponding commits become visible. We can ship a fix and roll it out across SurrealDB Cloud before the underlying issue is public. This is the same model used by most database and infrastructure projects at comparable scale, and it lets us close security issues as quickly as they're reported.&lt;/p&gt;

&lt;p&gt;A few practical notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Issues, PRs, and discussions stay on the public repo&lt;/strong&gt;. Keep filing them on &lt;code&gt;surrealdb/surrealdb&lt;/code&gt;. We watch and respond to them daily.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Security reports&lt;/strong&gt;. Use the GitHub Security Advisory flow on the public repo, or email &lt;code&gt;security@surrealdb.com&lt;/code&gt;. Both reach us privately, and are the right route for anything you don't want disclosed publicly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Community PRs&lt;/strong&gt;. We're still very keen for community contributions. They get reviewed against the public repo as before, and brought into the private tree as part of the normal cycle.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We don't expect this to be visible to most users in day-to-day use. If you follow commit-by-commit development on the public repo, the experience will change: you'll see batched updates around each release rather than a continuous stream. We think the security benefit is worth that tradeoff, and we plan to keep working this way for the foreseeable future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;SurrealDB 3.1 is available now. Head to the &lt;a href="https://surrealdb.com/install" rel="noopener noreferrer"&gt;install page&lt;/a&gt; to grab it for your platform.&lt;/p&gt;

&lt;p&gt;If you're running on SurrealDB Cloud, instances are being rolled to 3.1 as part of our standard release cycle. No action needed.&lt;/p&gt;

&lt;p&gt;Read the &lt;a href="https://surrealdb.com/releases/3.1.0" rel="noopener noreferrer"&gt;full release notes&lt;/a&gt; for the complete list of changes, and come say hello on &lt;a href="https://discord.gg/surrealdb" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; if you hit anything along the way.&lt;/p&gt;

</description>
      <category>surrealdb</category>
      <category>database</category>
      <category>news</category>
      <category>graphql</category>
    </item>
  </channel>
</rss>
