<?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 Grover</title>
    <description>The latest articles on DEV Community by Dev Grover (@devgr72).</description>
    <link>https://dev.to/devgr72</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%2F4192435%2Fbe5cc779-5006-44d8-b60a-8b3ff1869d87.png</url>
      <title>DEV Community: Dev Grover</title>
      <link>https://dev.to/devgr72</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/devgr72"/>
    <language>en</language>
    <item>
      <title>Every prescription looked reasonable. The chain didn't.</title>
      <dc:creator>Dev Grover</dc:creator>
      <pubDate>Sun, 18 Oct 2026 16:32:13 +0000</pubDate>
      <link>https://dev.to/devgr72/every-prescription-looked-reasonable-the-chain-didnt-13i7</link>
      <guid>https://dev.to/devgr72/every-prescription-looked-reasonable-the-chain-didnt-13i7</guid>
      <description>&lt;p&gt;&lt;em&gt;All patient data in this post is synthetic. FirstDomino is a hackathon prototype, not a medical device, and it has not been clinically validated.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; FirstDomino turns a patient's scattered medicine records into a &lt;strong&gt;FalkorDB graph&lt;/strong&gt;, finds a possible &lt;em&gt;prescribing cascade&lt;/em&gt; in &lt;strong&gt;one Cypher walk&lt;/strong&gt;, proves when two medicines are &lt;strong&gt;not&lt;/strong&gt; linked with FalkorDB's shortest-path algorithm, and asks for the &lt;strong&gt;one missing fact&lt;/strong&gt; a clinician needs. An AI agent helps, but it is never allowed to decide.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Video Link: &lt;a href="https://youtu.be/2DeOnoqJeyY" rel="noopener noreferrer"&gt;https://youtu.be/2DeOnoqJeyY&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: a side effect that looks like a new illness
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Day 0:&lt;/strong&gt; an older woman starts &lt;strong&gt;amlodipine&lt;/strong&gt;, a common blood pressure pill.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day 21:&lt;/strong&gt; her ankles swell. That is a known side effect of amlodipine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day 35:&lt;/strong&gt; at a different visit, with a different doctor, the swelling is treated with &lt;strong&gt;furosemide&lt;/strong&gt;, a water pill.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every prescription looked reasonable. But the second medicine may exist only because of the first.&lt;/p&gt;

&lt;p&gt;This is a &lt;strong&gt;prescribing cascade&lt;/strong&gt;: a side effect gets mistaken for a new illness and treated with another medicine. No single step looks wrong. You only see it when the whole story is laid out in order, and that story is usually spread across clinic letters, pharmacy records and what the family remembers.&lt;/p&gt;

&lt;p&gt;It is common. An international panel (ThinkCascades, 2022) agreed on nine important cascades in older adults. A second panel listed 65 potentially inappropriate ones (the PIPC list), and a 2026 &lt;em&gt;BMJ&lt;/em&gt; study of about 2.3 million older adults in Ontario found 24 of them to be common and closely timed.&lt;/p&gt;

&lt;p&gt;For Graph Hacks we built &lt;strong&gt;FirstDomino&lt;/strong&gt;. It finds that chain in one patient's record as a single walk through a FalkorDB graph, and asks for the one missing fact a clinician needs to review it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it in 60 seconds
&lt;/h2&gt;

&lt;p&gt;Open the &lt;strong&gt;patient lab&lt;/strong&gt; at &lt;a href="https://firstdomino-web.vercel.app/lab" rel="noopener noreferrer"&gt;https://firstdomino-web.vercel.app/lab&lt;/a&gt;, pick a preset and click &lt;strong&gt;Write to the graph and walk it&lt;/strong&gt;. A new synthetic patient is written to FalkorDB, and every query is timed.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reason not recorded:&lt;/strong&gt; amlodipine, then swelling, then furosemide with no reason on record. You get a possible pattern and one question to ask.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wrong order:&lt;/strong&gt; furosemide first, amlodipine later. The graph still connects the two medicines, but the rules say the order doesn't fit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A lookalike medicine:&lt;/strong&gt; diltiazem instead of amlodipine. The graph finds no path at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The pharmacist reviews&lt;/strong&gt; the medication story, the possible pattern and its evidence: documented, reported, or not known yet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The caregiver answers.&lt;/strong&gt; FirstDomino asks them for one missing fact, such as why furosemide was started.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The clinician decides.&lt;/strong&gt; FirstDomino reports &lt;em&gt;possible patterns worth reviewing&lt;/em&gt;. It never suggests changing a medicine; that is the prescribing clinician's call.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why a graph, not a table
&lt;/h2&gt;

&lt;p&gt;A graph stores things as &lt;strong&gt;nodes&lt;/strong&gt; (a patient, a medicine, a symptom) and the links between them as &lt;strong&gt;edges&lt;/strong&gt; (this patient HAS this medicine, this medicine is IN_CLASS of that family). You query it with &lt;strong&gt;Cypher&lt;/strong&gt;, the query language for graphs, by drawing the shape you're looking for.&lt;/p&gt;

&lt;p&gt;A cascade is a shape: medicine → its ingredient → its &lt;strong&gt;drug class&lt;/strong&gt; (a family of similar medicines) → a cited pattern → another drug class → ingredient → second medicine. In FalkorDB that is one query. Here is the core of Q1. Like every query in the project, it starts from one patient:&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;p:&lt;/span&gt;&lt;span class="n"&gt;Patient&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;$pid&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="o"&gt;-&amp;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;MedicationEpisode&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;:OF&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;ai:&lt;/span&gt;&lt;span class="n"&gt;Ingredient&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;:IN_CLASS&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;:DrugClass&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;:INDEX&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;pat:&lt;/span&gt;&lt;span class="n"&gt;Pattern&lt;/span&gt;&lt;span class="ss"&gt;),&lt;/span&gt;
      &lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pat&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;:MARKER&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;:DrugClass&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;:IN_CLASS&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;bi:&lt;/span&gt;&lt;span class="n"&gt;Ingredient&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;:OF&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;MedicationEpisode&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;: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="n"&gt;p&lt;/span&gt;&lt;span class="ss"&gt;),&lt;/span&gt;
      &lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pat&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;:VIA&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;e:&lt;/span&gt;&lt;span class="n"&gt;Effect&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;p&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="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;o:&lt;/span&gt;&lt;span class="n"&gt;Observation&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;:OF_EFFECT&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;e&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The walk goes through the &lt;strong&gt;class&lt;/strong&gt;, not the name. Nifedipine and felodipine match the same pattern as amlodipine. Diltiazem doesn't: it is also called a calcium-channel blocker, but it sits in a different ATC class (&lt;code&gt;C08DB&lt;/code&gt;, not &lt;code&gt;C08CA&lt;/code&gt;), and the cited studies are about amlodipine's class. A keyword list can't tell them apart. An &lt;code&gt;IN_CLASS&lt;/code&gt; edge can.&lt;/p&gt;

&lt;p&gt;Q1 only collects facts and how sure each one is. Tested Python rules (&lt;code&gt;engine/rules.py&lt;/code&gt;) set the status: &lt;code&gt;needs_evidence&lt;/code&gt;, &lt;code&gt;candidate&lt;/code&gt;, &lt;code&gt;competing_explanation&lt;/code&gt;, &lt;code&gt;not_matched&lt;/code&gt; and so on. &lt;strong&gt;The graph finds; the rules decide.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The path comes from the graph
&lt;/h2&gt;

&lt;p&gt;We didn't want the screen to draw a chain the database never returned. So Q11 asks FalkorDB for the walk itself, and the API hands back &lt;code&gt;nodes(path)&lt;/code&gt; and &lt;code&gt;relationships(path)&lt;/code&gt; in order. The web app draws exactly that: &lt;strong&gt;7 nodes, 6 relationships&lt;/strong&gt;, plus a side branch from the pattern's effect to the symptom the caregiver reported.&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%2Fphstyhiwpoo7xllfbqz2.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%2Fphstyhiwpoo7xllfbqz2.png" alt="The patient lab: a new synthetic patient written to FalkorDB, with the walk drawn from the path the query returned and each step's time" width="800" height="1431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the wrong order, the path is still there but the status is &lt;code&gt;not_matched&lt;/code&gt;. The path explains; the rules decide.&lt;/p&gt;
&lt;h2&gt;
  
  
  Proving a link is NOT there
&lt;/h2&gt;

&lt;p&gt;Finding a chain is half the job. The other half is showing that one isn't there. Q12 hands two medicines to FalkorDB's shortest-path algorithm and only lets it walk the knowledge edges, never the patient:&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;CALL&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;a&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
  &lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;algo.SPpaths&lt;/span&gt;&lt;span class="ss"&gt;({&lt;/span&gt;&lt;span class="py"&gt;sourceNode:&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="py"&gt;targetNode:&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt;
                     &lt;span class="py"&gt;relTypes:&lt;/span&gt; &lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'OF'&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'IN_CLASS'&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'INDEX'&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'MARKER'&lt;/span&gt;&lt;span class="ss"&gt;],&lt;/span&gt;
                     &lt;span class="py"&gt;relDirection:&lt;/span&gt; &lt;span class="s1"&gt;'both'&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;maxLen&lt;/span&gt;&lt;span class="dl"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;pathCount&lt;/span&gt;&lt;span class="dl"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="ss"&gt;})&lt;/span&gt; &lt;span class="k"&gt;YIELD&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;
  &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="nf"&gt;collect&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;paths&lt;/span&gt;
&lt;span class="ss"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;For amlodipine and furosemide it finds the 7-node path through the pattern. For &lt;strong&gt;diltiazem and furosemide it finds nothing&lt;/strong&gt;, because no edge leads from diltiazem's class to the pattern. A flat list ("calcium-channel blocker + water pill") still flags the pair, and the lab shows both answers side by side.&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%2Fwlpb6c2bwxyeyiacnyd0.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%2Fwlpb6c2bwxyeyiacnyd0.png" alt="The lookalike preset: algo.SPpaths finds no path from diltiazem to furosemide, while a flat name check still flags the pair" width="800" height="1258"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One honest caveat, which the app also shows: &lt;strong&gt;no chain does not mean nothing is wrong.&lt;/strong&gt; FirstDomino models one curated pattern, so a clinician still reviews the whole medicine list.&lt;/p&gt;
&lt;h2&gt;
  
  
  Memory you can see
&lt;/h2&gt;

&lt;p&gt;FalkorDB is the shared memory for the caregiver, the pharmacist with the agent, and the clinician.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Questions and agent steps are nodes&lt;/strong&gt; (&lt;code&gt;Question&lt;/code&gt;, &lt;code&gt;AgentStep&lt;/code&gt;). On a second run the agent reads them first and, instead of asking again, says &lt;strong&gt;"answered from memory"&lt;/strong&gt;: asked in run 1, still waiting for the caregiver.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Q2 picks the next question.&lt;/strong&gt; Each missing fact is an &lt;code&gt;EvidenceRequirement&lt;/code&gt;. Q2 counts how many open findings wait on each one and returns the fact that unblocks the most.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nothing is overwritten.&lt;/strong&gt; A corrected fact gets a new &lt;code&gt;FactVersion&lt;/code&gt; linked to the old one by &lt;code&gt;SUPERSEDES&lt;/code&gt;. The finding gets a new &lt;code&gt;FindingRevision&lt;/code&gt; linked by &lt;code&gt;PREVIOUS&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decisions stay on their revision.&lt;/strong&gt; If a clinician reviewed revision 1 and a late GP note then moves a date, revision 2 becomes &lt;code&gt;needs_reassessment&lt;/code&gt;. The decision stays on revision 1, the evidence the clinician actually saw.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Q3 re-checks only what changed.&lt;/strong&gt; When a fact changes, Q3 walks back from it to the findings that depend on it. Changing a statin's start date re-checks nothing. Correcting the swelling date re-checks exactly one finding.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It survives a restart.&lt;/strong&gt; FalkorDB runs with AOF (append-only file) persistence, and a test restarts it to prove nothing is lost.&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/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3d5azsm18g5duxfa9j1e.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3d5azsm18g5duxfa9j1e.gif" alt="The caregiver answers the missing fact: the dashed edge turns solid, the finding moves to version 2, and only that finding is re-checked" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  An agent that isn't allowed to decide
&lt;/h2&gt;

&lt;p&gt;The review agent gets one pass over one finding. Its rules are code, not prompt wording:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Six allowlisted tools:&lt;/strong&gt; read the evidence pack, inspect one medicine, find the next missing fact, ask a question, see which findings depend on a fact, and prepare the packet. It never writes Cypher.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;At most six steps.&lt;/strong&gt; Then it stops.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every reply is checked.&lt;/strong&gt; It must be valid JSON naming an allowed tool, every ID must come from an earlier tool result, and its text can't add days, medicines or reasons, or certainty words like "confirmed".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Templates are the fallback.&lt;/strong&gt; A bad reply, a rate limit or a timeout gets a retry, then deterministic templates, with the reason recorded. We run the LLM on Groq's free tier, so this happens, and the outcome is the same in plainer words.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every step is saved&lt;/strong&gt; in the graph as an &lt;code&gt;AgentStep&lt;/code&gt;, failures included.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The LLM never creates a finding or records a decision. The same read-only tools are also available in Claude Desktop through our MCP server, which calls the HTTP API, never the graph.&lt;/p&gt;
&lt;h2&gt;
  
  
  What FalkorDB handled for us
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What we needed&lt;/th&gt;
&lt;th&gt;What FalkorDB gave us&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Keep each family's data apart&lt;/td&gt;
&lt;td&gt;One graph per family, chosen with &lt;code&gt;select_graph&lt;/code&gt;, so there is no &lt;code&gt;WHERE family = …&lt;/code&gt; to forget&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Find the chain&lt;/td&gt;
&lt;td&gt;Cypher pattern matching through drug classes in one query&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prove a link is absent&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;algo.SPpaths&lt;/code&gt;, a built-in shortest-path algorithm&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stop two people overwriting each other&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;MERGE&lt;/code&gt; and a compare-and-set on &lt;code&gt;case_version&lt;/code&gt; in one query; a stale write gets HTTP 409&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keep answers across a crash&lt;/td&gt;
&lt;td&gt;AOF persistence, reported by our &lt;code&gt;/health&lt;/code&gt; endpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;See the real graph&lt;/td&gt;
&lt;td&gt;FalkorDB Browser, which we used to check every query by eye&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  What broke along the way (the useful part)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Railway stopped deploying &lt;code&gt;main&lt;/code&gt;.&lt;/strong&gt; Merges built nothing. The service's saved source had the repo but no branch, so there was no push trigger. We now connect the source with an explicit &lt;code&gt;--branch main&lt;/code&gt; and check the live API after every merge. (&lt;a href="https://github.com/AjaySingh-a/firstdomino/blob/main/docs/deploy.md" rel="noopener noreferrer"&gt;docs/deploy.md&lt;/a&gt;, commit &lt;a href="https://github.com/AjaySingh-a/firstdomino/commit/7a99cee" rel="noopener noreferrer"&gt;7a99cee&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The agent sent a stale &lt;code&gt;case_version&lt;/code&gt; and got a 409.&lt;/strong&gt; Right after a caregiver answer, the page reloads the case first and the investigation a moment later. Clicking &lt;strong&gt;Run review agent&lt;/strong&gt; in that gap sent the old version, and our own conflict check rejected it. The button now stays disabled until both versions agree. (&lt;a href="https://github.com/AjaySingh-a/firstdomino/pull/22" rel="noopener noreferrer"&gt;PR #22&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. &lt;code&gt;collect()&lt;/code&gt; over an empty &lt;code&gt;OPTIONAL MATCH&lt;/code&gt; isn't empty.&lt;/strong&gt; With no symptom on record, &lt;code&gt;collect([o.id, o.happened_at, …])&lt;/code&gt; returned &lt;code&gt;[[null, null, …]]&lt;/code&gt;, a list holding one empty entry. The rules now drop entries whose id is null. (&lt;a href="https://github.com/AjaySingh-a/firstdomino/blob/main/engine/queries/q1_candidate_match.cypher" rel="noopener noreferrer"&gt;q1_candidate_match.cypher&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. No path meant no row.&lt;/strong&gt; When &lt;code&gt;algo.SPpaths&lt;/code&gt; found nothing, the whole row vanished, including the two drug classes we wanted to show next to "no path". Wrapping the call in a &lt;code&gt;CALL {}&lt;/code&gt; subquery that collects the paths keeps one row with an empty list. (&lt;a href="https://github.com/AjaySingh-a/firstdomino/blob/main/engine/queries/q12_medicine_link.cypher" rel="noopener noreferrer"&gt;q12_medicine_link.cypher&lt;/a&gt;, commit &lt;a href="https://github.com/AjaySingh-a/firstdomino/commit/3266f30" rel="noopener noreferrer"&gt;3266f30&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Cloudflare blocked our agent's user agent.&lt;/strong&gt; Calls to Groq from Python's built-in &lt;code&gt;urllib&lt;/code&gt; came back as 403 (Cloudflare error 1010), because of its default &lt;code&gt;Python-urllib&lt;/code&gt; user agent. Sending our own user agent fixed it. (&lt;a href="https://github.com/AjaySingh-a/firstdomino/blob/main/agent/llm.py" rel="noopener noreferrer"&gt;agent/llm.py&lt;/a&gt;, commit &lt;a href="https://github.com/AjaySingh-a/firstdomino/commit/0d94607" rel="noopener noreferrer"&gt;0d94607&lt;/a&gt;)&lt;/p&gt;
&lt;h2&gt;
  
  
  Proof, and what it does not prove
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;12 of 12 synthetic cases&lt;/strong&gt; (98 of 98 checks) agree with their documented expectations when run on the graph engine. The cases were written from our data-model doc, not from the rules code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;We tested the test runner.&lt;/strong&gt; We made five expectations wrong on purpose, and it reported all five.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The restart test&lt;/strong&gt; restarts FalkorDB and the API after a correction and checks that everything reads back the same.&lt;/li&gt;
&lt;li&gt;The saved outputs from our final live check are in the &lt;a href="https://github.com/AjaySingh-a/firstdomino/tree/main/evidence/2026-10-18" rel="noopener noreferrer"&gt;evidence folder&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This shows the engine does what its documentation says. &lt;strong&gt;It is not clinical validation.&lt;/strong&gt; The same team wrote the cases and the engine.&lt;/p&gt;
&lt;h2&gt;
  
  
  What it doesn't do
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One curated pattern&lt;/strong&gt; (calcium-channel blocker → ankle swelling → loop diuretic). The model is built for the 65 PIPC cascades, but each needs a citation and careful review.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Synthetic data only.&lt;/strong&gt; There's no authentication and no clinical study.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The 90-day window is shown but excludes nothing,&lt;/strong&gt; because the published association lasts longer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The algorithm proves a connection or its absence.&lt;/strong&gt; It doesn't weigh evidence. The rules decide every status.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live app:&lt;/strong&gt; &lt;a href="https://firstdomino-web.vercel.app" rel="noopener noreferrer"&gt;https://firstdomino-web.vercel.app&lt;/a&gt; (take the 2-minute guided tour)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Patient lab:&lt;/strong&gt; &lt;a href="https://firstdomino-web.vercel.app/lab" rel="noopener noreferrer"&gt;https://firstdomino-web.vercel.app/lab&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API health:&lt;/strong&gt; &lt;a href="https://api-production-b29f.up.railway.app/health" rel="noopener noreferrer"&gt;https://api-production-b29f.up.railway.app/health&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/AjaySingh-a" rel="noopener noreferrer"&gt;
        AjaySingh-a
      &lt;/a&gt; / &lt;a href="https://github.com/AjaySingh-a/firstdomino" rel="noopener noreferrer"&gt;
        firstdomino
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Possible prescribing cascades from a synthetic patient's medication story, built on FalkorDB (Graph Hacks 2026)
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;FirstDomino&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/AjaySingh-a/firstdomino/actions/workflows/ci.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/AjaySingh-a/firstdomino/actions/workflows/ci.yml/badge.svg" alt="CI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A side effect, mistaken for a new illness, treated with another medicine. FirstDomino finds that
chain across a patient's visits as one walk through a FalkorDB graph, and asks for the one missing fact
a clinician needs.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;FirstDomino rebuilds a synthetic patient's medication story as a graph, matches it against a cited
prescribing-cascade pattern through drug classes, and keeps every fact, finding, question, agent step
and clinician decision with its history.&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Live app&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://firstdomino-web.vercel.app" rel="nofollow noopener noreferrer"&gt;https://firstdomino-web.vercel.app&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Patient lab&lt;/strong&gt; (build your own patient)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://firstdomino-web.vercel.app/lab" rel="nofollow noopener noreferrer"&gt;https://firstdomino-web.vercel.app/lab&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;API health&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://api-production-b29f.up.railway.app/health" rel="nofollow noopener noreferrer"&gt;https://api-production-b29f.up.railway.app/health&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Repository&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/AjaySingh-a/firstdomino" rel="noopener noreferrer"&gt;https://github.com/AjaySingh-a/firstdomino&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Video&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;TODO&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Blog&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;TODO&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tracks&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Agents That Act on Connected Data · Agent Memory and Coordination&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Built for &lt;strong&gt;Graph Hacks&lt;/strong&gt; (WeMakeDevs x FalkorDB, 15-18 Oct 2026) by team &lt;strong&gt;Kodeline&lt;/strong&gt; (Ajay Singh, Dev Grover).&lt;/p&gt;
&lt;div class="markdown-alert markdown-alert-important"&gt;
&lt;p class="markdown-alert-title"&gt;Important&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;All patient data in this repository is synthetic.&lt;/strong&gt; FirstDomino is a hackathon prototype, not a
medical device, and it has not been clinically validated. Findings are &lt;em&gt;possible patterns for&lt;/em&gt;…&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/AjaySingh-a/firstdomino" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;🧪 A challenge for you:&lt;/strong&gt; open the patient lab and try to build a patient the graph gets wrong. Tell us in the comments 👇&lt;/p&gt;

&lt;h2&gt;
  
  
  Team
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dev Grover:&lt;/strong&gt; engine, Cypher queries, API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ajay Singh:&lt;/strong&gt; web app, review agent, MCP server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Built for Graph Hacks by WeMakeDevs × FalkorDB.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>falkordb</category>
      <category>wemakedev</category>
      <category>ai</category>
      <category>hackathon</category>
    </item>
  </channel>
</rss>
