<?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: Mark Gyles</title>
    <description>The latest articles on DEV Community by Mark Gyles (@mgyles).</description>
    <link>https://dev.to/mgyles</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%2Fuser%2Fprofile_image%2F1602559%2Ff82af600-3936-4f4a-a067-dfee7a3228d4.jpg</url>
      <title>DEV Community: Mark Gyles</title>
      <link>https://dev.to/mgyles</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mgyles"/>
    <language>en</language>
    <item>
      <title>SurrealDB Cloud is now available in Frankfurt, Germany</title>
      <dc:creator>Mark Gyles</dc:creator>
      <pubDate>Mon, 07 Sep 2026 10:11:28 +0000</pubDate>
      <link>https://dev.to/surrealdb/surrealdb-cloud-is-now-available-in-frankfurt-germany-33lm</link>
      <guid>https://dev.to/surrealdb/surrealdb-cloud-is-now-available-in-frankfurt-germany-33lm</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;SurrealDB Cloud can now be deployed in Frankfurt, Germany. The new region is live today - pick it from the region list when you create an instance.&lt;/p&gt;

&lt;p&gt;For teams across the DACH region and continental Europe, this is a second European option and, for most of them, the closer one. Until now the nearest place to run a managed SurrealDB instance was Ireland, on the far western edge of the continent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Latency and data residency for your workloads
&lt;/h2&gt;

&lt;p&gt;Running in Frankfurt puts the database closer to your users across continental Europe, next to the interconnect that most European traffic already passes through. It also keeps German and EU customer data stored and processed in Germany, satisfying data-residency requirements that previously ruled out a managed deployment. All this while maintaining the same SOC 2 Type 2 controls, ISO 27001, GDPR, encryption and network isolation options as every other region. Agents gain twice over: the region removes the distance, and SurrealDB removes the fan-out. Vector search, graph traversal, full-text search and document reads all resolve in one query against one store rather than separate calls to a vector database, a search engine and a primary database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where SurrealDB Cloud runs
&lt;/h2&gt;

&lt;p&gt;Frankfurt joins six other regions:&lt;/p&gt;

&lt;p&gt;• US East (N. Virginia)&lt;br&gt;
• US East (Ohio)&lt;br&gt;
• US West (Oregon)&lt;br&gt;
• Europe (Ireland)&lt;br&gt;
• Europe (Frankfurt)&lt;br&gt;
• Asia Pacific (Mumbai)&lt;br&gt;
• South America (São Paulo)&lt;/p&gt;

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

&lt;p&gt;Create an instance in &lt;a href="https://studio.surrealdb.com/current/instances/deploy?_gl=1*13wbmse*FPAU*OTA4NzQ0MTAxLjE3ODgzNTQzMDM.*_ga*MTU2ODc2ODY1OS4xNzg4MzU0MzAz*_ga_J1NWM32T1V*czE3ODg3NzIyNTgkbzE1JGcxJHQxNzg4Nzc1NjA3JGo1MyRsMCRoNTYwNTMzMjcz*_fplc*MUglMkJld1JLJTJCWnlxSU5RZFJBYlNKaFdCRmJCSUNFVjNaSk5samQ4aEpUa3RGWVFTdHFVZmhEamNJYmdPMzVQMmNQZllHZDZvZmtpaiUyQnhZSU5MZ1NkZTFyUlg1ZXNDZVVrTXkwbHBBVFJYa0h1QWtlVkh6aHNWbXNKbFgwS3R3JTNEJTNE" rel="noopener noreferrer"&gt;SurrealDB Cloud&lt;/a&gt; and select Europe (Germany) as the region. Everything else - tiers, instance types, versions, backups - works exactly as it does elsewhere, so existing automation needs nothing more than the new region value.&lt;/p&gt;

&lt;p&gt;If you are running SurrealDB in production and want high availability alongside in-region deployment, the &lt;a href="https://surrealdb.com/blog/introducing-scale-surrealdb-cloud-built-for-high-availability-and-scale" rel="noopener noreferrer"&gt;Scale tier&lt;/a&gt; gives you multi-node, multi-availability-zone clusters.&lt;/p&gt;

</description>
      <category>news</category>
      <category>surrealdbcloud</category>
      <category>database</category>
      <category>cloud</category>
    </item>
    <item>
      <title>SurrealDB Cloud is now available in São Paulo, Brazil</title>
      <dc:creator>Mark Gyles</dc:creator>
      <pubDate>Mon, 07 Sep 2026 09:51:34 +0000</pubDate>
      <link>https://dev.to/surrealdb/surrealdb-cloud-is-now-available-in-sao-paulo-brazil-1o89</link>
      <guid>https://dev.to/surrealdb/surrealdb-cloud-is-now-available-in-sao-paulo-brazil-1o89</guid>
      <description>&lt;p&gt;Author: &lt;a href="https://x.com/IgnacioPaz87" rel="noopener noreferrer"&gt;Ignacio Paz&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;SurrealDB Cloud can now be deployed in São Paulo, Brazil. The new region is live today - pick it from the region list when you create an instance.&lt;/p&gt;

&lt;p&gt;For teams building in Brazil and across Latin America, this closes the last big gap in the deployment story: until now the nearest place to run a managed SurrealDB instance was North America, and every query paid for the trip.&lt;/p&gt;

&lt;h2&gt;
  
  
  Latency and data residency for your workloads
&lt;/h2&gt;

&lt;p&gt;Running in São Paulo puts the database closer to your customers for our South American users. It also keeps Brazilian customer data stored and processed in Brazil, satisfying data-residency requirements that previously ruled out a managed deployment. All this while maintaining the same SOC 2 Type 2 controls, ISO 27001, GDPR, encryption and network isolation options as every other region. Agents gain twice over: the region removes the distance, and SurrealDB removes the fan-out. Vector search, graph traversal, full-text search and document reads all resolve in one query against one store rather than separate calls to a vector database, a search engine and a primary database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where SurrealDB Cloud runs
&lt;/h2&gt;

&lt;p&gt;São Paulo joins five other regions:&lt;/p&gt;

&lt;p&gt;• US East (N. Virginia)&lt;br&gt;
• US East (Ohio)&lt;br&gt;
• US West (Oregon)&lt;br&gt;
• Europe (Ireland)&lt;br&gt;
• Asia Pacific (Mumbai)&lt;br&gt;
• South America (São Paulo)&lt;/p&gt;

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

&lt;p&gt;Create an instance in &lt;a href="https://studio.surrealdb.com/current/instances/deploy?_gl=1*cfkuhl*FPAU*OTA4NzQ0MTAxLjE3ODgzNTQzMDM.*_ga*MTU2ODc2ODY1OS4xNzg4MzU0MzAz*_ga_J1NWM32T1V*czE3ODg3NzIyNTgkbzE1JGcxJHQxNzg4Nzc0Mjg2JGo2MCRsMCRoNTYwNTMzMjcz*_fplc*MUglMkJld1JLJTJCWnlxSU5RZFJBYlNKaFdCRmJCSUNFVjNaSk5samQ4aEpUa3RGWVFTdHFVZmhEamNJYmdPMzVQMmNQZllHZDZvZmtpaiUyQnhZSU5MZ1NkZTFyUlg1ZXNDZVVrTXkwbHBBVFJYa0h1QWtlVkh6aHNWbXNKbFgwS3R3JTNEJTNE" rel="noopener noreferrer"&gt;SurrealDB Cloud&lt;/a&gt; and select &lt;strong&gt;South America (Brazil)&lt;/strong&gt; as the region. Everything else - tiers, instance types, versions, backups - works exactly as it does elsewhere, so existing automation needs nothing more than the new region value.&lt;/p&gt;

&lt;p&gt;If you are running SurrealDB in production and want high availability alongside in-region deployment, the &lt;a href="https://surrealdb.com/blog/introducing-scale-surrealdb-cloud-built-for-high-availability-and-scale" rel="noopener noreferrer"&gt;Scale tier&lt;/a&gt; gives you multi-node, multi-availability-zone clusters. The &lt;a href="https://surrealdb.com/docs/manage/cloud" rel="noopener noreferrer"&gt;Cloud documentation&lt;/a&gt; covers the full set of options.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://studio.surrealdb.com/current/instances/deploy?_gl=1*13c3trd*FPAU*OTA4NzQ0MTAxLjE3ODgzNTQzMDM.*_ga*MTU2ODc2ODY1OS4xNzg4MzU0MzAz*_ga_J1NWM32T1V*czE3ODg3NzIyNTgkbzE1JGcxJHQxNzg4Nzc0Mjg2JGo2MCRsMCRoNTYwNTMzMjcz*_fplc*MUglMkJld1JLJTJCWnlxSU5RZFJBYlNKaFdCRmJCSUNFVjNaSk5samQ4aEpUa3RGWVFTdHFVZmhEamNJYmdPMzVQMmNQZllHZDZvZmtpaiUyQnhZSU5MZ1NkZTFyUlg1ZXNDZVVrTXkwbHBBVFJYa0h1QWtlVkh6aHNWbXNKbFgwS3R3JTNEJTNE" rel="noopener noreferrer"&gt;Deploy in São Paulo&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>surrealdbcloud</category>
      <category>database</category>
      <category>cloud</category>
      <category>news</category>
    </item>
    <item>
      <title>How Cobrainer built graph-based agent memory on one engine</title>
      <dc:creator>Mark Gyles</dc:creator>
      <pubDate>Mon, 07 Sep 2026 09:32:35 +0000</pubDate>
      <link>https://dev.to/surrealdb/how-cobrainer-built-graph-based-agent-memory-on-one-engine-2a6k</link>
      <guid>https://dev.to/surrealdb/how-cobrainer-built-graph-based-agent-memory-on-one-engine-2a6k</guid>
      <description>&lt;p&gt;Author: &lt;a href="https://x.com/IgnacioPaz87" rel="noopener noreferrer"&gt;Ignacio Paz&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An AI agent is only as useful as what it can remember - and how well it can connect the things it remembers. Most teams hand their agent a memory by reaching for a vector store: embed everything, retrieve by similarity, hope the relevant context comes back. It works, until you notice the agent keeps surfacing things that are near the question but not actually connected to it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.cobrainer.com/" rel="noopener noreferrer"&gt;Cobrainer&lt;/a&gt;, a skills-intelligence company based in Munich, took a different route. They gave their AI agent a memory that lives in the database as a graph, where the agent builds the relationships between nodes as it goes. They did it without adding a graph database, a vector engine, or a search engine to their stack. It all runs on SurrealDB, alongside a Rust-native agentic graph RAG built on the same store.&lt;/p&gt;

&lt;p&gt;Here's how, and why a single engine made the difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with flat memory
&lt;/h2&gt;

&lt;p&gt;Cobrainer runs a skills-intelligence platform - the kind of system that reasons about how people, roles, skills, and capabilities relate to one another. That's an inherently graph-shaped problem. But their first retrieval setup wasn't graph-shaped at all.&lt;/p&gt;

&lt;p&gt;It pulled context through flat vector retrieval over an S3-and-OpenSearch pipeline, which carried two recurring costs:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accuracy&lt;/strong&gt;. Flat vector matches returned context that was loosely related - semantically near, but not necessarily connected in any meaningful way. The team wanted the agent to follow real relationships between entities, so its answers were grounded rather than approximate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tokens&lt;/strong&gt;. Broad vector matches meant stuffing a lot of marginally relevant context into every prompt - expensive, and more so with every call. The team wanted to fetch only the context that mattered.&lt;/p&gt;

&lt;p&gt;The obvious fix - adding a graph database on top of the vector and search systems they already ran - would have meant more infrastructure to operate. For a startup moving fast, that fragmentation was the thing to avoid, not embrace.&lt;/p&gt;

&lt;h2&gt;
  
  
  What they wanted instead
&lt;/h2&gt;

&lt;p&gt;Cobrainer's requirements were less about any single feature and more about not paying a tax for combining them.&lt;/p&gt;

&lt;p&gt;• &lt;strong&gt;An agent and a graph RAG on one store&lt;/strong&gt;: an AI agent whose memory lives in the database as a graph, plus an agentic graph RAG. Both are backed by a single engine for graph, vector, and full-text.&lt;/p&gt;

&lt;p&gt;• &lt;strong&gt;A self-building memory graph&lt;/strong&gt;: rather than hand-modelling every relationship up front, the agent should make use of the relations between nodes that it builds automatically as it works.&lt;/p&gt;

&lt;p&gt;• &lt;strong&gt;Grounding through structure&lt;/strong&gt;: retrieval that traverses real relationships, so answers are anchored in how things actually connect.&lt;/p&gt;

&lt;p&gt;• &lt;strong&gt;Token efficiency by design&lt;/strong&gt;: pull the relevant subgraph, not a broad similarity sweep.&lt;/p&gt;

&lt;p&gt;• &lt;strong&gt;Pace&lt;/strong&gt;: stand up new storage patterns quickly, without long migrations slowing the team down.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why did Cobrainer choose SurrealDB?
&lt;/h2&gt;

&lt;p&gt;SurrealDB gave Cobrainer graph, vector, and full-text in a single engine, all queried through SurrealQL - so the agentic use case didn't need pgvector or OpenSearch sitting alongside their database. (Their existing RDS Postgres stayed exactly where it was, doing the job it already did well.)&lt;/p&gt;

&lt;p&gt;A few things made the build land the way they wanted:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One engine, queried one way&lt;/strong&gt;. Documents, graphs, vectors, and full-text all live in one store - schemaless and schemafull side by side - and all reachable through SurrealQL. The agentic case added no new systems to operate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Graph-based agent memory that builds itself&lt;/strong&gt;. SurrealDB stores the agent's memory and its session checkpoints. As the agent works, it automatically creates the relations between the nodes it writes - so the memory graph forms on its own, and the agent can later traverse those connections instead of re-deriving them. The team described a multi-layered, graph-based memory model, and the thing that surprised them was how fast it came together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Rust-native agentic graph RAG&lt;/strong&gt;. Cobrainer built the graph RAG natively in Rust on SurrealDB's Rust SDK, combining graph traversal and vector similarity in a single query. That's the piece that replaced the old S3-and-OpenSearch RAG - letting the agent walk relationships and rank by similarity in one round trip instead of orchestrating across services.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Managed, and inside the EU boundary&lt;/strong&gt;. Because this is HR data, residency isn't optional. SurrealDB Cloud let a lean team run the engine as a managed service with EU-region data residency, keeping everything inside the boundary they're required to maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  The results
&lt;/h2&gt;

&lt;p&gt;Cobrainer went from evaluation to a customer-facing production deployment in roughly &lt;strong&gt;three months&lt;/strong&gt;. As of August 2026, that deployment is live. Measured against their previous S3-and-OpenSearch pipeline, the team reports three changes.&lt;/p&gt;

&lt;p&gt;• &lt;strong&gt;Better accuracy&lt;/strong&gt;: grounding the agent in real graph relationships, rather than loose vector matches, made its responses more reliable.&lt;/p&gt;

&lt;p&gt;• &lt;strong&gt;Lower token cost per call&lt;/strong&gt;: fetching only graph-relevant context - instead of a broad vector sweep - trimmed the tokens going into each prompt.&lt;/p&gt;

&lt;p&gt;• &lt;strong&gt;A flexible platform for what's next&lt;/strong&gt;: graph-based agent memory and a Rust-native agentic graph RAG, both on one engine, ready to absorb new use cases without another round of stack expansion.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"As a startup, we didn't want to fragment our stack every time we add a capability. For our agentic graph RAG, the alternative was weighing Postgres with pgvector and search extensions against bolting on OpenSearch - instead we got graph, vector, and full-text from one engine. What surprised us was how quickly we stood up a multi-layered, graph-based memory model for our AI agent, with SurrealDB handling its memory and session checkpoints. And SurrealDB Cloud kept all of it inside our EU data boundary."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Daniel Alker, Senior Cloud Platform Engineer, Cobrainer&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;Giving an agent a memory doesn't have to mean giving it another database. Cobrainer's story is a small proof of a larger idea. When one engine handles graph, vector, and full-text together, and the agent can build its own memory graph on top of it, the "right" architecture stops being a collection of specialised services. It starts being a single query.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://surrealdb.com/cloud" rel="noopener noreferrer"&gt;Start with SurrealDB Cloud&lt;/a&gt; or explore &lt;a href="https://surrealdb.com/docs" rel="noopener noreferrer"&gt;the docs&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>agentmemory</category>
      <category>surrealdb</category>
      <category>database</category>
    </item>
    <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 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>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>
    <item>
      <title>Kreuzberg &amp; SurrealDB: from unstructured documents to hybrid retrieval</title>
      <dc:creator>Mark Gyles</dc:creator>
      <pubDate>Tue, 12 May 2026 12:35:55 +0000</pubDate>
      <link>https://dev.to/surrealdb/kreuzberg-surrealdb-from-unstructured-documents-to-hybrid-retrieval-3657</link>
      <guid>https://dev.to/surrealdb/kreuzberg-surrealdb-from-unstructured-documents-to-hybrid-retrieval-3657</guid>
      <description>&lt;p&gt;Author: &lt;a href="https://x.com/IgnacioPaz87" rel="noopener noreferrer"&gt;Ignacio Paz&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We’re excited to share a new partner integration: &lt;code&gt;kreuzberg-surrealdb&lt;/code&gt;, a connector that bridges the Kreuzberg document intelligence framework directly into SurrealDB. This integration was created by the Kreuzberg team and we are excited to have this functionality available now in SurrealDB.&lt;/p&gt;

&lt;p&gt;Kreuzberg extracts, chunks, and generates embeddings from 88+ document formats, while SurrealDB provides a multi-model database for AI applications, combining documents, graphs, vectors, and full-text search in a single system.&lt;/p&gt;

&lt;p&gt;Together, they make it easy to build document search and RAG pipelines.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the integration does
&lt;/h2&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%2F0mi2ox6g7h6vmbu2d7x3.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%2F0mi2ox6g7h6vmbu2d7x3.png" alt="document extraction" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kreuzberg-surrealdb&lt;/code&gt; handles the full ingestion workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automatic schema setup&lt;/li&gt;
&lt;li&gt;Content deduplication using SHA-256 hashing&lt;/li&gt;
&lt;li&gt;Storage and indexing in SurrealDB&lt;/li&gt;
&lt;li&gt;Documents ready for search immediately after ingest&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The integration supports two modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DocumentConnector:indexes full documents for BM25 keyword search.&lt;/li&gt;
&lt;li&gt;DocumentPipeline:chunks documents, generates embeddings, and enables semantic and hybrid search using HNSW vector indexes and Reciprocal Rank Fusion.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why it matters
&lt;/h2&gt;

&lt;p&gt;Building document search systems often requires combining multiple tools for extraction, chunking, embeddings, and storage.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;kreuzberg-surrealdb&lt;/code&gt;, the entire workflow runs through a &lt;strong&gt;single integration&lt;/strong&gt;—no schema boilerplate, no duplicate ingestion, and built-in support for &lt;strong&gt;keyword&lt;/strong&gt;, &lt;strong&gt;semantic&lt;/strong&gt;, and &lt;strong&gt;hybrid search&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;See how to get started in &lt;a href="https://surrealdb.com/docs/build/integrations/ai-frameworks/kreuzberg?utm_source=kreuzberg_blog&amp;amp;utm_medium=blog&amp;amp;utm_campaign=kreuzberg_blog" rel="noopener noreferrer"&gt;SurrealDB Docs: Kreuzberg Integration&lt;/a&gt;, and check out our example of &lt;a href="https://surrealdb.com/blog/how-to-build-a-knowledge-graph-for-ai?utm_source=kreuzberg_blog&amp;amp;utm_medium=blog&amp;amp;utm_campaign=kreuzberg_blog#parsing-unstructured-data" rel="noopener noreferrer"&gt;How to build a knowledge graph for AI with SurrealDB and Kreuzberg&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>surrealdb</category>
      <category>kreuzberg</category>
      <category>database</category>
      <category>integration</category>
    </item>
    <item>
      <title>Schema migrations in SurrealDB: A local dev workflow</title>
      <dc:creator>Mark Gyles</dc:creator>
      <pubDate>Fri, 08 May 2026 12:42:16 +0000</pubDate>
      <link>https://dev.to/surrealdb/schema-migrations-in-surrealdb-a-local-dev-workflow-1ijm</link>
      <guid>https://dev.to/surrealdb/schema-migrations-in-surrealdb-a-local-dev-workflow-1ijm</guid>
      <description>&lt;p&gt;Author: &lt;a href="https://github.com/itsezc" rel="noopener noreferrer"&gt;Chiru Boggavarapu&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This post walks through a proper migration workflow for local development using &lt;strong&gt;SurrealKit&lt;/strong&gt;, an official tool from the SurrealDB team that handles schema sync, rollouts, seeding, and testing.&lt;/p&gt;

&lt;p&gt;If you've spent any time working with SurrealDB, you know that with great flexibility comes the responsibility of managing your schema carefully. You're iterating quickly, adding tables, refining field definitions, and removing what you no longer need, and before long your local database can drift out of sync with what your code expects.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is SurrealKit?
&lt;/h2&gt;

&lt;p&gt;SurrealKit is a CLI tool that manages your SurrealDB schema through &lt;code&gt;.surql&lt;/code&gt; files. You define your schema as files, and SurrealKit keeps your database in sync with them.&lt;/p&gt;

&lt;p&gt;It has two main modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sync&lt;/strong&gt;: a fast, declarative approach for local and dev environments. Your files are the source of truth. Add something and it gets created, change it and it gets updated, remove it and it gets deleted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rollout&lt;/strong&gt;: a more controlled migration path for shared or production databases, with planning, staged execution, and rollback support.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For local dev, you'll mostly live in &lt;code&gt;sync&lt;/code&gt;. Rollouts come into play when you're pushing to shared environments.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Cargo is the Rust package manager. It downloads a Rust package's dependencies, compiles the packages, and makes distributable packages.&lt;/p&gt;

&lt;p&gt;Install via Cargo&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo &lt;span class="nb"&gt;install &lt;/span&gt;surrealkit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or grab a prebuilt binary from the GitHub releases page if you don't want to compile it.&lt;/p&gt;

&lt;p&gt;Initialise a project&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;surrealkit init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a &lt;code&gt;/database&lt;/code&gt; directory with the scaffolding you need. SurrealKit connects to your database using environment variables, so add these to your &lt;code&gt;.env&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;.env&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="s"&gt;SURREALDB_HOST=localhost:8000&lt;/span&gt;
&lt;span class="s"&gt;SURREALDB_NAME=myapp&lt;/span&gt;
&lt;span class="s"&gt;SURREALDB_NAMESPACE=development&lt;/span&gt;
&lt;span class="s"&gt;SURREALDB_USER=root&lt;/span&gt;
&lt;span class="s"&gt;SURREALDB_PASSWORD=secret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It supports both &lt;code&gt;DATABASE_HOST&lt;/code&gt; and &lt;code&gt;PUBLIC_DATABASE_HOST&lt;/code&gt; variants, which is handy if you're working in a SvelteKit or similar setup where env vars are split by visibility.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Local Dev Workflow
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Writing Your Schema
&lt;/h3&gt;

&lt;p&gt;Schema files live in &lt;code&gt;database/schema/&lt;/code&gt;. Each file is a &lt;code&gt;.surql&lt;/code&gt; file with your table definitions, indexes, access rules — whatever you'd normally write in SurrealQL.&lt;/p&gt;

&lt;p&gt;The structure is entirely up to you. You might have one file per table, or group related things together. Either way, SurrealKit tracks what's in those files and reconciles it with what's actually in your database.&lt;/p&gt;

&lt;h3&gt;
  
  
  Syncing Changes
&lt;/h3&gt;

&lt;p&gt;When you've made a change and want to apply it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;surrealkit &lt;span class="nb"&gt;sync&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. SurrealKit diffs your schema files against the current database state and applies what's changed. If you deleted a table definition from your files, it removes it from the database too. Everything stays in sync.&lt;/p&gt;

&lt;p&gt;For active development sessions, watch mode is where you'll spend most of your time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;surrealkit &lt;span class="nb"&gt;sync&lt;/span&gt; &lt;span class="nt"&gt;--watch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This watches your &lt;code&gt;database/schema/&lt;/code&gt; directory and resyncs automatically whenever you save a file. It handles deletions too — if you remove a definition, it gets cleaned up. No manual intervention needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Moving to Shared Environments
&lt;/h2&gt;

&lt;p&gt;The sync approach is great for local databases you own completely. But the moment you're working against a shared or staging database, you need more control. That's what rollouts are for.&lt;/p&gt;

&lt;h3&gt;
  
  
  Baseline First
&lt;/h3&gt;

&lt;p&gt;Before your first rollout on an existing database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;surrealkit rollout baseline
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This records the current state so SurrealKit knows what it's working from.&lt;/p&gt;

&lt;h3&gt;
  
  
  Plan → Start → Complete
&lt;/h3&gt;

&lt;p&gt;When you're ready to ship a schema change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;surrealkit rollout plan &lt;span class="nt"&gt;--name&lt;/span&gt; add_user_indexes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generates a TOML manifest in &lt;code&gt;database/rollouts/&lt;/code&gt; describing exactly what will change. You can review it, commit it, get it reviewed. Treat it like a pull request for your schema.&lt;/p&gt;

&lt;p&gt;When you're ready to apply, you'll reference the manifest by its full filename. SurrealKit names these with a timestamp prefix: &lt;code&gt;20260302153045&lt;/code&gt; translates to 2nd March 2026 at 15:30:45. This is the time the plan was generated, and it's there so rollouts always have a guaranteed sort order. If two people generate a plan on the same day, the timestamps keep them distinct and sequential. You'll see the full name in your &lt;code&gt;database/rollouts/&lt;/code&gt; directory after running &lt;code&gt;plan&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When you're ready to apply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;surrealkit rollout start 20260302153045__add_user_indexes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This runs the non-destructive part of the migration. Once your application is deployed and you're confident everything is working:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;surrealkit rollout &lt;span class="nb"&gt;complete &lt;/span&gt;20260302153045__add_user_indexes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This finishes the migration, including any cleanup of legacy objects.&lt;/p&gt;

&lt;p&gt;If something goes wrong mid-rollout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;surrealkit rollout rollback 20260302153045__add_user_indexes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also validate a manifest without touching the database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;surrealkit rollout lint 20260302153045__add_user_indexes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And check what state a rollout is in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;surrealkit rollout status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SurrealKit tracks rollout state in the database itself, so it's always resumable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Seeding
&lt;/h2&gt;

&lt;p&gt;SurrealKit has a built-in seeding system for populating your local database with test data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;surrealkit seed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seed files live alongside your schema in the &lt;code&gt;/database&lt;/code&gt; directory. Useful for onboarding new team members quickly — they clone the repo, init SurrealKit, run sync and seed, and they're good to go.&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;One of the more interesting features is the declarative testing framework. You write TOML test suites in &lt;code&gt;database/tests/suites/&lt;/code&gt; and run them with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;surrealkit &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each suite runs in an isolated ephemeral namespace, so tests can't interfere with each other or your actual data. You can test SQL assertions, permission rules, schema metadata, and even HTTP API endpoints.&lt;/p&gt;

&lt;p&gt;A simple example, checking that a guest can't create an order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"security_smoke"&lt;/span&gt;

&lt;span class="nn"&gt;[[cases]]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"guest_cannot_create_order"&lt;/span&gt;
&lt;span class="py"&gt;kind&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"sql_expect"&lt;/span&gt;
&lt;span class="py"&gt;actor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"guest"&lt;/span&gt;
&lt;span class="py"&gt;sql&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"CREATE order CONTENT { total: 10 };"&lt;/span&gt;
&lt;span class="py"&gt;allow&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="py"&gt;error_contains&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"permission"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also test permission matrices across a whole table at once, which is handy for verifying your access rules haven't regressed after a schema change.&lt;/p&gt;

&lt;p&gt;For CI, add &lt;code&gt;--json-out&lt;/code&gt; to get machine-readable output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;surrealkit &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--json-out&lt;/span&gt; database/tests/report.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command exits non-zero on any failure, so it integrates cleanly with GitHub Actions and other CI/CD systems seamlessly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Putting It Together
&lt;/h2&gt;

&lt;p&gt;Here's what a typical day looks like with this setup:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pull the latest changes&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;surrealkit sync&lt;/code&gt; to update your local DB&lt;/li&gt;
&lt;li&gt;Edit schema files in &lt;code&gt;database/schema/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;surrealkit sync --watch&lt;/code&gt; while you work&lt;/li&gt;
&lt;li&gt;When you're done, run &lt;code&gt;surrealkit test&lt;/code&gt; to make sure nothing broke&lt;/li&gt;
&lt;li&gt;Commit your schema files alongside your code&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For production changes, swap step 4 for &lt;code&gt;surrealkit rollout plan&lt;/code&gt; and follow the plan → start → complete flow.&lt;/p&gt;

&lt;p&gt;Would love to hear how you get on with SurrealKit. &lt;a href="https://discord.com/invite/surrealdb" rel="noopener noreferrer"&gt;Jump into Discord&lt;/a&gt; and let us know how it goes or if you need a hand along the way.&lt;/p&gt;

</description>
      <category>surrealdb</category>
      <category>surrealkit</category>
      <category>database</category>
      <category>schema</category>
    </item>
    <item>
      <title>New SurrealDB docs search using hybrid search and HNSW/BM25 reranking</title>
      <dc:creator>Mark Gyles</dc:creator>
      <pubDate>Fri, 01 May 2026 15:32:31 +0000</pubDate>
      <link>https://dev.to/surrealdb/new-surrealdb-docs-search-using-hybrid-search-and-hnswbm25-reranking-51jd</link>
      <guid>https://dev.to/surrealdb/new-surrealdb-docs-search-using-hybrid-search-and-hnswbm25-reranking-51jd</guid>
      <description>&lt;p&gt;Author: &lt;a href="https://x.com/mithridates" rel="noopener noreferrer"&gt;Dave MacLeod&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Trying to produce relevant search results is one of the hardest things to get right when building an app. Searching can be done in a lot of ways: basic text search, full-text search, vector search, and often even graph and geospatial.&lt;/p&gt;

&lt;p&gt;This post introduces an example that includes two of these models: full-text and vector search together. These two tend to involve quite a bit of SurrealQL code, because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To use full-text search, you first have to define how you want text to be split up and modified. Do you want it to be case-sensitive? Split by whitespace, or some other way? Modified to root forms based on the language of the text? (And so on...)&lt;/li&gt;
&lt;li&gt;To use vector search, you have to generate embeddings for pieces of text and know how to query them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What makes this example special is that it is the search function you use every time you type some text into the "Search the docs" part of the SurrealDB documentation! This functionality was recently implemented, and because it's open source we are able to show you the ins and outs of how it was done.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Doc Search Is Implemented
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/surrealdb/docs.surrealdb.com/blob/main/search/README.md" rel="noopener noreferrer"&gt;README&lt;/a&gt; in the &lt;a href="https://github.com/surrealdb/docs.surrealdb.com/tree/main/search" rel="noopener noreferrer"&gt;/search&lt;/a&gt; section of the docs repo goes into a good amount of detail. &lt;/p&gt;

&lt;p&gt;In addition, you can see the mechanics of the search itself inside  this &lt;a href="https://github.com/surrealdb/docs.surrealdb.com/blob/main/src/utils/search.ts" rel="noopener noreferrer"&gt;search.ts file&lt;/a&gt; in another section.&lt;/p&gt;

&lt;p&gt;The entire implementation is done using SurrealDB, along with Bun to deploy the website and an OpenAI API key. You can go to &lt;a href="https://github.com/surrealdb/docs.surrealdb.com/tree/main/search#local-development" rel="noopener noreferrer"&gt;this part of the page&lt;/a&gt; to deploy it locally and test it out. &lt;/p&gt;

&lt;p&gt;Here is what the final result looks like:&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%2Fgsy92u2q866ijtmk97w5.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%2Fgsy92u2q866ijtmk97w5.png" alt="How Doc Search Is Implemented" width="800" height="679"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since the README file already goes into detail about how to do the deployment yourself, in this post we will instead take a closer look at the SurrealQL statements themselves to see how they work and how they might help you do something similar in your own tools and apps.&lt;/p&gt;




&lt;h2&gt;
  
  
  The role SurrealDB Plays
&lt;/h2&gt;

&lt;p&gt;As the README mentions, the search functionality uses hybrid search:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Hybrid search for the SurrealDB documentation. Combines BM25 full-text search with OpenAI vector embeddings, fused via Reciprocal Rank Fusion (RRF) inside SurrealDB.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://surrealdb.com/docs/learn/data-models/vector-search/hybrid-search" rel="noopener noreferrer"&gt;Hybrid search&lt;/a&gt; allows you to combine full-text search with vector search, giving you the best of both worlds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vector search&lt;/strong&gt; is useful because it is capable of going beyond the outward appearance of words to get to their semantic meaning. For example, the term lead in the following paragraph is used to mean two different things. We can recognize that as humans, and LLM models are also able to do the same, returning the output as an embedding (a large array of numbers representing semantic space).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The project &lt;strong&gt;lead&lt;/strong&gt; frowned and took a hard look at the results. They were clear as day. There was far too much lead content in the water. That's why the town had gotten sick!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;However, full-text search is useful too because it excels at slicing and modifying text into tokens. To show what that looks like, here's what the full-text analyzer looks like that is used in the docs search.&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;ANALYZER&lt;/span&gt; &lt;span class="n"&gt;OVERWRITE&lt;/span&gt; &lt;span class="k"&gt;simple&lt;/span&gt;
    &lt;span class="n"&gt;TOKENIZERS&lt;/span&gt; &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;camel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;punct&lt;/span&gt;
    &lt;span class="n"&gt;FILTERS&lt;/span&gt; &lt;span class="n"&gt;snowball&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;english&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The easiest way to understand what all the tokenizers and filters do in an analyzer is by putting a string into the &lt;a href="https://surrealdb.com/docs/reference/query-language/functions/database-functions/search#searchanalyze" rel="noopener noreferrer"&gt;search::analyze()&lt;/a&gt; function, which returns an array. To avoid showing a huge array in this blog post, we'll call &lt;code&gt;.join(' ')&lt;/code&gt; on the output to join them back into a single string.&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;search&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="k"&gt;analyze&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;"simple"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="nv"&gt;"The project lead frowned and took a hard look at the results. They were clear as day. There was far too much lead content in the water. That's how the town had gotten sick!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As the output shows, using this analyzer will allow you to match on terms like "FroWNinG" which will turn into "frown" before being compared against a search string.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"the project lead frown and took a hard look at the result . they were clear as day . there was far too much lead content in the water . that ' s how the town had gotten sick !"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So if vector search and full-text search each have their advantages, how can you combine the two? It would be nice if you could somehow plug them into a function that does this.&lt;/p&gt;

&lt;p&gt;And it turns out that SurrealDB has exactly that! It is called &lt;a href="https://surrealdb.com/docs/reference/query-language/functions/database-functions/search#searchrrf" rel="noopener noreferrer"&gt;search::rrf()&lt;/a&gt; and was added during the 3.0 beta period. Here is what it looks like in practice.&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="c1"&gt;-- ── Reciprocal Rank Fusion ──&lt;/span&gt;
&lt;span class="c1"&gt;-- Combines the four ranked lists into a single ranking.&lt;/span&gt;
&lt;span class="c1"&gt;-- RRF scores each result as: sum(1 / (k + rank_in_list))&lt;/span&gt;
&lt;span class="c1"&gt;-- across all lists the result appears in.&lt;/span&gt;
&lt;span class="c1"&gt;--   arg 1: array of ranked lists to fuse&lt;/span&gt;
&lt;span class="c1"&gt;--   arg 2: k=60 (smoothing constant, standard RRF default)&lt;/span&gt;
&lt;span class="c1"&gt;--   arg 3: limit=80 (max candidates to consider)&lt;/span&gt;
&lt;span class="n"&gt;LET&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;fused&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;search&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;rrf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;page_ft&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;page_vs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;section_ft&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;section_vs&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; 
    &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="mi"&gt;80&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can probably guess, it is fusing the results of previous queries for full-text search on pages and sections (&lt;code&gt;$page_ft, $section_ft&lt;/code&gt;) together with vector searches on pages and sections (&lt;code&gt;$page_vs, $section_vs&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Full-text searches are done using the &lt;code&gt;@@&lt;/code&gt; operator (the "matches" operator). This operator works as long as you have a index on a field that uses a &lt;code&gt;FULLTEXT ANALYZER&lt;/code&gt;:&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="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;OVERWRITE&lt;/span&gt; &lt;span class="n"&gt;page_ft_title&lt;/span&gt;       &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="n"&gt;FIELDS&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;       &lt;span class="n"&gt;FULLTEXT&lt;/span&gt; &lt;span class="n"&gt;ANALYZER&lt;/span&gt; &lt;span class="k"&gt;simple&lt;/span&gt; &lt;span class="n"&gt;BM25&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;DEFINE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;OVERWRITE&lt;/span&gt; &lt;span class="n"&gt;page_ft_breadcrumb&lt;/span&gt;  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="n"&gt;FIELDS&lt;/span&gt; &lt;span class="n"&gt;breadcrumb&lt;/span&gt;  &lt;span class="n"&gt;FULLTEXT&lt;/span&gt; &lt;span class="n"&gt;ANALYZER&lt;/span&gt; &lt;span class="k"&gt;simple&lt;/span&gt; &lt;span class="n"&gt;BM25&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;DEFINE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;OVERWRITE&lt;/span&gt; &lt;span class="n"&gt;page_ft_description&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="n"&gt;FIELDS&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="n"&gt;FULLTEXT&lt;/span&gt; &lt;span class="n"&gt;ANALYZER&lt;/span&gt; &lt;span class="k"&gt;simple&lt;/span&gt; &lt;span class="n"&gt;BM25&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;DEFINE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;OVERWRITE&lt;/span&gt; &lt;span class="n"&gt;page_ft_content&lt;/span&gt;     &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="n"&gt;FIELDS&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;     &lt;span class="n"&gt;FULLTEXT&lt;/span&gt; &lt;span class="n"&gt;ANALYZER&lt;/span&gt; &lt;span class="k"&gt;simple&lt;/span&gt; &lt;span class="n"&gt;BM25&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;DEFINE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;OVERWRITE&lt;/span&gt; &lt;span class="n"&gt;page_ft_path&lt;/span&gt;        &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="n"&gt;FIELDS&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;        &lt;span class="n"&gt;FULLTEXT&lt;/span&gt; &lt;span class="n"&gt;ANALYZER&lt;/span&gt; &lt;span class="k"&gt;simple&lt;/span&gt; &lt;span class="n"&gt;BM25&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In between these two &lt;code&gt;@&lt;/code&gt; operators you can insert a number to access the &lt;code&gt;search::score()&lt;/code&gt; for each field. This will make sense once you take a look at these two queries. Each of them is querying multiple fields, each of which will return a score thanks to &lt;code&gt;BM25&lt;/code&gt; defined in the field. To know which field is which, you put a reference number in between the &lt;code&gt;@&lt;/code&gt; operators like &lt;code&gt;@0@&lt;/code&gt; and &lt;code&gt;@2@&lt;/code&gt;. They can then be added together or multiplied or whatever logic you would like to use.&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;LET&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;page_ft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&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="nv"&gt;"page"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;page_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;breadcrumb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;search&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;search&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;search&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;search&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;search&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;ft_score&lt;/span&gt;
        &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;
        &lt;span class="k"&gt;WHERE&lt;/span&gt;
            &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;
            &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;
            &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;breadcrumb&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;
            &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;
            &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;
        &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;ft_score&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
        &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;LET&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;section_ft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&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="nv"&gt;"section"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"#"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;page_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collection&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;breadcrumb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;search&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;search&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;search&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;ft_score&lt;/span&gt;
        &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;section&lt;/span&gt;
        &lt;span class="k"&gt;WHERE&lt;/span&gt;
            &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;
            &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;breadcrumb&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;
            &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;
        &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;ft_score&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
        &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The vector search query also uses its own index. Note that you don't specifically need an index to use vector search, as you can go with a &lt;a href="https://surrealdb.com/docs/reference/query-language/functions/database-functions/vector#vectorsimilaritycosine" rel="noopener noreferrer"&gt;brute force function&lt;/a&gt; such as &lt;code&gt;vector::similarity::cosine()&lt;/code&gt; instead. However, an &lt;code&gt;HNSW&lt;/code&gt; index instead can speed up the process dramatically if you don't mind sacrificing a bit of precision. Here the index is defined with a dimension of 1536 to match the length of the array returned by OpenAI's text-embedding-3-small model.&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="c1"&gt;-- HNSW index for approximate nearest-neighbour vector search.&lt;/span&gt;
&lt;span class="c1"&gt;-- Dimension 1536 matches OpenAI text-embedding-3-small output.&lt;/span&gt;
&lt;span class="c1"&gt;-- Cosine distance is the standard metric for normalised text embeddings.&lt;/span&gt;
&lt;span class="n"&gt;DEFINE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;OVERWRITE&lt;/span&gt; &lt;span class="n"&gt;page_embedding_hnsw&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="n"&gt;FIELDS&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt;
    &lt;span class="n"&gt;HNSW&lt;/span&gt; &lt;span class="n"&gt;DIMENSION&lt;/span&gt; &lt;span class="mi"&gt;1536&lt;/span&gt; &lt;span class="n"&gt;DIST&lt;/span&gt; &lt;span class="n"&gt;COSINE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Having an &lt;code&gt;HNSW&lt;/code&gt; index also lets you use the &lt;code&gt;&amp;lt;||&amp;gt;&lt;/code&gt; operator (the KNN, or K-nearest neighbour) operator when performing the query. The comments above the code explain what each of the two numbers mean. Here, the &lt;code&gt;|30,100|&lt;/code&gt; syntax means to return 30 neighbours among up to 100 candidates.&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="c1"&gt;-- ── Page vector search ──&lt;/span&gt;
&lt;span class="c1"&gt;-- Finds the 30 pages whose embeddings are closest to the&lt;/span&gt;
&lt;span class="c1"&gt;-- query embedding. The &amp;lt;|30,100|&amp;gt; syntax means: return 30&lt;/span&gt;
&lt;span class="c1"&gt;-- neighbours, exploring up to 100 candidates in the HNSW&lt;/span&gt;
&lt;span class="c1"&gt;-- graph (higher = more accurate but slower).&lt;/span&gt;
&lt;span class="n"&gt;LET&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;page_vs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&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="nv"&gt;"page"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;page_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;breadcrumb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;knn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;distance&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&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;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;100&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;qvec&lt;/span&gt;
    &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;distance&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;
    &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- ── Section vector search ──&lt;/span&gt;
&lt;span class="c1"&gt;-- Same as page vector search but for H2 sections.&lt;/span&gt;
&lt;span class="c1"&gt;-- Pulls the parent page's path and collection via the&lt;/span&gt;
&lt;span class="c1"&gt;-- record link (page.path, page.collection).&lt;/span&gt;
&lt;span class="n"&gt;LET&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;section_vs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&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="nv"&gt;"section"&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"#"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;page_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collection&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;breadcrumb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;knn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;distance&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;section&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&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;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;100&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;qvec&lt;/span&gt;
    &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;distance&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;
    &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So that is how the magic happens. Well, part of it. Be sure to check out the rest of the code which shows what happens after the SurrealDB query returns. For example, as the database is unaware of which collections it is searching through, some modifications are made on the SDK side to give SDK docs less precedence compared to more core docs. This is something that you could perhaps do on the database side if you wanted (starting with &lt;code&gt;DEFINE TABLE sdk_page&lt;/code&gt; instead of &lt;code&gt;DEFINE TABLE page&lt;/code&gt; for example) but that will always depend on your own case.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ──────────────────────────────────────────────────────────&lt;/span&gt;
&lt;span class="c1"&gt;// Post-retrieval boosting&lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="c1"&gt;// After RRF fusion, we apply multiplicative boosts to adjust&lt;/span&gt;
&lt;span class="c1"&gt;// rankings based on signals the database query can't capture:&lt;/span&gt;
&lt;span class="c1"&gt;// title similarity, content type (page vs section), source&lt;/span&gt;
&lt;span class="c1"&gt;// collection, and comparison-query detection.&lt;/span&gt;
&lt;span class="c1"&gt;// ──────────────────────────────────────────────────────────&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Non-SDK doc collections get a small ranking boost because
 * generic queries like "authentication" should prefer the
 * core concept page over an SDK API reference page that
 * happens to mention auth as one of many methods.
 */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CORE_COLLECTIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;doc-surrealdb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;doc-surrealql&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;doc-tutorials&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;doc-cloud&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;doc-surrealist&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;doc-surrealml&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;doc-surrealkv&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;doc-integrations&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Simplifying the Example
&lt;/h2&gt;

&lt;p&gt;Since you might not have the head space at the moment to play around with the existing code demonstrated in this blog post, let's finish up with the same pattern as above in a very simplified form. This will let you copy and paste the example into the online &lt;a href="https://app.surrealdb.com/?_gl=1*n737cr*_gcl_aw*R0NMLjE3NzQ2MjU1MDcuQ2owS0NRancxWmpPQmhDbUFSSXNBRER1RlRDVV9VN2JENUdhMFQzTm5mV0paMzNOVkxiTDNROVFOSTlUckhRUHV0V2FVN1l2czJ0MlNGQWFBc3hCRUFMd193Y0I.*FPAU*NTM0Mzg1Mzg1LjE3NzA2NTk1MTg.*_ga*NDAyMzA0NTY4LjE3NzEyNDU1Nzg.*_ga_J1NWM32T1V*czE3Nzc2NDUxMDkkbzQxMyRnMSR0MTc3NzY0NTE5NCRqNjAkbDAkaDQwOTUwMzMzMA..*_fplc*OVhscHhYNEtSdG1MUlk4bUJqSnJVaTVXSDBNSTEyUG1DMFUlMkY3VXoycHhQbFVUaHFGY1dvR2JUdHFuVkhXWEY0JTJGTkYlMkZnUDVVajNoalZ3V095cG93cGd2N0c2ZzdMZk5hM1hLS2xxMXYyNG42R3hUZ0UlMkY0SXdsUnFWak9DWkElM0QlM0Q." rel="noopener noreferrer"&gt;Surrealist UI&lt;/a&gt; to give it a try yourself.&lt;/p&gt;

&lt;p&gt;The content below holds all the same features: a full-text analyzer, a full-text and vector index, a full-text and vector query, and a final query that combines the two into hybrid search results. However, it has all been simplified to a single field for text content and embeddings, and a single document table that is being searched on. In addition, the embeddings have been cut down to an array of just ten numbers in length. An array that small is very imprecise, but just long enough to serve for our simple example.&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="c1"&gt;-- Define an analyzer&lt;/span&gt;
&lt;span class="n"&gt;DEFINE&lt;/span&gt; &lt;span class="n"&gt;ANALYZER&lt;/span&gt; &lt;span class="n"&gt;OVERWRITE&lt;/span&gt; &lt;span class="k"&gt;simple&lt;/span&gt; &lt;span class="n"&gt;TOKENIZERS&lt;/span&gt; &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;camel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;punct&lt;/span&gt; &lt;span class="n"&gt;FILTERS&lt;/span&gt; &lt;span class="n"&gt;SNOWBALL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;en&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Attach it to an index&lt;/span&gt;
&lt;span class="n"&gt;DEFINE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;OVERWRITE&lt;/span&gt; &lt;span class="n"&gt;ft&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt; &lt;span class="n"&gt;FIELDS&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="n"&gt;FULLTEXT&lt;/span&gt; &lt;span class="n"&gt;ANALYZER&lt;/span&gt; &lt;span class="k"&gt;simple&lt;/span&gt; &lt;span class="n"&gt;BM25&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Create a vector index too&lt;/span&gt;
&lt;span class="n"&gt;DEFINE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;OVERWRITE&lt;/span&gt; &lt;span class="n"&gt;page_embedding_hnsw&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt; &lt;span class="n"&gt;FIELDS&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="n"&gt;HNSW&lt;/span&gt; &lt;span class="n"&gt;DIMENSION&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="n"&gt;DIST&lt;/span&gt; &lt;span class="n"&gt;COSINE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Add some sample data&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;one&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;   &lt;span class="nv"&gt;"Elves and halflings and witches and such"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0019&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0142&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0664&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0173&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0109&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0066&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0045&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0204&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0087&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;two&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;   &lt;span class="nv"&gt;"Is that a wizard?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                        &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0010&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0051&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0207&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0787&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0061&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0127&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0172&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0097&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0077&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0120&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;three&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"Databases are used to store information"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0317&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0112&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0118&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0348&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0061&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0114&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0310&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0117&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0034&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0195&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;NONE&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;-- A sample query and its embedding&lt;/span&gt;
&lt;span class="n"&gt;LET&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;witch_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"witches"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;LET&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;witch_embed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0059&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0081&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0475&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0295&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0183&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0170&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0048&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0286&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;-- Get the full-text score&lt;/span&gt;
&lt;span class="n"&gt;LET&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;fts_score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&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;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="k"&gt;search&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;ft_score&lt;/span&gt;
        &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;
        &lt;span class="k"&gt;WHERE&lt;/span&gt;
            &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;witch_text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Get the vector score&lt;/span&gt;
&lt;span class="n"&gt;LET&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;vector_score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&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;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;knn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;distance&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&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;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;100&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;witch_embed&lt;/span&gt;
    &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;distance&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Combine the results as a hybrid score&lt;/span&gt;
&lt;span class="k"&gt;search&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;rrf&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;fts_score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;vector_score&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can see in the output that while only one document had a match on the word "witches" (and thus a value for ft_score), the vector search portion has been able to conclude that the sentence about wizards is semantically a bit closer than the one about databases. This would be a lot more accurate of course if the embedding were longer than 10 elements, but even at this length we can still see its potential.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Elves and halflings and witches and such"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"distance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.2789615948854415&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"ft_score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.4782197177410126&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"document:one"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"rrf_score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.024691358024691357&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Is that a wizard?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"distance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.385041442417419&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"document:two"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"rrf_score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.012195121951219513&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Databases are used to store information"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"distance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.3876749269204385&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"document:three"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"rrf_score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.012048192771084338&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hopefully the simplified example has made it easy to follow the logic we use in our own doc search and has given you some ideas for how to implement patterns yourself. Have any questions or comments about hybrid search? Feel free to &lt;a href="https://surrealdb.com/docs/build/deployment/surrealdb-cloud/getting-started/create-an-instance" rel="noopener noreferrer"&gt;Create a free SurrealDB Cloud instance today to get started&lt;/a&gt;, and drop by &lt;a href="https://discord.com/invite/surrealdb" rel="noopener noreferrer"&gt;our Discord community&lt;/a&gt; to discuss anything on your mind with the SurrealDB community and staff.&lt;/p&gt;

</description>
      <category>surrealdb</category>
      <category>database</category>
      <category>devrel</category>
      <category>hybridsearch</category>
    </item>
    <item>
      <title>How to get near-perfect, deterministic accuracy from your AI agents</title>
      <dc:creator>Mark Gyles</dc:creator>
      <pubDate>Thu, 23 Apr 2026 22:23:21 +0000</pubDate>
      <link>https://dev.to/surrealdb/how-to-get-near-perfect-deterministic-accuracy-from-your-ai-agents-56ma</link>
      <guid>https://dev.to/surrealdb/how-to-get-near-perfect-deterministic-accuracy-from-your-ai-agents-56ma</guid>
      <description>&lt;p&gt;Author: &lt;a href="https://www.linkedin.com/in/mpenaroza/" rel="noopener noreferrer"&gt;Matthew Penaroza&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I have spent a lot of time working on large-scale agent architectures with some of the largest organizations in the world, and the single most common mistake I see teams make is assuming their accuracy problems are model problems. They almost never are. Your agent is typically not struggling because the LLM is weak. It is struggling because your retrieval layer is feeding it bad context. Fix your retrieval, and accuracy jumps. Make your retrieval learn from its own results, and accuracy becomes deterministic. Here is what that looks like in practice.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; - Agent accuracy problems are almost always retrieval problems, not model problems. At scale, vector search alone is not enough. You need structured filters, graph traversal, temporal constraints, and vector similarity composed in a single atomic query to get to ~90–95% accuracy. To close the remaining gap to ~99%+, you need reasoning graphs and retrieval graphs: structured graph edges that capture the agent's logic and retrieval patterns, feeding back into the pipeline so the system learns which approaches produce correct outcomes. Both require a multi-model database with ACID transactions across every data model, which is exactly what we designed SurrealDB to be.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The problem: why agent accuracy hits a ceiling
&lt;/h2&gt;

&lt;p&gt;Every agent follows the same loop: retrieve context, reason over it, act. The quality of the action is bounded by the quality of the context. I have seen perfectly capable models produce terrible outputs because the context they were given was noisy, and I have seen mid-tier models produce excellent outputs because the context was precise. The model matters less than most teams think.&lt;/p&gt;

&lt;p&gt;At small scale, none of this is a problem. If you have a few thousand documents, vector search returns good results, everything fits in a single database, and accuracy looks great. The problems emerge when data grows into the millions or billions of records. Suddenly more candidates look semantically similar, top-k selection becomes unstable, unrelated chunks bleed into the context window, and your agent starts hallucinating. The RAG survey literature (Gao et al., 2023) documents these failure modes extensively. Vector similarity is a useful ranking signal. It is not a control plane.&lt;/p&gt;

&lt;p&gt;What makes it worse is the typical production stack. Most teams are running Postgres for state, Neo4j for relationships, Pinecone for vectors, MongoDB for documents, sometimes Elasticsearch and a memory layer on top. Five or six independent systems, each with its own consistency model. When the graph database is 200ms behind the relational store, or the vector index has not ingested a document that was just written, your agent is reasoning over a version of reality that does not exist. It does not know the data is stale. It just makes a worse decision. And in multi-step workflows, each bad retrieval compounds into the next.&lt;/p&gt;

&lt;p&gt;So the question worth asking is not "which model should we use?" It is: how do you guarantee that the context reaching the model is valid, complete, current, and precise, even at scale? In my experience, the answer has two layers. The first gets you to roughly 90–95%. The second closes the gap to 99%+.&lt;/p&gt;

&lt;h2&gt;
  
  
  The accuracy staircase
&lt;/h2&gt;

&lt;p&gt;Each layer compounds on the last. Skip any step and accuracy hits a ceiling.&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%2Fa5lmoyx1h1gwz8fg7zvz.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%2Fa5lmoyx1h1gwz8fg7zvz.png" alt="The accuracy staircase" width="800" height="894"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting to ~90–95%: scope-first retrieval
&lt;/h2&gt;

&lt;p&gt;The idea is straightforward: instead of running similarity search against everything and cleaning up the results, you constrain the candidate set before ranking. Define what counts as a valid result first. Rank inside that scope second.&lt;/p&gt;

&lt;p&gt;Think about what a single agent retrieval step actually needs, and what goes wrong without each layer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Structured filters&lt;/strong&gt; narrow by type, permissions, and tenancy. Without them, I have watched a support agent retrieve internal engineering postmortems and serve them verbatim to a customer, because the postmortem was semantically similar to the customer's question. The vector search was technically correct. The result was a data leak.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Temporal constraints&lt;/strong&gt; ensure the data is current. Without them, an agent recommending products will happily surface items that were discontinued months ago, because the old product description is still the closest embedding match. The customer clicks through to a dead link. Trust erodes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Graph traversal&lt;/strong&gt; follows real relationships: this customer owns this product, which has this known issue, which maps to this knowledge base article. Without it, your agent retrieves articles about similar-sounding problems on entirely different products. The answer reads plausibly but is wrong in the specifics, which is the worst kind of hallucination because it is hard to catch.&lt;/p&gt;

&lt;p&gt;Only after all of that scoping should vector similarity and full-text search run, ranking the remaining candidates by relevance. By this point, the candidate pool is already valid. Similarity is just picking the best among the valid.&lt;/p&gt;

&lt;p&gt;The problem is you cannot compose these operations across a fragmented stack. In a multi-database architecture, each is an independent query against an independent system. The application layer stitches the results together and hopes the data was consistent across all of them. It never fully is. You need a database that handles all of this natively, in a single query, against a single consistent snapshot. That is why we built SurrealDB the way we did: structured filters, temporal constraints, graph traversal, vector similarity, full-text search, and business rule enforcement all compose in one SurrealQL transaction.&lt;/p&gt;

&lt;p&gt;Here is what that looks like. One query, every scoping layer, against one consistent snapshot:&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;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;knn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;vec_dist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;search&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;ft_score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;knn&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;65&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;search&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;35&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;blend_score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;search&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;highlight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'&amp;lt;em&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;/em&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;content_snippet&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;knowledge_base&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'support'&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;tenant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;tenant&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;agent_principal&lt;/span&gt; &lt;span class="n"&gt;INSIDE&lt;/span&gt; &lt;span class="n"&gt;allowed_principals&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;owns&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;has_issue&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;knowledge_base&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;content_embedding&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;|&lt;/span&gt;&lt;span class="mi"&gt;100&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;query_embedding&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;query_text&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;blend_score&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That single statement does what would take four or five round-trips across a fragmented stack: permission checks, temporal filtering, graph traversal through the customer's actual product relationships, and hybrid vector + full-text ranking. One query. One transaction. One consistent snapshot. The line that does the most work is the graph traversal: &lt;code&gt;$customer-&amp;gt;owns-&amp;gt;product-&amp;gt;has_issue-&amp;gt;knowledge_base&lt;/code&gt;. That is not a join. It is a native graph traversal across edges, and it means the agent only sees knowledge base articles connected to the specific products this customer actually owns. Everything else is excluded before similarity even runs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Two reasons fragmented stacks kill accuracy
&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.amazonaws.com%2Fuploads%2Farticles%2Fvx7ib5z59fa66hlpf13l.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%2Fvx7ib5z59fa66hlpf13l.png" alt="Two reasons fragmented stacks kill accuracy" width="800" height="1019"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Microsoft's GraphRAG work (Edge et al., 2024) demonstrated the same principle at the retrieval level: combining graph structure with similarity search significantly outperforms vector-only approaches. The CoALA framework (Sumers et al., 2024) describes it architecturally: agent memory must be consistent and accessible within a single reasoning cycle. A multi-model database that handles all data models under one transaction boundary is the simplest way I know to actually do both.&lt;/p&gt;

&lt;p&gt;This gets you to roughly 90–95% accuracy on large corpora. The consistency gaps are gone. The retrieval noise is way down. For many use cases, this is enough.&lt;/p&gt;

&lt;p&gt;But for agents that need to operate autonomously, make consequential decisions, or run in production without human oversight, the last 5–10% is where the difference between "mostly works" and "deterministic" lives. Closing that gap requires a different kind of mechanism.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting to ~99%+: reasoning graphs and retrieval graphs
&lt;/h2&gt;

&lt;p&gt;Scope-first retrieval fixes the shape of the funnel. Reasoning and retrieval graphs fix the funnel itself. They are the mechanism that turns a static retrieval pipeline into one that learns from its own results, and in my experience they are the single biggest differentiator between agents that are "pretty good" and agents that are genuinely deterministic.&lt;/p&gt;

&lt;p&gt;Every time an agent runs a retrieval pipeline and makes a decision, two graph structures get written in the same transaction as the action: a &lt;strong&gt;reasoning graph&lt;/strong&gt; (the agent's decision and the evaluated edges for each piece of evidence it considered) and a &lt;strong&gt;retrieval graph&lt;/strong&gt; (the operational details of how context was retrieved).&lt;/p&gt;

&lt;p&gt;I want to be specific about what these are. They are not log entries. They are not analytics events. They are graph edges stored in the same database as the data they describe, connecting the agent, the action, the customer, and the evidence records the agent evaluated. Written atomically with the agent's action and fully traversable with the same query language you use for everything else. ACID-guaranteed edges in the same graph your agent operates on. This is exactly why SurrealDB is a graph-native multi-model database: the reasoning and retrieval graphs have to be part of the same graph as the documents, the relationships, the vector embeddings, and the agent state, all under one transaction boundary. If they are not, the causal link between "what was retrieved," "how the agent reasoned about it," and "what it decided" breaks.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;reasoning graph&lt;/strong&gt; is a set of edges radiating from the agent. A &lt;strong&gt;decided&lt;/strong&gt; edge connects the agent to the action, carrying confidence and the eventual outcome. But the reasoning itself is not a field on that edge. Each step of the agent's logic is its own &lt;strong&gt;evaluated&lt;/strong&gt; edge connecting the agent directly to the evidence it considered: &lt;code&gt;agent-&amp;gt;evaluated-&amp;gt;kb:4821&lt;/code&gt; with a verdict (used or rejected), the reason, and how much it moved the agent's confidence. The reasoning is not a string or an array. It is a set of traversable edges connecting the agent to every piece of evidence it touched. The &lt;strong&gt;retrieval graph&lt;/strong&gt; is a &lt;code&gt;retrieved_via&lt;/code&gt; edge from the action back to the customer, carrying which scope filters were applied, the graph path traversed, the candidate count before and after scoping, whether the top result was relevant, and the retrieval latency.&lt;/p&gt;

&lt;h3&gt;
  
  
  Anatomy of the Reasoning and Retrieval Graphs
&lt;/h3&gt;

&lt;p&gt;Every reasoning step and every retrieval detail is a graph edge. Traversable, not buried in strings.&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%2F9f8wtnejx29cff55d4q1.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%2F9f8wtnejx29cff55d4q1.png" alt="Anatomy of the Reasoning and Retrieval Graphs" width="800" height="742"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The reason the reasoning has to be a graph, not a field, is that the pipeline planner needs to traverse individual evaluation steps across thousands of runs. If &lt;code&gt;kb:2201&lt;/code&gt; gets rejected in 94% of firmware queries, the planner can only discover that by traversing evaluated edges, not by parsing strings in a log. In SurrealQL, this is what the actual transaction looks like. The action is a node. The reasoning and retrieval are edges, created with &lt;code&gt;RELATE&lt;/code&gt;, connecting the agent to each piece of evidence and the action back to the customer:&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;BEGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;support_action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;escalate_7291&lt;/span&gt; &lt;span class="n"&gt;CONTENT&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'escalate_to_engineering'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;acme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ticket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ticket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;9913&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;RELATE&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;support_tier2&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;decided&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;support_action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;escalate_7291&lt;/span&gt; &lt;span class="n"&gt;CONTENT&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;confidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;94&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;RELATE&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;support_tier2&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;evaluated&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;kb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;4821&lt;/span&gt; &lt;span class="n"&gt;CONTENT&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;support_action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;escalate_7291&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'used'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'Identical symptoms on same SKU'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;confidence_delta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;RELATE&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;support_tier2&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;evaluated&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;kb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2201&lt;/span&gt; &lt;span class="n"&gt;CONTENT&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;support_action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;escalate_7291&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'rejected'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'Wrong product line'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;confidence_delta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;RELATE&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;support_tier2&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;evaluated&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;kb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;5540&lt;/span&gt; &lt;span class="n"&gt;CONTENT&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;support_action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;escalate_7291&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'rejected'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'Issue resolved, no longer active'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;confidence_delta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;RELATE&lt;/span&gt; &lt;span class="n"&gt;support_action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;escalate_7291&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;retrieved_via&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;acme&lt;/span&gt; &lt;span class="n"&gt;CONTENT&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;scope_filters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'support'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'acme'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'30d'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="n"&gt;graph_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'customer:acme-&amp;gt;owns-&amp;gt;product:x100-&amp;gt;has_issue-&amp;gt;kb'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;candidates_before&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2847331&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;candidates_after&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;top_result_relevant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;latency_ms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The decision logic is not buried in strings. Every reasoning step is an edge in the graph. The &lt;code&gt;decided&lt;/code&gt; edge connects the agent to the action. Each &lt;code&gt;evaluated&lt;/code&gt; edge connects the agent to a specific piece of evidence it considered, carrying the verdict, the reason, and the confidence delta. The &lt;code&gt;retrieved_via&lt;/code&gt; edge connects the action back to the customer with the retrieval metadata. The evaluated edges are not magic. Your agent framework already evaluates candidates and produces a decision. The &lt;code&gt;RELATE&lt;/code&gt; statements just persist what the agent already computed. The only design requirement is that your agent emits structured evaluations per candidate rather than a single monolithic response.&lt;/p&gt;

&lt;p&gt;That graph structure gives you something most teams have never had: you can replay any agent run, traverse the exact reasoning it followed, and identify exactly where and why it went wrong. But the real value is what happens when the reasoning and retrieval graphs feed back into the system. And they feed back in two different ways, serving two different purposes.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;pipeline planner&lt;/strong&gt; traverses the reasoning and retrieval graphs to decide &lt;strong&gt;what context the agent sees&lt;/strong&gt;. This is structural optimization. The planner can start from &lt;code&gt;kb:2201&lt;/code&gt; and traverse all evaluated edges pointing at it across every run. If it discovers that &lt;code&gt;kb:2201&lt;/code&gt; gets rejected 94% of the time for firmware queries, it deprioritizes or excludes it from the candidate set for that query type. It can walk &lt;code&gt;agent:support_tier2-&amp;gt;evaluated-&amp;gt;*&lt;/code&gt; to see every piece of evidence this agent has ever considered, grouped by action, filtered by verdict. It learns which retrieval patterns, graph paths, and scope filters correlate with correct outcomes and tightens the funnel accordingly.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;LLM itself&lt;/strong&gt; receives relevant past reasoning edges as context to guide &lt;strong&gt;how it thinks about the candidates it receives&lt;/strong&gt;. This is behavioral optimization. When the agent retrieves context for a firmware issue on &lt;code&gt;product:x100&lt;/code&gt;, the system can also pull in past evaluated edges for queries in the same product category: "Last time an agent evaluated a firmware issue on this product line, it used &lt;code&gt;kb:4821&lt;/code&gt; (identical symptoms, same SKU) and rejected &lt;code&gt;kb:2201&lt;/code&gt; (wrong product line). That decision was correct." The agent is not just getting better candidates. It is getting guidance on how to reason about those candidates, grounded in what actually worked before.&lt;/p&gt;

&lt;p&gt;Those two mechanisms work on different layers. The pipeline planner tightens the funnel so fewer, better candidates reach the agent. The past reasoning edges sharpen how the agent evaluates whatever reaches it. Together, they compress errors from both directions. This is different from a context graph, which stores world facts (this customer owns this product). The reasoning and retrieval graphs extend the context graph with behavioral facts: how agents reasoned, what worked, what did not. The pipeline planner and the LLM both walk all three layers.&lt;/p&gt;

&lt;h3&gt;
  
  
  The graph feedback loop
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;This is the critical shift.&lt;/strong&gt; The reasoning and retrieval graphs turn retrieval from a static pipeline into a feedback loop. The funnel does not just narrow the data. It narrows itself. Every agent run produces decided, evaluated, and retrieval edges. Those edges inform the next pipeline plan. The scoping gets tighter. The graph paths get more precise. And because both graphs are written in the same transaction as the action, you have a causally consistent, traversable record of every decision the agent ever made, every piece of evidence it considered, and the retrieval path that produced it.&lt;/p&gt;
&lt;/blockquote&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%2F4bekos5qtp4z4jij8ah5.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%2F4bekos5qtp4z4jij8ah5.png" alt="The graph feedback loop" width="800" height="727"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let me walk through what this looks like concretely. On the first run for a given query type, the retrieval funnel starts with 2.8 million candidates and narrows to 14. The agent evaluates the top candidates, creating an evaluated edge for each one with a verdict and reason. It makes a decision. The decision turns out to be correct. The decided edge, the evaluated edges, and the retrieval edges all persist in the graph.&lt;/p&gt;

&lt;p&gt;On the next similar query, both mechanisms kick in. The pipeline planner traverses past retrieval and reasoning edges. It knows the successful graph path. It knows which filter combination produced a good reduction ratio. It knows &lt;code&gt;kb:2201&lt;/code&gt; was rejected last time for being the wrong product line, so it excludes it from the candidate set entirely. The funnel goes from 2.8 million to 9 instead of 14. Meanwhile, the agent receives past evaluated edges as context alongside the candidates: "For similar firmware queries, &lt;code&gt;kb:4821&lt;/code&gt; was used because it described identical symptoms on the same SKU. &lt;code&gt;kb:5540&lt;/code&gt; was rejected because the issue was already resolved." The agent is not just seeing better candidates. It is seeing how a previous agent successfully reasoned about similar candidates.&lt;/p&gt;

&lt;p&gt;After a hundred similar queries, the graph contains a rich web of evaluated edges for that query type. The planner can see, across all runs, which evidence records consistently get used versus rejected, which rejection reasons recur, and which evaluation patterns correlate with correct outcomes. The LLM, on every new run, gets the distilled reasoning patterns from the most relevant past decisions. The funnel is no longer exploring. The reasoning is no longer starting from scratch. Both layers are applying learned patterns. And the patterns are not summaries or statistics computed offline. They are live graph topology that gets traversed on every run.&lt;/p&gt;

&lt;p&gt;This is what "deterministic" means in practice. Not that the LLM produces identical tokens every time. It means that given the same type of query, the system follows the same learned retrieval pattern, produces the same quality of context, and arrives at the same caliber of decision. The variability gets squeezed out of the data layer, which is where it was causing problems in the first place. The model can still be creative where creativity helps. But the retrieval is locked down.&lt;/p&gt;

&lt;h3&gt;
  
  
  The full retrieval funnel
&lt;/h3&gt;

&lt;p&gt;Putting it all together, here is the complete pipeline from raw candidates to agent context, with graphs feeding back into pipeline planning. &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%2Feuk9mceyalc1m8w2nu4q.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%2Feuk9mceyalc1m8w2nu4q.png" alt="The full retrieval funnel" width="800" height="1099"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the agent acts, the decided, evaluated, and retrieval edges are written back, feeding into the next cycle of pipeline planning. The funnel self-tightens with every run.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why the graphs break in a fragmented stack
&lt;/h3&gt;

&lt;p&gt;There is one more thing worth making concrete, because it is easy to nod along with "you need one database" without feeling why.&lt;/p&gt;

&lt;p&gt;Say your reasoning and retrieval edges write to Postgres, your vector embeddings live in Pinecone, and your graph relationships are in Neo4j. The pipeline planner needs to query: "for this query type, which graph paths and filter combinations correlated with correct decisions in the last 200 runs?" That query needs to join reasoning edges and retrieval edges in Postgres with graph topology in Neo4j and vector metadata in Pinecone. What transaction boundary covers all three systems? There is not one. So the planner is reading behavioral data that may or may not reflect what actually happened, referencing graph paths that may have changed since the edges were written, and correlating against outcomes that landed in a different system at a different time. The feedback loop is broken before it starts. You are building a learning system on top of eventually consistent quicksand.&lt;/p&gt;

&lt;p&gt;That is why this architecture needs a multi-model graph database. Not because fewer databases is aesthetically nicer, but because the reasoning and retrieval edges need to live in the same graph as the entities they reference. The feedback loop is a graph traversal: walk from an agent through its decided edges, through its evaluated edges to each piece of evidence, follow the retrieval edges, compare outcomes. That only works when the reasoning and retrieval edges, the documents, the graph relationships, the vector embeddings, and the agent state all live in the same engine with the same transactional guarantees. SurrealDB handles all of this natively. This is the exact problem we designed it to solve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Supporting research
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Cognitive Architectures for Language Agents (CoALA)&lt;/strong&gt; — Sumers, Yao, Narasimhan, Griffiths · 2024. Formalizes agent memory into episodic, semantic, and procedural types coordinated through a single decision loop. Reasoning and retrieval graphs map directly to CoALA's episodic memory: structured records of past actions that inform future decisions. The framework makes clear that scattering memory types across separate systems works against reliable agent behavior.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;From Local to Global: A Graph RAG Approach&lt;/strong&gt; — Edge, Trinh, Larson, Truitt (Microsoft Research) · 2024. Demonstrated that combining knowledge graphs with retrieval-augmented generation significantly outperforms vector-only retrieval for complex queries requiring multi-hop reasoning and structured context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retrieval-Augmented Generation: A Survey&lt;/strong&gt; — Gao, Xiong, Gao et al. · 2023. Identifies consistent failure modes in RAG at scale: top-k instability, chunk blending, precision degradation. Recommends constraining the candidate set before similarity search using structural filters and graph traversal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Graph Retrieval-Augmented Generation: A Survey&lt;/strong&gt; — Peng, Zhu, Liu et al. · 2024. First comprehensive overview of GraphRAG, formalizing how graph-based indexing, retrieval, and generation improve accuracy and context-awareness over similarity-only approaches.&lt;/p&gt;

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

&lt;p&gt;Near-perfect agent accuracy comes from two things working together. A retrieval pipeline that scopes candidates using structural filters, temporal constraints, graph traversal, and business rules before similarity ranking, all within a single ACID transaction. That gets you to roughly 90–95% on large corpora by eliminating the consistency gaps and retrieval noise that cause most agent failures.&lt;/p&gt;

&lt;p&gt;And reasoning and retrieval graphs stored as edges in the same transaction as the agent's action, feeding back into the pipeline so the funnel tightens with every run. That is what closes the gap to 99%+.&lt;/p&gt;

&lt;p&gt;Neither works if your data models are split across separate systems. The scope query cannot compose and the feedback loop breaks unless everything shares one transaction boundary. That is why we built SurrealDB.&lt;/p&gt;

&lt;p&gt;On the first run, 2.8 million candidates narrowed to 14. A few hundred runs later, it narrows to 9, then 6, each time more precise, each time learning from the last. That is what deterministic accuracy looks like in practice: agents that do not just perform well on average, but get better with every decision they make.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Sumers, T.R., Yao, S., Narasimhan, K., &amp;amp; Griffiths, T.L. (2024). Cognitive Architectures for Language Agents. &lt;em&gt;Transactions on Machine Learning Research&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Edge, D., Trinh, H., Larson, J., &amp;amp; Truitt, S. (2024). From Local to Global: A Graph RAG Approach to Query-Focused Summarization. &lt;em&gt;arXiv:2404.16130&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Gao, Y., Xiong, Y., Gao, X., et al. (2023). Retrieval-Augmented Generation for Large Language Models: A Survey. &lt;em&gt;arXiv:2312.10997&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Peng, B., Zhu, Y., Liu, Y., et al. (2024). Graph Retrieval-Augmented Generation: A Survey. &lt;em&gt;arXiv:2408.08921&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>surrealdb</category>
      <category>agents</category>
      <category>database</category>
    </item>
  </channel>
</rss>
