<?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: Dhanalakshmi.d.gowda23</title>
    <description>The latest articles on DEV Community by Dhanalakshmi.d.gowda23 (@dhanalakshmidgowda23_bf).</description>
    <link>https://dev.to/dhanalakshmidgowda23_bf</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%2F3690038%2Fd42c1ba8-abd8-4b9c-ba61-91aced2fbd1d.png</url>
      <title>DEV Community: Dhanalakshmi.d.gowda23</title>
      <link>https://dev.to/dhanalakshmidgowda23_bf</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dhanalakshmidgowda23_bf"/>
    <language>en</language>
    <item>
      <title>What happens when a kidney swap breaks? Building an exchange agent on FalkorDB</title>
      <dc:creator>Dhanalakshmi.d.gowda23</dc:creator>
      <pubDate>Sun, 18 Oct 2026 17:30:02 +0000</pubDate>
      <link>https://dev.to/dhanalakshmidgowda23_bf/what-happens-when-a-kidney-swap-breaks-building-an-exchange-agent-on-falkordb-1noa</link>
      <guid>https://dev.to/dhanalakshmidgowda23_bf/what-happens-when-a-kidney-swap-breaks-building-an-exchange-agent-on-falkordb-1noa</guid>
      <description>&lt;p&gt;More than 90,000 people in the US are waiting for a kidney. Some of them already have a donor: a partner, a sibling, a friend who would give a kidney tomorrow. The problem is that the donor doesn't match. Wrong blood type, or the patient's immune system would reject that particular kidney.&lt;/p&gt;

&lt;p&gt;Kidney paired exchange fixes this by letting pairs swap. My donor gives to you, your donor gives to someone else, and their donor gives back to me. Everyone in the ring gets a kidney that fits.&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%2Fym7sxue104c7dcml3co7.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%2Fym7sxue104c7dcml3co7.png" alt="Three donor-patient pairs swapping donors in a ring" width="799" height="333"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Three pairs in a ring: each donor gives to the next patient.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For Graph Hacks 2026 (WeMakeDevs × FalkorDB), we built &lt;strong&gt;Swap&lt;/strong&gt;, an agent that runs one of these exchanges on a FalkorDB graph. This post is about what we built, the mistake we made halfway through, and what we learned about FalkorDB along the way.&lt;/p&gt;

&lt;p&gt;Everything in Swap uses synthetic patients. It's a demo of a workflow, not a clinical tool.&lt;/p&gt;
&lt;h2&gt;
  
  
  The easy part: finding rings
&lt;/h2&gt;

&lt;p&gt;A kidney exchange is a graph almost by definition. Each pair is a node. If pair A's donor can give to pair B's patient, there's an edge A → B. A swap is a cycle.&lt;/p&gt;

&lt;p&gt;Finding every 3-way swap is one Cypher 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="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;Pair&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;:CAN_GIVE&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;b:&lt;/span&gt;&lt;span class="n"&gt;Pair&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;:CAN_GIVE&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;Pair&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;:CAN_GIVE&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;a&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;a.id&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;b.id&lt;/span&gt; &lt;span class="ow"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;a.id&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;c.id&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;a.id&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b.id&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c.id&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;a.id &amp;lt; b.id AND a.id &amp;lt; c.id&lt;/code&gt; part makes sure each ring is counted once instead of three times.&lt;/p&gt;

&lt;p&gt;On a pool of 200 synthetic pairs, allowing only 2-way swaps matches 88 patients. Allowing 3-way rings matches 106. That's 18 more people from the same donors. This isn't our discovery: Roth, Sönmez and Ünver showed it in 2007, and we reproduce their result. Our generator follows their blood-type and immune-sensitivity mix, and &lt;code&gt;make verify SEEDS=20&lt;/code&gt; recomputes our numbers over 20 random pools.&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%2Fcxto6ze0aa1292jj9107.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%2Fcxto6ze0aa1292jj9107.png" alt="88 patients matched with 2-way swaps, 106 with up to 3-way" width="800" height="133"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Same 200 synthetic pairs: 88 matched with 2-way swaps only, 106 once 3-way rings are allowed.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The hard part: plans fall apart
&lt;/h2&gt;

&lt;p&gt;Here's what got us interested. Research on US exchanges (Dickerson, Procaccia and Sandholm, &lt;em&gt;Failure-Aware Kidney Exchange&lt;/em&gt;) found that most planned exchange transplants never actually happen. A common reason is the crossmatch, a lab test run shortly before surgery. If it comes back positive, that donor can't give to that patient, and the whole ring they were part of breaks.&lt;/p&gt;

&lt;p&gt;So the question we wanted to answer wasn't just "what is the best plan?" It was "what do you do when the plan breaks?"&lt;/p&gt;
&lt;h2&gt;
  
  
  Our first version used the graph wrong
&lt;/h2&gt;

&lt;p&gt;Our first working version did this: FalkorDB found all the cycles, we pulled them into Python, an integer program (scipy's &lt;code&gt;milp&lt;/code&gt;) picked the best non-overlapping set, and that was it. The graph was basically a fancy cycle finder.&lt;/p&gt;

&lt;p&gt;When we stepped back, we didn't like it. Take FalkorDB out and most of the product still worked. So we rebuilt it around one idea: &lt;strong&gt;the graph should hold the state of the exchange&lt;/strong&gt;, not just the input data.&lt;/p&gt;
&lt;h2&gt;
  
  
  How it works now
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Every swap is a node
&lt;/h3&gt;

&lt;p&gt;When a pool loads, Cypher stores every possible 2- and 3-way swap as its own node, linked to the pairs in 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="ss"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;:Cycle&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;members&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hard&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;back_arcs&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;live&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;taken&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;:HAS&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pos&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="nc"&gt;:Pair&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For 300 pairs that's about 65,000 swaps, created in around 0.6 seconds. Each swap also stores two things we use later: how many hard-to-match patients it helps, and its number of "back-arcs" (more on those below).&lt;/p&gt;

&lt;h3&gt;
  
  
  A plan is guarded and then approved by a person
&lt;/h3&gt;

&lt;p&gt;The agent drafts a plan. The integer program still does the choosing, but it now only chooses among swaps the graph says are alive and not already taken. Before anything is saved, a couple of Cypher queries check the plan: every donor-to-patient link still exists, and no pair appears twice.&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%2Fzidrfnykq5ge49mq1m4a.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%2Fzidrfnykq5ge49mq1m4a.png" alt="FalkorDB's check of a drafted plan and the queries it ran" width="799" height="622"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Every draft shows the check FalkorDB ran and each query behind it, with its time.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Then a person approves it. The AI has no tool for approving at all; that action only exists behind the web UI. When someone approves, the graph commits those pairs and marks every other swap that touches them as taken. At 300 pairs, that's 66,864 swaps updated in about 140 ms.&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%2Fmushn0oo5d12sz1p60vu.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%2Fmushn0oo5d12sz1p60vu.png" alt="A plan approved by a person, with what the graph committed" width="799" height="219"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Approval is a person's action, recorded with their name and time.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  When a lab test fails
&lt;/h3&gt;

&lt;p&gt;This is the part we're proudest of. A person records a positive crossmatch, say pair 1's donor can't give to pair 70's patient. One query finds every stored swap where pair 70 comes right after pair 1, marks it dead, and links it to the lab result that killed 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;t:&lt;/span&gt;&lt;span class="n"&gt;Crossmatch&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;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;Pair&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;&amp;lt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="py"&gt;h1:&lt;/span&gt;&lt;span class="ow"&gt;HAS&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;c:&lt;/span&gt;&lt;span class="n"&gt;Cycle&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;h2:&lt;/span&gt;&lt;span class="ow"&gt;HAS&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;b:&lt;/span&gt;&lt;span class="n"&gt;Pair&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;WHERE&lt;/span&gt; &lt;span class="n"&gt;c.live&lt;/span&gt; &lt;span class="ow"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;h2.pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h1.pos&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;c.len&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;c.live&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&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;:BROKE&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;c&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our 200-pair demo, that kills 27 swaps, including one the coordinator had already approved: the ring 1 → 70 → 173. Its three patients are now stranded, so the graph releases them.&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%2Fn2teloorxkiviq244vhb.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%2Fn2teloorxkiviq244vhb.png" alt="What the graph found after a positive crossmatch: 27 swaps died, 3 patients stranded, 1 rescue" width="800" height="763"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;One failed test: 27 swaps die, the approved ring 1 → 70 → 173 breaks, and the graph finds what can still be saved.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Then it looks for something to save. A 3-way ring sometimes has a "back-arc": a link going the other way between two of its pairs. If so, those two pairs can still do a simple 2-way swap even though the ring is broken. The UK's national exchange counts back-arcs for exactly this reason (Manlove and O'Malley, 2014). In our demo, pairs 1 and 173 have one, so there's a rescue.&lt;/p&gt;

&lt;p&gt;The agent then drafts a repair from the swaps still alive, with the stranded patients weighted first. It re-matches pairs 1 and 173. Pair 70 is very hard to match and has no live option left, and the app just says so. A person approves the repair, and the graph records which plan it repaired and which lab result caused 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%2F50w8ptzq1s353buyz8a7.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%2F50w8ptzq1s353buyz8a7.png" alt="The repair plan re-matching pairs 1 and 173" width="799" height="350"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The repair re-matches two of the three stranded patients and says plainly that pair 70 has to wait.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The whole reaction to the failed test is eight small queries, about 40 ms. The UI shows that list of queries with their timings after every action, because we wanted people to see the graph doing the work.&lt;/p&gt;

&lt;h3&gt;
  
  
  "Why was this patient left out?"
&lt;/h3&gt;

&lt;p&gt;This used to be a weak spot. Now it's a four-step walk in the graph: from the patient, to their possible swaps, to the other pairs in those swaps, to the chosen swap that's already using one of those pairs. The answer reads like: &lt;em&gt;"Pair 5 has 193 possible swaps, but each one needs a pair that the best set already uses. The chosen swap 55&amp;gt;84&amp;gt;78 blocks 45 of them."&lt;/em&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%2Fz2nmdcqfshkbra6d5hcr.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%2Fz2nmdcqfshkbra6d5hcr.png" alt="Why pair 5 is left out, with the swaps blocking it and the queries FalkorDB ran" width="758" height="2021"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The answer names the swaps in the way, and shows the four-step walk FalkorDB did to find them.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For patients with no short swap at all, &lt;code&gt;shortestPath&lt;/code&gt; finds the shortest ring back to them. Sometimes the answer is simply that the donor can't give to anyone in the pool. In one test pool, four pairs had an AB donor, and AB donors can only give to AB patients. No swap of any length could include them.&lt;/p&gt;

&lt;h3&gt;
  
  
  What-ifs on a copy
&lt;/h3&gt;

&lt;p&gt;"What if one more donor joined?" and "what if this pair dropped out?" run on a &lt;code&gt;GRAPH.COPY&lt;/code&gt; of the live graph. We change the copy with Cypher, compare the results and drop it. The real data never leaves the database. We liked this a lot; it was simpler than anything we'd planned.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping the AI honest
&lt;/h2&gt;

&lt;p&gt;The agent uses an LLM, but with tight limits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It can only call a fixed set of tools. Every tool is a human-written, parameterised Cypher query. The model never writes queries.&lt;/li&gt;
&lt;li&gt;The same tools are served over MCP, so any MCP client gets the same rules.&lt;/li&gt;
&lt;li&gt;If the answer mentions a patient that no tool actually returned, the answer is withheld.&lt;/li&gt;
&lt;li&gt;Approving a plan and recording a lab result are human-only. They're not in the agent's tools or on the MCP server, and a test fails if they ever show up there.&lt;/li&gt;
&lt;li&gt;Every answer carries its evidence. An empty result means "the graph doesn't know", never "safe".&lt;/li&gt;
&lt;/ul&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%2Ft89z02bada2n3tza7c5c.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%2Ft89z02bada2n3tza7c5c.png" alt="The agent answering why pair 5 is not matched, with the tool it called" width="800" height="304"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Asked in plain English, the model picked one fixed tool. The tool call and its result are shown under the answer.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We also didn't want to trust our own numbers blindly. A separate plain-Python recount, which never touches the database, has to agree with the graph's cycle counts and max-flow bound, or &lt;code&gt;make verify&lt;/code&gt; fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Some numbers
&lt;/h2&gt;

&lt;p&gt;Measured through the app's own tools on 300 pairs (&lt;code&gt;make graph-bench&lt;/code&gt;, median of 5 fresh loads):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What the graph does&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Store all 64,976 three-way swaps as nodes&lt;/td&gt;
&lt;td&gt;~590 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Approval: mark 66,864 swaps as taken&lt;/td&gt;
&lt;td&gt;~137 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failed test: kill the swaps that used that link&lt;/td&gt;
&lt;td&gt;~6 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Find the 2-way rescue inside a broken ring&lt;/td&gt;
&lt;td&gt;~1 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Why left out?" four-hop walk&lt;/td&gt;
&lt;td&gt;~3 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;GRAPH.COPY&lt;/code&gt; of the whole graph for a what-if&lt;/td&gt;
&lt;td&gt;~270 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;algo.maxFlow&lt;/code&gt; upper bound&lt;/td&gt;
&lt;td&gt;~110 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What we learned about FalkorDB
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Things we liked.&lt;/strong&gt; Cycle-finding is a natural fit for Cypher patterns. It found tens of thousands of rings in under a second. &lt;code&gt;GRAPH.COPY&lt;/code&gt; made what-ifs almost boring. &lt;code&gt;algo.maxFlow&lt;/code&gt; gave us an upper bound without writing a solver. And getting started was one Docker container and a few lines of Python.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Things that tripped us up:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Results get cut off silently.&lt;/strong&gt; By default a query returns at most 10,000 rows, with no warning. Our cycle counts were wrong until we noticed and raised the limit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Some Cypher isn't supported.&lt;/strong&gt; You can't put a pattern inside &lt;code&gt;CASE&lt;/code&gt; or inside &lt;code&gt;exists()&lt;/code&gt;, and &lt;code&gt;shortestPath&lt;/code&gt; only works in &lt;code&gt;WITH&lt;/code&gt; or &lt;code&gt;RETURN&lt;/code&gt;. Each time, there was a workaround (&lt;code&gt;OPTIONAL MATCH&lt;/code&gt;, or storing a list of members on the node), but it took some trial and error.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unclear errors from Python.&lt;/strong&gt; Passing a numpy number instead of a plain Python int gave "Encountered unhandled type in inlined properties", without saying which parameter was wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scale has a cost.&lt;/strong&gt; After we added about 67,000 swap nodes, &lt;code&gt;algo.maxFlow&lt;/code&gt; got roughly four times slower, even though it doesn't read those nodes. Storing every swap was still worth it for us, but it's a trade-off.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Things we'd love to see: a built-in way to find cycles up to a given length, a warning when results are truncated, and error messages that point at the exact problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;All patients are synthetic. No real or personal data, no clinical validation, not medical advice.&lt;/li&gt;
&lt;li&gt;The 2-way vs 3-way result is a known one. We reproduce it; we didn't discover it.&lt;/li&gt;
&lt;li&gt;The graph builds, scores, checks and records the plan, but an integer program picks the swaps.&lt;/li&gt;
&lt;li&gt;We react to failed tests. We don't predict which tests will fail.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://swap.dhanalakshmi.tech/" rel="noopener noreferrer"&gt;https://swap.dhanalakshmi.tech/&lt;/a&gt; (step 6, "Test fails", is the part described above)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/Dhanalakshmi-D04/swap-falkorDB" rel="noopener noreferrer"&gt;https://github.com/Dhanalakshmi-D04/swap-falkorDB&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run the failure story yourself:&lt;/strong&gt; &lt;code&gt;make up&lt;/code&gt;, then &lt;code&gt;make failure-story&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We used AI coding assistants while building this, and we've written down exactly how in &lt;code&gt;AI_USE.md&lt;/code&gt; in the repo.&lt;/p&gt;

&lt;p&gt;If you work on kidney exchange or graph databases and something here looks wrong, we'd genuinely like to hear about it.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>database</category>
      <category>devchallenge</category>
      <category>python</category>
    </item>
    <item>
      <title>This Portfolio Scrolls Different (And That’s Intentional)</title>
      <dc:creator>Dhanalakshmi.d.gowda23</dc:creator>
      <pubDate>Mon, 12 Jan 2026 16:14:23 +0000</pubDate>
      <link>https://dev.to/dhanalakshmidgowda23_bf/this-portfolio-scrolls-different-and-thats-intentional-2j54</link>
      <guid>https://dev.to/dhanalakshmidgowda23_bf/this-portfolio-scrolls-different-and-thats-intentional-2j54</guid>
      <description>&lt;p&gt;This is a submission for the New Year, New You Portfolio Challenge presented by Google AI&lt;br&gt;
&lt;a href="https://dev.to/challenges/new-year-new-you-google-ai-2025-12-31"&gt;https://dev.to/challenges/new-year-new-you-google-ai-2025-12-31&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;u&gt;&lt;strong&gt;About Me&lt;/strong&gt;&lt;/u&gt;&lt;br&gt;
Hi, I’m Dhanalakshmi D, a passionate frontend-focused developer who loves building interactive, animation-driven web experiences.&lt;/p&gt;

&lt;p&gt;This portfolio represents my “New Year, New You” reset — a complete rebuild of how I present myself online. Instead of a static resume, I wanted a living, immersive portfolio that reflects how I think, design, and engineer interfaces.&lt;br&gt;
My goal was clear, I just wanted to make my portfolio feel like an experience, not a webpage.&lt;/p&gt;

&lt;p&gt;&lt;u&gt;&lt;strong&gt;Portfolio&lt;/strong&gt;&lt;/u&gt;&lt;br&gt;
Below is my live, fully deployed portfolio, embedded directly into this post.&lt;br&gt;
It is:&lt;br&gt;
-&amp;gt;Live &amp;amp; functional&lt;br&gt;
-&amp;gt;Production-ready&lt;br&gt;
-&amp;gt;Responsive&lt;br&gt;
-&amp;gt;Deployed on Firebase&lt;br&gt;
URL:- (&lt;a href="https://laksh-portfolio-484114.web.app" rel="noopener noreferrer"&gt;https://laksh-portfolio-484114.web.app&lt;/a&gt;)&lt;br&gt;
--labels dev-tutorial=devnewyear2026&lt;/p&gt;

&lt;p&gt;&lt;u&gt;&lt;strong&gt;How I Built It&lt;/strong&gt;&lt;/u&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tech Stack:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;React + Vite – fast, modern frontend&lt;/li&gt;
&lt;li&gt;Tailwind CSS – clean, scalable styling&lt;/li&gt;
&lt;li&gt;GSAP + ScrollTrigger – smooth, cinematic animations&lt;/li&gt;
&lt;li&gt;Locomotive Scroll–style interactions – fluid scrolling experience&lt;/li&gt;
&lt;li&gt;Firebase Hosting – fast global deployment&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Design Approach:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Dark-themed, minimal UI to keep focus on content&lt;/li&gt;
&lt;li&gt;Motion-first design inspired by modern creative studios&lt;/li&gt;
&lt;li&gt;Sections flow naturally using scroll-based animations&lt;/li&gt;
&lt;li&gt;Clean typography and intentional spacing&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Google AI:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Used Gemini during the early ideation phase to brainstorm layout structures, section flow, and overall portfolio narrative.&lt;/li&gt;
&lt;li&gt;Leveraged Gemini to refine copy for the hero section, project descriptions, and “About Me” content, making it clearer and more concise.&lt;/li&gt;
&lt;li&gt;Used Antigravity (agent-first AI IDE) while building components to:

&lt;ul&gt;
&lt;li&gt;Generate and refactor React components&lt;/li&gt;
&lt;li&gt;Explore alternate implementations for animations and layout                  logic&lt;/li&gt;
&lt;li&gt;Debug issues faster during development&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Experimented with AI Studio / Gemini CLI to test small code snippets, validate ideas, and speed up iteration.&lt;/li&gt;
&lt;li&gt;Relied on Google AI tools to reduce repetitive work, allowing me to focus more on UX decisions, motion design, and performance optimization.&lt;/li&gt;
&lt;li&gt;Treated AI as a development assistant, not a shortcut — using it to support thinking, iteration, and polish rather than auto-generating the entire project.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;u&gt;&lt;strong&gt;What I’m Most Proud Of&lt;/strong&gt;&lt;/u&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Smooth scroll-based animations that feel premium and intentional&lt;/li&gt;
&lt;li&gt;Motion-driven storytelling instead of static sections&lt;/li&gt;
&lt;li&gt;Performance-optimized build despite heavy animations&lt;/li&gt;
&lt;li&gt;End-to-end deployment — from local dev to live production&lt;/li&gt;
&lt;li&gt;Rebuilding my personal brand with clarity and confidence&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most importantly, this portfolio reflects growth — not just in skills, but in how I present myself as a developer.&lt;/p&gt;

&lt;p&gt;&lt;u&gt;&lt;strong&gt;Final Thoughts&lt;/strong&gt;&lt;/u&gt;&lt;/p&gt;

&lt;p&gt;This challenge pushed me to stop procrastinating and finally ship a portfolio I’m proud of.&lt;br&gt;
It’s not “perfect” — but it’s real, live, and evolving.&lt;br&gt;
Thanks to Google AI and the Dev community for creating a challenge that motivates builders to actually build.&lt;br&gt;
Happy New Year &amp;amp; happy shipping:-)&lt;/p&gt;

&lt;h1&gt;
  
  
  devchallenge #googleaichallenge #portfolio #gemini
&lt;/h1&gt;

</description>
      <category>devchallenge</category>
      <category>googleaichallenge</category>
      <category>portfolio</category>
      <category>gemini</category>
    </item>
  </channel>
</rss>
