<?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: Manoj Kumar Gogula</title>
    <description>The latest articles on DEV Community by Manoj Kumar Gogula (@manoj_kumargogula_4042c7).</description>
    <link>https://dev.to/manoj_kumargogula_4042c7</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%2F4120440%2Facfdba5e-b30c-4158-a387-19f4906b459b.png</url>
      <title>DEV Community: Manoj Kumar Gogula</title>
      <link>https://dev.to/manoj_kumargogula_4042c7</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/manoj_kumargogula_4042c7"/>
    <language>en</language>
    <item>
      <title>Five Cypher patterns I keep reaching for when building agent memory</title>
      <dc:creator>Manoj Kumar Gogula</dc:creator>
      <pubDate>Fri, 11 Sep 2026 09:30:08 +0000</pubDate>
      <link>https://dev.to/manoj_kumargogula_4042c7/five-cypher-patterns-i-keep-reaching-for-when-building-agent-memory-26gp</link>
      <guid>https://dev.to/manoj_kumargogula_4042c7/five-cypher-patterns-i-keep-reaching-for-when-building-agent-memory-26gp</guid>
      <description>&lt;p&gt;Cypher is easy to read and surprisingly easy to write badly. The syntax is so close to plain English that your first few queries work, and then one of them quietly returns 40,000 rows because you matched two unrelated patterns in the same clause.&lt;/p&gt;

&lt;p&gt;These are the five patterns I keep coming back to when the graph is backing an agent's memory. Nothing exotic — just the ones that saved me the most time. All of them run on any Bolt/Cypher endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. MERGE for writes you'll run more than once
&lt;/h2&gt;

&lt;p&gt;Agents re-observe the same facts constantly. The same entity gets extracted from three different documents, the same relationship gets inferred twice. If you &lt;code&gt;CREATE&lt;/code&gt;, you get duplicates; if you check-then-insert, you get a race.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;MERGE&lt;/code&gt; matches or creates in one atomic step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MERGE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;p:&lt;/span&gt;&lt;span class="n"&gt;Person&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;id:&lt;/span&gt; &lt;span class="n"&gt;$person_id&lt;/span&gt;&lt;span class="ss"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;p.name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;$name&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p.first_seen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;timestamp&lt;/span&gt;&lt;span class="ss"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;MATCH&lt;/span&gt;  &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;p.last_seen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;timestamp&lt;/span&gt;&lt;span class="ss"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important and frequently-missed detail: &lt;code&gt;MERGE&lt;/code&gt; matches on the &lt;em&gt;entire&lt;/em&gt; pattern you give it. &lt;code&gt;MERGE (p:Person {id: $id, name: $name})&lt;/code&gt; will create a second node when the name changes, because the pattern no longer matches. Merge on the identifying property only, then &lt;code&gt;SET&lt;/code&gt; the rest. And put a uniqueness constraint on that property before you rely on any of this — without one, concurrent merges can still both create.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Bounded variable-length paths
&lt;/h2&gt;

&lt;p&gt;"What does this depend on, transitively?" is one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;s:&lt;/span&gt;&lt;span class="n"&gt;Service&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;name:&lt;/span&gt; &lt;span class="n"&gt;$name&lt;/span&gt;&lt;span class="ss"&gt;})&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:DEPENDS_ON&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dep&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;dep.name&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;length&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="ss"&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;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;distance&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the upper bound. Writing &lt;code&gt;*&lt;/code&gt; or &lt;code&gt;*1..&lt;/code&gt; unbounded on a well-connected graph is how you hang a query — in a social-ish graph almost everything is reachable within six hops, so an unbounded traversal degrades into "scan the graph." Pick a real depth. If you don't know it, start at 3 and measure.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;DISTINCT&lt;/code&gt; matters too: multiple paths reach the same node, and without it you'll return the same dependency once per route.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. OPTIONAL MATCH instead of a second round trip
&lt;/h2&gt;

&lt;p&gt;The reflex from SQL is a &lt;code&gt;LEFT JOIN&lt;/code&gt;; the reflex from ORMs is a second query. Cypher lets you attach the optional part inline, so a node with no attachments still comes back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;d:&lt;/span&gt;&lt;span class="n"&gt;Document&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;id:&lt;/span&gt; &lt;span class="n"&gt;$doc_id&lt;/span&gt;&lt;span class="ss"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;OPTIONAL&lt;/span&gt; &lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:AUTHORED&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;author:&lt;/span&gt;&lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;OPTIONAL&lt;/span&gt; &lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:CITES&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;cited:&lt;/span&gt;&lt;span class="n"&gt;Document&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;d.title&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt;
       &lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;author.name&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;authors&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt;
       &lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;cited.title&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;citations&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A plain &lt;code&gt;MATCH&lt;/code&gt; here would drop the document entirely if it happened to have no citations, which is the kind of bug that only shows up on the one record your demo uses.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The neighbourhood fetch
&lt;/h2&gt;

&lt;p&gt;This is the query that actually feeds the model. Resolve the entities in the question, walk out a fixed radius, return the subgraph — and nothing else:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;seed.id&lt;/span&gt; &lt;span class="ow"&gt;IN&lt;/span&gt; &lt;span class="n"&gt;$seed_ids&lt;/span&gt;
&lt;span class="k"&gt;MATCH&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;neighbour&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;neighbour&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt;
     &lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="nf"&gt;relationships&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="ss"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;rels&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rels&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The discipline here is the radius, not the syntax. Two hops is usually plenty; three often doubles the context for very little extra signal. Whatever you pick, cap the result size explicitly — one unexpectedly popular hub node can drag half the graph into your prompt, and you'd rather truncate deliberately than discover it in a token bill.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Provenance as an edge property
&lt;/h2&gt;

&lt;p&gt;If an agent is going to act on a fact, you need to know where the fact came from. Put it on the relationship when you write it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;a:&lt;/span&gt;&lt;span class="n"&gt;Person&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;id:&lt;/span&gt; &lt;span class="n"&gt;$a&lt;/span&gt;&lt;span class="ss"&gt;}),&lt;/span&gt; &lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;b:&lt;/span&gt;&lt;span class="n"&gt;Company&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;id:&lt;/span&gt; &lt;span class="n"&gt;$b&lt;/span&gt;&lt;span class="ss"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;MERGE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="py"&gt;r:&lt;/span&gt;&lt;span class="n"&gt;WORKS_AT&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;r.source&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;$source_doc&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;r.method&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;$extraction_method&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;r.confidence&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;$confidence&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;r.asserted_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;timestamp&lt;/span&gt;&lt;span class="ss"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then a retrieval query can return the path &lt;em&gt;and&lt;/em&gt; its justification together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;a:&lt;/span&gt;&lt;span class="n"&gt;Person&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;id:&lt;/span&gt; &lt;span class="n"&gt;$a&lt;/span&gt;&lt;span class="ss"&gt;})&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:WORKS_AT&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;c:&lt;/span&gt;&lt;span class="n"&gt;Company&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;c.name&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt;
       &lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt; &lt;span class="ow"&gt;IN&lt;/span&gt; &lt;span class="nf"&gt;relationships&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;rel.source&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;sources&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No separate audit table, no reconstructing the reasoning by hand. "Why do you believe this?" becomes a column.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mistake behind most slow queries
&lt;/h2&gt;

&lt;p&gt;Worth stating plainly, because it caught me more than once: two unconnected patterns in the same &lt;code&gt;MATCH&lt;/code&gt; produce a cartesian product.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="py"&gt;accidental:&lt;/span&gt; &lt;span class="n"&gt;every&lt;/span&gt; &lt;span class="n"&gt;Person&lt;/span&gt; &lt;span class="n"&gt;paired&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;every&lt;/span&gt; &lt;span class="n"&gt;Company&lt;/span&gt;
&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;p:&lt;/span&gt;&lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="ss"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;c:&lt;/span&gt;&lt;span class="n"&gt;Company&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's every Person times every Company. Either connect the patterns with a relationship, or split them across &lt;code&gt;WITH&lt;/code&gt; boundaries. Most engines will warn you; read the warning.&lt;/p&gt;

&lt;p&gt;And always run &lt;code&gt;EXPLAIN&lt;/code&gt; or &lt;code&gt;PROFILE&lt;/code&gt; before you blame the database. &lt;code&gt;PROFILE&lt;/code&gt; shows rows per operator, and the culprit is usually an unindexed label scan sitting at the bottom.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trying these somewhere
&lt;/h2&gt;

&lt;p&gt;All of the above is standard Cypher, so it runs against any Bolt endpoint. I've been using &lt;a href="https://cognodb.com" rel="noopener noreferrer"&gt;CognoDB&lt;/a&gt; for prototypes because the official Neo4j drivers connect unchanged (Bolt 5.0–5.4, one-line URI change) and the free tier comes up in about a minute with no card — which is the difference between trying a graph idea on a Tuesday evening and not trying it.&lt;/p&gt;

&lt;p&gt;Full disclosure: I work on CognoDB. The patterns aren't specific to it, and I'd rather you learn Cypher on whatever endpoint is closest to hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  What did I miss?
&lt;/h2&gt;

&lt;p&gt;I'm genuinely curious about the ones I haven't internalised yet — especially around modelling time-varying relationships and pruning stale agent memory, which I still don't have a clean pattern for. If you've solved either, I'd like to read it.&lt;/p&gt;

&lt;p&gt;More in this series under #cognodb.&lt;/p&gt;

</description>
      <category>cognodb</category>
      <category>cypher</category>
      <category>graphdatabase</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>When an agent's question is a path, not a similarity score</title>
      <dc:creator>Manoj Kumar Gogula</dc:creator>
      <pubDate>Fri, 11 Sep 2026 09:23:40 +0000</pubDate>
      <link>https://dev.to/manoj_kumargogula_4042c7/when-an-agents-question-is-a-path-not-a-similarity-score-27m9</link>
      <guid>https://dev.to/manoj_kumargogula_4042c7/when-an-agents-question-is-a-path-not-a-similarity-score-27m9</guid>
      <description>&lt;p&gt;Most retrieval stacks start with a vector index, and for good reason: "find me things that look like this" is the single most common question an agent asks. But a meaningful slice of agent questions aren't about similarity at all. They're about connection — what depends on this, who touched it, how did we get from A to B. Cosine distance has no concept of an edge, so it cannot answer those. A relational store can, but you pay for it with a join per relationship type and a recursive CTE tuned per query.&lt;/p&gt;

&lt;p&gt;This post is about that second class of question, and what changes when you model it as a graph.&lt;/p&gt;

&lt;h2&gt;
  
  
  Depth as a parameter
&lt;/h2&gt;

&lt;p&gt;Suppose an agent has to answer "what depends on service X?" You don't know the depth in advance. It might be two hops today and five after the next deploy. In Cypher the depth is just part of the pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;s:&lt;/span&gt;&lt;span class="n"&gt;Service&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;name:&lt;/span&gt; &lt;span class="n"&gt;$name&lt;/span&gt;&lt;span class="ss"&gt;})&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:DEPENDS_ON&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dependent&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;dependent.name&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;length&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;hops&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;hops&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same question in SQL means a recursive CTE, a termination condition you have to get right, and a rewrite every time someone adds a new kind of relationship. The graph version doesn't change shape when the schema grows — typed edges &lt;em&gt;are&lt;/em&gt; the data model, so a new relationship type is data, not a migration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retrieval bounded by neighbourhood, not by corpus
&lt;/h2&gt;

&lt;p&gt;The usual RAG failure mode is stuffing everything plausibly relevant into the prompt and hoping the model sorts it out. Graph retrieval inverts that: resolve the entities in the question, walk out a couple of hops, and send only that.&lt;/p&gt;

&lt;p&gt;The difference is easy to underestimate. On a 3,700-entity knowledge base, dumping every node and edge rendered to 202,285 tokens per query; fetching the two-hop neighbourhood around the query entities rendered to 2,668. Same text, same tokenizer (GPT-4 &lt;code&gt;cl100k_base&lt;/code&gt;). The gap widens with scale, and that's the actual point — baseline tokens grow with the corpus, while neighbourhood retrieval stays bounded by the neighbourhood.&lt;/p&gt;

&lt;h2&gt;
  
  
  The path is the explanation
&lt;/h2&gt;

&lt;p&gt;The part I find most underrated: when you traverse for the answer, you get the justification in the same result set. A vector hit gives you a score and no account of itself. A graph query returns the route it took, and provenance can live on the edge as a property, so "why do you believe this?" is answerable without a separate audit table.&lt;/p&gt;

&lt;p&gt;That matters a lot once an agent is allowed to &lt;em&gt;act&lt;/em&gt; on what it retrieved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this doesn't apply
&lt;/h2&gt;

&lt;p&gt;It's not graph versus vectors. Similarity search is still the right tool for fuzzy recall over unstructured text, and most real systems end up using both: embeddings to find the entry points, traversal to assemble grounded context around them. If your data has no meaningful relationships, a graph buys you nothing but a new thing to operate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trying it without standing up a cluster
&lt;/h2&gt;

&lt;p&gt;This is the part that used to stop me from prototyping graph ideas. &lt;a href="https://cognodb.com" rel="noopener noreferrer"&gt;CognoDB&lt;/a&gt; is a managed graph database that speaks Bolt 5.0–5.4 and Cypher, so the official Neo4j drivers connect unchanged — migration is a one-line URI change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;neo4j&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;GraphDatabase&lt;/span&gt;

&lt;span class="n"&gt;driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GraphDatabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bolt+s://db-7f3a2c1e.databases.cognodb.cloud&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cognodb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DB_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;session&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;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MATCH (p:Person)-[:FOLLOWS]-&amp;gt;(f) &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WHERE p.name = $name RETURN f.name AS name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ada&lt;/span&gt;&lt;span class="sh"&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;for&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's a free tier with no card, plus a built-in MCP server if you want an agent querying the graph directly. Published benchmark numbers (0.27 ms two-hop p95, ~74K read queries/s, ~80 bytes per edge on disk) come with full methodology on the site rather than as round marketing figures — worth reading the footnotes before you trust any vendor's graph benchmark, including ours.&lt;/p&gt;

&lt;p&gt;If you're new to this, the &lt;a href="https://cognodb.com" rel="noopener noreferrer"&gt;concepts explainers&lt;/a&gt; on graphs, Cypher and GraphRAG are short and run on the free tier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Over to you
&lt;/h2&gt;

&lt;p&gt;What are you using for agent memory right now — vectors, a relational store, something hand-rolled? And if you've tried traversal-based retrieval, did the token savings hold up in production or did prompt-stuffing creep back in? I'd genuinely like to hear where this breaks.&lt;/p&gt;

&lt;p&gt;We'll be posting more under #cognodb: Cypher patterns, GraphRAG walkthroughs, and honest benchmark write-ups. Questions welcome in the comments.&lt;/p&gt;

</description>
      <category>cognodb</category>
      <category>graphdatabase</category>
      <category>ai</category>
      <category>database</category>
    </item>
  </channel>
</rss>
