<?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: Dev Kansal</title>
    <description>The latest articles on DEV Community by Dev Kansal (@thedevkansal).</description>
    <link>https://dev.to/thedevkansal</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%2F4192684%2F77af04cf-c6c8-48e3-9762-5f74cd2454dc.jpg</url>
      <title>DEV Community: Dev Kansal</title>
      <link>https://dev.to/thedevkansal</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/thedevkansal"/>
    <language>en</language>
    <item>
      <title>I built a town that gossips: graph memory for multi-agent systems with FalkorDB</title>
      <dc:creator>Dev Kansal</dc:creator>
      <pubDate>Sun, 18 Oct 2026 18:29:10 +0000</pubDate>
      <link>https://dev.to/thedevkansal/i-built-a-town-that-gossips-graph-memory-for-multi-agent-systems-with-falkordb-nkf</link>
      <guid>https://dev.to/thedevkansal/i-built-a-town-that-gossips-graph-memory-for-multi-agent-systems-with-falkordb-nkf</guid>
      <description>&lt;h1&gt;
  
  
  I built a town that gossips: graph memory for multi-agent systems with FalkorDB
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;Built for Graph Hacks: Context for AI Agents (WeMakeDevs x FalkorDB).&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://living-town.onrender.com" rel="noopener noreferrer"&gt;https://living-town.onrender.com&lt;/a&gt; · &lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/thedevkansal/living-town" rel="noopener noreferrer"&gt;https://github.com/thedevkansal/living-town&lt;/a&gt;&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%2Fennwzez6ynkx3i2w2s1a.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%2Fennwzez6ynkx3i2w2s1a.png" alt="Captain Oswin explains how he knows, with the provenance thread drawn across the map" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The question that started it
&lt;/h2&gt;

&lt;p&gt;Most "agent memory" today is a private notebook: one agent, one store, a pile of embedded snippets. That works until you have more than one agent. The moment agents talk to each other, the interesting questions are no longer &lt;em&gt;what&lt;/em&gt; an agent knows, but &lt;strong&gt;how it came to know it&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who told you that?&lt;/li&gt;
&lt;li&gt;Who told &lt;em&gt;them&lt;/em&gt;?&lt;/li&gt;
&lt;li&gt;Did anyone actually see it happen, or is this the fifth retelling of a rumour?&lt;/li&gt;
&lt;li&gt;If two agents disagree, whose version wins?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every one of those is a question about relationships, and relationships are exactly what a vector store throws away. So for this hackathon I built a small world where those questions are the whole game.&lt;/p&gt;
&lt;h2&gt;
  
  
  Living Town in 30 seconds
&lt;/h2&gt;

&lt;p&gt;Thornbury has ten NPC agents: a blacksmith, a tavern keeper, a guard captain, a deputy, a priest and others. Each one's memory is a neighbourhood in a FalkorDB graph.&lt;/p&gt;

&lt;p&gt;You walk into the Forge and steal Bram's silver hammer. Only Bram sees it. Then you let the day play out:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;At midday Bram tells Mira at the tavern.&lt;/li&gt;
&lt;li&gt;In the evening Mira tells Captain Oswin. Oswin never saw anything.&lt;/li&gt;
&lt;li&gt;Oswin posts an investigation on a shared task board. Finn, the deputy, claims it: the database only lets an agent claim a task if it &lt;code&gt;KNOWS_HOW&lt;/code&gt; to do it.&lt;/li&gt;
&lt;li&gt;Finn traces the story back to its eyewitness and takes Bram's testimony.&lt;/li&gt;
&lt;li&gt;At dawn Oswin walks up to you: &lt;em&gt;"You there! Mira told me, and it traces back to Bram, who saw it."&lt;/em&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ask Oswin &lt;em&gt;"How do you know?"&lt;/em&gt; and the answer is not generated text. It is the path the query returned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ada -[stole]-&amp;gt; (theft) &amp;lt;-[WITNESSED]- Bram -[TOLD]-&amp;gt; Mira -[TOLD]-&amp;gt; Captain Oswin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ask Sister Agnes the same question and she says &lt;em&gt;"Silver hammer? Nothing in my memory connects to that."&lt;/em&gt; No path, no answer. I like that more than any confident hallucination.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three kinds of memory, one graph
&lt;/h2&gt;

&lt;p&gt;The track asked for episodic, semantic and procedural memory. In a graph they stop being three systems and become three edge types:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Memory&lt;/th&gt;
&lt;th&gt;Shape in the graph&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Episodic: what I saw&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(:NPC)-[:WITNESSED]-&amp;gt;(:Event)-[:AT]-&amp;gt;(:Location)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Semantic: what I believe, and how sure&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(:NPC)-[:BELIEVES {confidence, stance, hop, via}]-&amp;gt;(:Fact)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Procedural: what I know how to do&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(:NPC)-[:KNOWS_HOW {proficiency, learned_from}]-&amp;gt;(:Procedure)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two more pieces make it &lt;em&gt;social&lt;/em&gt; memory:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Provenance.&lt;/strong&gt; Every time a story moves, a &lt;code&gt;TOLD&lt;/code&gt; edge is written between the two agents. Each believer has exactly one &lt;em&gt;primary&lt;/em&gt; incoming &lt;code&gt;TOLD&lt;/code&gt; edge per fact, so the chain back to the witness is a tree you can walk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Social structure.&lt;/strong&gt; &lt;code&gt;(:NPC)-[:KNOWS {trust, affinity}]-&amp;gt;(:NPC)&lt;/code&gt;. Bram dislikes the captain (affinity 0.1) and never talks to him, so the story &lt;em&gt;has&lt;/em&gt; to route through Mira. That routing does not exist anywhere except in the graph.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The rules of gossip
&lt;/h2&gt;

&lt;p&gt;The engine is small and deliberately boring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An eyewitness believes with confidence 1.0.&lt;/li&gt;
&lt;li&gt;A listener's confidence is &lt;code&gt;speaker confidence × listener's trust in the speaker × 0.9&lt;/code&gt;. Below 0.3 the story is &lt;em&gt;doubted&lt;/em&gt;, not accepted.&lt;/li&gt;
&lt;li&gt;Hearing the same story again from someone else corroborates it instead of creating a second chain.&lt;/li&gt;
&lt;li&gt;Every dawn, hearsay fades by 7%; eyewitness memory keeps its confidence but loses vividness.&lt;/li&gt;
&lt;li&gt;Lies are just facts that contradict other facts. If you tell Mira that Pip stole the hammer and she already believes otherwise with more confidence, she catches the lie, and &lt;em&gt;that&lt;/em&gt; becomes a new story about you.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Cypher that does the work
&lt;/h2&gt;

&lt;p&gt;The agents never write Cypher. Every query is hand-written and parameterised (108 of them: 50 reads, 50 writes, 8 graph-algorithm calls), and agents, an LLM, or an MCP client only choose which query to run and with what arguments. Reads go through &lt;code&gt;GRAPH.RO_QUERY&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The gossip handoff is one atomic statement:&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;NPC&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;$from&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;:BELIEVES&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;f:&lt;/span&gt;&lt;span class="n"&gt;Fact&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;$fid&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;NPC&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;$to&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;b&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;bel:&lt;/span&gt;&lt;span class="n"&gt;BELIEVES&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;f&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;bel.confidence&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;$conf&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;bel.stance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;$conf&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;$accept&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'accepted'&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="s1"&gt;'doubted'&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;bel.hop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;$hop&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bel.via&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;$from&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;bel.corroborations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;coalesce&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bel.corroborations&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&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;t:&lt;/span&gt;&lt;span class="n"&gt;TOLD&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;fact_id:&lt;/span&gt;&lt;span class="n"&gt;$fid&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"How do you know?" is a variable-length path over primary &lt;code&gt;TOLD&lt;/code&gt; edges:&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;me:&lt;/span&gt;&lt;span class="n"&gt;NPC&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;$npc&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;b:&lt;/span&gt;&lt;span class="n"&gt;BELIEVES&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;f:&lt;/span&gt;&lt;span class="n"&gt;Fact&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;$fid&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="n"&gt;chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&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;:TOLD&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;fact_id:&lt;/span&gt;&lt;span class="n"&gt;$fid&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="py"&gt;primary:&lt;/span&gt;&lt;span class="k"&gt;true&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;me&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;me&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="n"&gt;f&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&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;chain&lt;/span&gt;&lt;span class="ss"&gt;)&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;1&lt;/span&gt;
&lt;span class="n"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My first version filtered the edges &lt;em&gt;after&lt;/em&gt; expanding the path (&lt;code&gt;WHERE all(r IN relationships(chain) ...)&lt;/code&gt;). That enumerated every gossip path in town before throwing most of them away, and after about five simulated days the query timed out. Putting the filter on the relationship pattern itself makes FalkorDB prune while it expands: 4-9 ms, flat, no matter how much the town gossips.&lt;/p&gt;

&lt;p&gt;And the task board settles races in the database, not in Python. Only one agent can claim a task, and only if it actually knows the procedure:&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;t:&lt;/span&gt;&lt;span class="n"&gt;Task&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;$tid&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;t.status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'open'&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;n:&lt;/span&gt;&lt;span class="n"&gt;NPC&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;$npc&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;:KNOWS_HOW&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;p:&lt;/span&gt;&lt;span class="n"&gt;Procedure&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;p.id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t.required&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;t.status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'claimed'&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t.claimed_by&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;$npc&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&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;:CLAIMED_BY&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;n&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Graph algorithms as town insights
&lt;/h2&gt;

&lt;p&gt;FalkorDB ships graph algorithms as procedures, and each one answers a question about the town:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;algo.labelPropagation&lt;/code&gt; over close friendships finds the cliques, where gossip moves fastest.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;algo.betweenness&lt;/code&gt; over &lt;code&gt;KNOWS&lt;/code&gt; finds the gossip broker (it is always Mira, the tavern keeper).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;algo.pageRank&lt;/code&gt; over &lt;code&gt;TOLD&lt;/code&gt; finds who hears the most stories.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;algo.SSpaths&lt;/code&gt; over social distance forecasts who is likely to hear a story next, drawn as dashed rings on the map.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;algo.WCC&lt;/code&gt; and &lt;code&gt;algo.HarmonicCentrality&lt;/code&gt; find rumour islands and who can spread news fastest.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  One graph per player
&lt;/h2&gt;

&lt;p&gt;Tenancy was the easiest part, and the one I was most happy with. FalkorDB holds many independent graphs in one instance, so every player gets their own: &lt;code&gt;town:ada&lt;/code&gt;, &lt;code&gt;town:bo&lt;/code&gt;, and so on. There is no &lt;code&gt;WHERE tenant_id = ...&lt;/code&gt; anywhere. In the demo, two players side by side live in two different graphs; a crime in one never leaks into the other. Come back under the same name in a new session and the town greets you from what it remembers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug that taught me the most: round trips
&lt;/h2&gt;

&lt;p&gt;Locally a simulation tick (all ten agents perceiving, gossiping, claiming tasks) took about 100 ms. Deployed, with the app in Singapore and FalkorDB Cloud in Mumbai, the same tick took &lt;strong&gt;2.8 seconds&lt;/strong&gt;. Nothing was slow on its own; every query was simply a cross-region round trip, and a tick made about 25 of them one after another.&lt;/p&gt;

&lt;p&gt;The fix was to stop thinking in queries and start thinking in round trips:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Independent per-agent reads and writes run concurrently with &lt;code&gt;asyncio.gather&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Small queries were merged; writes were batched with &lt;code&gt;UNWIND&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The whole town is built in a single statement.&lt;/li&gt;
&lt;li&gt;A warm connection pool is opened at startup, because opening each new connection to the cloud instance cost about 350 ms.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A tick is now about six sequential round trips: &lt;strong&gt;260 to 400 ms live&lt;/strong&gt; (down from 2.8 s), and creating a new world dropped from ~15 s to ~1.4 s. A whole simulated day plays out in about ten seconds on the live site.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agents, LLMs and MCP
&lt;/h2&gt;

&lt;p&gt;By default the agents are rule-based, and that is on purpose: the memory, gossip and coordination are the product, and they behave the same with or without a model. With an Anthropic API key, NPC dialogue is generated by Claude using tool calls over the same fixed memory catalogue, and every person, item or place the model mentions is checked against that turn's tool results; if it names something the graph never returned, the reply is thrown away and a memory-grounded template is shown instead.&lt;/p&gt;

&lt;p&gt;The same tools are exposed over MCP, so any MCP client (Claude Code, Claude Desktop, ...) can open a town and ask &lt;em&gt;"In player ada's town, how does Captain Oswin know about the hammer?"&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd do next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Let agents &lt;em&gt;forget&lt;/em&gt; deliberately: compress old episodic memories into semantic summaries, keeping the provenance edges.&lt;/li&gt;
&lt;li&gt;Reputation that spreads: right now each NPC's opinion of you is derived from their own beliefs; I'd like opinions to be gossip too.&lt;/li&gt;
&lt;li&gt;Use the same engine outside games: support agents that share what they learned about a customer, with an auditable trail of who learned what from whom.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Live demo: &lt;a href="https://living-town.onrender.com" rel="noopener noreferrer"&gt;https://living-town.onrender.com&lt;/a&gt; (free tier, so the first load may take a minute)&lt;/li&gt;
&lt;li&gt;Code, data model and every Cypher query: &lt;a href="https://github.com/thedevkansal/living-town" rel="noopener noreferrer"&gt;https://github.com/thedevkansal/living-town&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Built with Claude Code as a coding assistant, as disclosed in the repo.&lt;/em&gt;&lt;/p&gt;

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