<?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: Gautam K</title>
    <description>The latest articles on DEV Community by Gautam K (@gautamtalksdev).</description>
    <link>https://dev.to/gautamtalksdev</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%2F3868814%2F162b0c1d-a0db-4fc9-81ae-c61eb85c0664.jpg</url>
      <title>DEV Community: Gautam K</title>
      <link>https://dev.to/gautamtalksdev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gautamtalksdev"/>
    <language>en</language>
    <item>
      <title>Some pills are treating other pills: building Domino on FalkorDB</title>
      <dc:creator>Gautam K</dc:creator>
      <pubDate>Sun, 18 Oct 2026 17:30:39 +0000</pubDate>
      <link>https://dev.to/gautamtalksdev/some-pills-are-treating-other-pills-building-domino-on-falkordb-4k9j</link>
      <guid>https://dev.to/gautamtalksdev/some-pills-are-treating-other-pills-building-domino-on-falkordb-4k9j</guid>
      <description>&lt;p&gt;&lt;em&gt;Graph Hacks 2026 · WeMakeDevs × FalkorDB · Try it live: &lt;a href="https://domino.gautamkhosla.com" rel="noopener noreferrer"&gt;domino.gautamkhosla.com&lt;/a&gt; · &lt;a href="https://youtu.be/9OHJUJJjd0Q" rel="noopener noreferrer"&gt;3-minute video&lt;/a&gt; · &lt;a href="https://github.com/GautamTalksDev/domino" rel="noopener noreferrer"&gt;Code&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Beatrice is 84. In eighteen months she saw five prescribers. Her family doctor started amlodipine for her blood pressure. Seven weeks later her ankles were swollen, so a walk-in clinic prescribed furosemide. Her potassium dropped, so potassium chloride followed. The furosemide made her rush to the bathroom and sometimes not make it, so a urologist added oxybutynin. Oxybutynin is strongly anticholinergic, and a few months later she was confused enough that a geriatric clinic started donepezil for her memory.&lt;/p&gt;

&lt;p&gt;Every one of those prescriptions was reasonable on its own. Together, four of her nine medicines may exist only because of the first one.&lt;/p&gt;

&lt;p&gt;Beatrice is synthetic. The pattern is not. Geriatricians call it a &lt;strong&gt;prescribing cascade&lt;/strong&gt;: a side effect is mistaken for a new illness and treated with another drug (Rochon and Gurwitz, BMJ 1997). In 2022, 38 clinicians from six countries agreed on nine cascades that usually do more harm than good (ThinkCascades). In 2025, a study of 533,464 older adults in Ireland found a diuretic was 1.93 times more likely to be started &lt;em&gt;after&lt;/em&gt; a calcium channel blocker than before it.&lt;/p&gt;

&lt;p&gt;I built &lt;strong&gt;Domino&lt;/strong&gt;, a team of AI agents that find these chains in a FalkorDB graph, prove every link, and hand a pharmacist one question: &lt;em&gt;could the swelling be from amlodipine?&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The insight: a cascade is a path
&lt;/h2&gt;

&lt;p&gt;Take the medicine out of it and a cascade is five things: drug A starts, a side effect of A appears in its usual time window, someone misreads it, drug B starts to treat it, and the whole thing repeats from B. Every fact needed to see this already exists: prescription dates in the pharmacy record, side effects and indications in public labelling. What's missing is the &lt;strong&gt;connection&lt;/strong&gt;, because the facts are written by different people, months apart.&lt;/p&gt;

&lt;p&gt;That's a graph problem. Drug → side effect → drug → side effect → drug. In SQL it's a pile of self-joins that breaks with every extra hop. In Cypher it's one 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;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;:TAKES&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;m:&lt;/span&gt;&lt;span class="n"&gt;Drug&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;p&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="n"&gt;m.id&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;meds&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;:TAKES&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;Drug&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;:CAUSES&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;:Effect&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;:TREATED_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="nc"&gt;:Drug&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;:TAKES&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="k"&gt;WITH&lt;/span&gt; &lt;span class="k"&gt;DISTINCT&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;meds&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;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;:CAUSES&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;TREATED_BY&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="m"&gt;6&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;z:&lt;/span&gt;&lt;span class="n"&gt;Drug&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;z&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;IN&lt;/span&gt; &lt;span class="nf"&gt;nodes&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;WHERE&lt;/span&gt; &lt;span class="ow"&gt;NOT&lt;/span&gt; &lt;span class="py"&gt;n:&lt;/span&gt;&lt;span class="n"&gt;Drug&lt;/span&gt; &lt;span class="ow"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;n.id&lt;/span&gt; &lt;span class="ow"&gt;IN&lt;/span&gt; &lt;span class="n"&gt;meds&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&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="ss"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;startNode&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="n"&gt;.id&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="ss"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;endNode&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="n"&gt;.id&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On FalkorDB this returns all forty of Beatrice's candidate paths in about a tenth of a second.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modelling choices that made the queries simple
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Point both edges forward.&lt;/strong&gt; &lt;code&gt;CAUSES&lt;/code&gt; goes drug → effect and &lt;code&gt;TREATED_BY&lt;/code&gt; goes effect → drug. With both directed "downstream", any directed path over those two types alternates drug, effect, drug, effect on its own. The query needs no alternation logic at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One node type for side effects and diagnoses.&lt;/strong&gt; A cascade &lt;em&gt;is&lt;/em&gt; a side effect being treated as a diagnosis, so "ankle swelling" is the same &lt;code&gt;Effect&lt;/code&gt; node whether amlodipine causes it or a patient &lt;code&gt;HAS&lt;/code&gt; it. That makes "does the second drug have another reason?" a two-hop pattern: &lt;code&gt;(p)-[:HAS]-&amp;gt;(c:Effect)-[:TREATED_BY]-&amp;gt;(b)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dates as integers.&lt;/strong&gt; &lt;code&gt;TAKES.start&lt;/code&gt; is a day number, so timing ("B started 48 days after A, inside amlodipine's 7 to 120 day window for edema") is arithmetic, and the population query can count "B after A within a year" in a single aggregate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Knowledge as nodes, not code.&lt;/strong&gt; The 27 documented cascades are &lt;code&gt;Pattern&lt;/code&gt; nodes linked to drug classes and the effect between them. Matching a link to the literature is a traversal: &lt;code&gt;(a)-[:IN_CLASS]-&amp;gt;()&amp;lt;-[:INDEX]-(pt)-[:VIA]-&amp;gt;(e)&lt;/code&gt;. The same goes for dangerous combinations like the "triple whammy" (an ACE inhibitor, a diuretic and an NSAID together), and for class conflicts used when suggesting alternatives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agents that use the graph as their tools
&lt;/h2&gt;

&lt;p&gt;Domino's three agents only know the world through fourteen tools, and every tool runs named, parameterised Cypher from a catalog of 105 queries. Read tools run as &lt;code&gt;GRAPH.RO_QUERY&lt;/code&gt;, so the database itself refuses a write, even if a bug or a prompt injection tries one.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;Detective&lt;/strong&gt; walks the paths, finds loops (oxybutynin causes confusion, which donepezil treats; donepezil causes incontinence, which oxybutynin treats), checks side-effect pile-ups and combinations, and records findings back into the graph.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Pharmacist&lt;/strong&gt; explains each root's evidence, looks for alternatives that treat the same diagnosis without that side effect and without clashing with anything else the patient takes, runs FalkorDB's betweenness centrality to find the chokepoint drugs where cascades meet, and simulates the untangle.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Scribe&lt;/strong&gt; writes review tasks, an SBAR note for the prescriber and a plain-language note for the family.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The untangle is my favourite piece. It's a fixed-point computation over the cascade links: a medicine stays justified if it has its own diagnosis or is reached by a strong link from a justified medicine. For Beatrice, "what if amlodipine were reviewed?" leaves 4 of 9 medicines, drops her anticholinergic burden from 4 to 0, and removes the triple whammy. It's labelled a simulation, because it is one.&lt;/p&gt;

&lt;p&gt;Each agent is an LLM tool-calling loop when a key is set (Gemini, Claude, or anything OpenAI-compatible) and a deterministic planner when it isn't. The planner calls the same tools in the same order and stores the same audit trail. With no model at all, Domino still finds every cascade.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does the graph actually matter? I test it live
&lt;/h2&gt;

&lt;p&gt;The hackathon asks one question: is the graph doing work the agent could not do without it? So the landing page answers it live, not with a claim. &lt;strong&gt;Same question. Same model. One has a graph.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The language model gets Beatrice's whole record as plain text (every medicine, its start date, its prescriber and why it was started) and no FalkorDB. Domino's agents get the graph. Both are asked the same thing, and I slip a medicine that does not exist into the question: &lt;em&gt;Zorbaxapril&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In my runs, the model on its own scored 5 of 8 and Domino scored 8 of 8. The plain model found the obvious link but missed the cascades that take several steps, like furosemide → leaking urine → oxybutynin. And it called Zorbaxapril "an ACE inhibitor" and warned it was unsafe with her other drugs: a confident answer about a medicine that was never real. Domino looked Zorbaxapril up in the graph, didn't find it, and said so.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not in the graph is not the same as safe.&lt;/strong&gt; That line became a rule across the whole product: any medicine a question, an EHR order or a pasted list names is resolved against the graph's full-text index, and one Domino doesn't know is named as not checked, never guessed at.&lt;/p&gt;

&lt;p&gt;The page shows the exact prompt and the model's unedited answer, so anyone can read it and judge for themselves. Results vary from run to run, which is the point of running it live.&lt;/p&gt;

&lt;h2&gt;
  
  
  Memory and coordination, in the graph
&lt;/h2&gt;

&lt;p&gt;Agents hand off through &lt;code&gt;Handoff&lt;/code&gt; nodes. Each handoff is signed with HMAC-SHA256, written to FalkorDB, read back by the next agent and verified; a failed check stops the run. Before a review starts, the runner takes a lease on the patient in the graph: one &lt;code&gt;MERGE&lt;/code&gt; with a guarded &lt;code&gt;SET&lt;/code&gt;, atomic because FalkorDB runs writes one at a time per graph. Start twelve reviews of one patient at once and exactly one runs.&lt;/p&gt;

&lt;p&gt;Memory comes in four kinds, all of them nodes and edges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Episodic.&lt;/strong&gt; Every run, every tool call (&lt;code&gt;Step&lt;/code&gt;) and the catalog &lt;code&gt;Query&lt;/code&gt; nodes it ran, every finding and handoff. Each run starts by saying what changed since the last review.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantic.&lt;/strong&gt; Clinicians mark a link &lt;em&gt;confirmed&lt;/em&gt;, &lt;em&gt;intentional&lt;/em&gt; or &lt;em&gt;not a cascade&lt;/em&gt;. Those signed &lt;code&gt;Decision&lt;/code&gt; nodes hang off the patient and off the &lt;code&gt;Pattern&lt;/code&gt;, so feedback accumulates across patients. Agents have no tool that writes one; a test writes a forged decision straight into FalkorDB and checks the agents ignore it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Procedural.&lt;/strong&gt; A &lt;code&gt;Procedure&lt;/code&gt; node says who a review task goes to, how soon, and whether a "possible" link gets one at all. Every task a person approves or dismisses is an outcome; when the outcomes favour something else, a new version &lt;code&gt;SUPERSEDES&lt;/code&gt; the old one and links to every task it &lt;code&gt;LEARNED_FROM&lt;/code&gt;. At my demo clinic, prescribers acted on calcium-channel-blocker swelling tasks that pharmacists kept passing on, so those tasks now go to the prescriber, and the inbox says "routed by learned procedure v1".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conversations.&lt;/strong&gt; Every question and answer is a &lt;code&gt;Message&lt;/code&gt; in a &lt;code&gt;Conversation&lt;/code&gt; owned by one person, so "what could replace it?" resolves days later.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And because all of it is edges, &lt;strong&gt;provenance is a traversal&lt;/strong&gt;: from any finding, back to the step that wrote it, the queries that step ran, the evidence and decisions it used, and forward to the handoffs that carried it and the tasks it led to. The workspace draws that as &lt;em&gt;How Domino got here&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A care-team brain
&lt;/h2&gt;

&lt;p&gt;Once tasks, notes, decisions and conversations live in one graph next to the prescriptions, the clinic itself becomes queryable. Who prescribed what (&lt;code&gt;Prescriber&lt;/code&gt; nodes behind every &lt;code&gt;TAKES&lt;/code&gt;), who on the team has handled each kind of cascade, where cascades cross from one prescriber to another, and one search over everything the team wrote, by words (full-text index), meaning (vector index) and the graph. A brain agent answers questions about all of it over MCP and cites every query. Each clinic is its own FalkorDB graph, so none of this leaks between tenants.&lt;/p&gt;

&lt;h2&gt;
  
  
  Did it work?
&lt;/h2&gt;

&lt;p&gt;I generated 600 synthetic patients with 632 hidden cascades, plus the confounders a real record has: the same drugs prescribed for real diagnoses, deliberate pairs (stomach protection with an NSAID), and drugs started in the wrong order. Then I turned the graph signals on one at a time:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Precision&lt;/th&gt;
&lt;th&gt;Recall&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Graph links only&lt;/td&gt;
&lt;td&gt;0.30&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;+ time order&lt;/td&gt;
&lt;td&gt;0.44&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;+ onset window&lt;/td&gt;
&lt;td&gt;0.59&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;+ other reasons&lt;/td&gt;
&lt;td&gt;0.58&lt;/td&gt;
&lt;td&gt;0.96&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Domino&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.75&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.96&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;And zero of the 227 deliberate pairs were flagged. The honest caveat: the generator and the engine share one knowledge graph, so this tests the reasoning, not clinical truth. The interesting row is "+ other reasons": a charted indication raises confidence in real cascades &lt;em&gt;and&lt;/em&gt; in deliberate pairs, so it doesn't help on its own. Only the pattern layer, which knows gastroprotection is good care, separates them.&lt;/p&gt;

&lt;p&gt;Then the question that matters more: would it have &lt;em&gt;prevented&lt;/em&gt; them? I replayed every prescription in the cohort as the record stood the day it was written, with the prescriber check in the way. It would have stopped 401 of the 534 cascade drugs it met at the first downstream prescription, and 510 prescriptions might never have been written. 95% of its cascade stops were real cascades. The same check runs inside an EHR as a CDS Hooks 2.0 service: sign a new order, and Domino can pause it with a card that explains why.&lt;/p&gt;

&lt;p&gt;I also checked the medical knowledge against the real world. A script pulls current FDA drug labels from openFDA and looks for every side effect the graph claims: 249 of 265 (94%) are named in the label. The other 16 are listed in the repository with the reason for each, mostly class effects in older adults that labels word differently. Diuretic labels say "increased and urgent urination", not "incontinence", but in an 84-year-old that's what it becomes. I'd rather show that list than claim 100%.&lt;/p&gt;

&lt;p&gt;On a single FalkorDB instance on my laptop, Domino runs about 100 to 125 full patient analyses a second, about 1,000 to 1,250 graph queries a second.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Let an algorithm decide, and say why.&lt;/strong&gt; FalkorDB's &lt;code&gt;algo.betweenness&lt;/code&gt; found the chokepoints: the symptoms and drugs that sit on the most cascade routes. Among drugs, those chokepoints now set priority: a review task for a cascade through one is due in half the usual time, and the task says so. A ranking you can explain beats a ranking you have to trust.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sequence symmetry without a graph is noisy.&lt;/strong&gt; "Anticoagulant then beta blocker" is a strong signal in my cohort, and it's just the normal order of care for atrial fibrillation. Joining each signal to a mechanism path in the graph is what separates cascades from care pathways.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The best place to break a cascade is before it starts.&lt;/strong&gt; The prescriber check (type a new drug, see if it treats a side effect of something already taken) became one of the most useful screens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fact-check the model with the graph.&lt;/strong&gt; In one run, the model wrote a handoff calling furosemide a "chokepoint". It isn't. Now every handoff that says "chokepoint" is checked against the betweenness result before it is signed, and a wrong claim is flagged.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An honest gap beats a perfect score.&lt;/strong&gt; The evaluation's caveats, the 16 unmatched label edges and the comparison's "results vary" note are all on the page. That's what makes the rest believable.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Open &lt;a href="https://domino.gautamkhosla.com" rel="noopener noreferrer"&gt;domino.gautamkhosla.com&lt;/a&gt; and press &lt;em&gt;Take the 3-minute tour&lt;/em&gt;, or press &lt;em&gt;Run it live&lt;/em&gt; to see the comparison for yourself. Six synthetic patients each tell a different story, including Dorothy: same two drugs as Beatrice, but her furosemide came first, for heart failure. Domino checks, finds the reasons, and stays quiet. That's the one I'd show a pharmacist first.&lt;/p&gt;

&lt;p&gt;The code, the graph model and every Cypher query are on &lt;a href="https://github.com/GautamTalksDev/domino" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, and the demo is on &lt;a href="https://youtu.be/9OHJUJJjd0Q" rel="noopener noreferrer"&gt;YouTube&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Domino is a hackathon prototype on synthetic data. It raises questions for clinicians, and it never tells anyone to stop a medicine. I built it with an AI coding assistant (Claude, by Anthropic); the README says what I did myself.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>falkordb</category>
      <category>ai</category>
      <category>graphdatabase</category>
      <category>healthtech</category>
    </item>
    <item>
      <title>The bug a security tool must never have</title>
      <dc:creator>Gautam K</dc:creator>
      <pubDate>Mon, 05 Oct 2026 17:48:48 +0000</pubDate>
      <link>https://dev.to/gautamtalksdev/the-bug-a-security-tool-must-never-have-j5l</link>
      <guid>https://dev.to/gautamtalksdev/the-bug-a-security-tool-must-never-have-j5l</guid>
      <description>&lt;p&gt;My security tool said SUCCESS 100 times. It kept 12.&lt;/p&gt;

&lt;p&gt;That's not a figure of speech. It's the first entry in the changelog of mcp-pin, the tool I built and released in September, and it's the reason the changelog entry for version 0.1.0 ends with one line: &lt;strong&gt;Do not use it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This post is about that bug: what happened, why it's the worst possible failure for this kind of software, and what I changed so it can't come back quietly.&lt;/p&gt;

&lt;p&gt;If you'd rather watch than read, here's the 7 minute film about this bug and the problem mcp-pin exists for:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/tGtbDNr9qvE" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  What mcp-pin is for
&lt;/h2&gt;

&lt;p&gt;AI agents use tools through MCP servers. Each tool comes with a description, and the model reads that description as instructions. You approve a tool once. Your agent re-reads its description at the start of every session, and nothing tells you if it changed in between.&lt;/p&gt;

&lt;p&gt;mcp-pin is a small proxy that sits between your client and a server. The first time it sees a server, it fingerprints every tool (name, description, input schema, annotations) and pins that fingerprint. Every session after that, it checks. If anything changed, the session is blocked and you get a diff.&lt;/p&gt;

&lt;p&gt;So the whole product is one promise: &lt;strong&gt;I will remember what you approved.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The test
&lt;/h2&gt;

&lt;p&gt;I ran one hundred pins at the same time, for one hundred different servers. Every single one reported success.&lt;/p&gt;

&lt;p&gt;Then I looked at what was actually on disk:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;log.ndjson&lt;/code&gt; had 100 entries with 100 unique server ids.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pins.json&lt;/code&gt; had 12 keys.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Eighty-eight approvals were gone. The hash chain in the log also broke five times, because log appends had the same problem.&lt;/p&gt;

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

&lt;p&gt;0.1.0 kept every pin in one file, and updated it like this (simplified):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pins&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PINS_FILE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;pins&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;serverId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PINS_FILE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pins&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the file, add your key, write the file back. With one process that's fine. With two processes at the same moment, both read the same old file, both add their own key, and the second write erases the first. That's a lost update, one of the oldest bugs there is. With a hundred processes, most of them lose.&lt;/p&gt;

&lt;p&gt;Nothing threw an error. Each process wrote its own version of the file successfully. Each one printed that it had pinned the server. Each one was telling the truth about its own write and wrong about the outcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is the worst bug a tool like this can have
&lt;/h2&gt;

&lt;p&gt;A tool that crashes makes you go and look. A tool that fails loudly gets fixed.&lt;/p&gt;

&lt;p&gt;A tool that reports success while dropping the thing it exists to keep does the opposite. It makes you stop looking. You were careful, you put a pin in front of your most dangerous server, and the tool told you it was handled.&lt;/p&gt;

&lt;p&gt;Here's what a lost pin means in practice. To the proxy, a server with no pin looks like a first run, and a first run pins whatever the server says that day. So if the server changed its tool after you approved it, which is the exact attack mcp-pin exists to catch, the next session would quietly accept the new version as the baseline. No block. No diff.&lt;/p&gt;

&lt;p&gt;A version of it that loses approvals while telling you everything is fine is worse than not having it, because you'd trust it. That is the worst possible failure mode for this category of software, and I shipped it in the first version.&lt;/p&gt;

&lt;p&gt;The irony isn't lost on me. mcp-pin catches a change that happens with no signal. This bug was a change that happened with no signal.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I changed
&lt;/h2&gt;

&lt;p&gt;The fixes are in 0.1.1 and 0.1.2. None of them are clever, which is the point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One file per server, replaced atomically.&lt;/strong&gt; Each pin now lives in its own file, &lt;code&gt;pins.d/&amp;lt;server_id&amp;gt;.json&lt;/code&gt;. It's written to a temp file, fsynced, and renamed into place. Two different servers can't touch the same file anymore, and a reader never sees half a write.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The log takes a lock.&lt;/strong&gt; Appending to the log means taking an exclusive lock, reading the tail, appending, fsyncing, and unlocking. Two processes can't extend the hash chain at the same time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Windows contends differently.&lt;/strong&gt; The lock is a file opened with the exclusive create flag. On Linux, a held lock shows up as &lt;code&gt;EEXIST&lt;/code&gt;. On Windows it can also be &lt;code&gt;EPERM&lt;/code&gt;, &lt;code&gt;EACCES&lt;/code&gt; or &lt;code&gt;EBUSY&lt;/code&gt;, and 0.1.1 only expected the first. That shipped in a tagged release, which I withdrew before it reached npm; 0.1.2 handles all four.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The record comes first.&lt;/strong&gt; The first pin used to write the pin file and then the log entry. If the append failed, you had a trusted pin with no public record of it. The test matrix caught it on Windows with Node 20: 16 pin files, 15 log lines. Now the log entry is the commitment, and the pin is written only after it succeeds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Corruption fails closed.&lt;/strong&gt; 0.1.0 treated a truncated &lt;code&gt;pins.json&lt;/code&gt; or a garbage log as "no pins yet". For a pinning tool that's the same lost-pin failure through a different door. Now corrupt state is a hard error that names the file. A missing file is still allowed, because that's a genuine first run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The client waits until the check is done.&lt;/strong&gt; 0.1.0 forwarded the client's first message straight away and checked the tools in parallel, so a fast client could call a tool before the block arrived. Now client messages are queued from the start, the client gets no answer until the tools match the pin, and on drift the queue is thrown away.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell anyone building a security tool
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Test the concurrent path on day one.&lt;/strong&gt; The bug only existed when two writes overlapped, and real setups start several servers at once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Only claim success after the durable write.&lt;/strong&gt; "Pinned" should mean the bytes are fsynced and the record exists, not that a function returned.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fail closed when you can't read your own state.&lt;/strong&gt; If a security tool doesn't know what you approved, it should stop, not assume.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Write your failures down where users will see them.&lt;/strong&gt; The changelog for 0.1.0 doesn't soften it. If you're trusting a tool with your approvals, you should know how it has failed before.&lt;/p&gt;

&lt;h2&gt;
  
  
  See it work
&lt;/h2&gt;

&lt;p&gt;The whole failure the tool exists for, in about ten seconds, with a harmless bundled server and nothing to configure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx mcp-pin@0.1.4 demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first session pins a tool. The second session, the tool's description starts asking for notes from your conversation, and mcp-pin blocks it with the diff.&lt;/p&gt;

&lt;p&gt;The code, the changelog and the public log of tool changes are at &lt;a href="https://github.com/GautamTalksDev/mcp-pin" rel="noopener noreferrer"&gt;github.com/GautamTalksDev/mcp-pin&lt;/a&gt;. The film above covers the problem and both bugs I shipped while building it.&lt;/p&gt;

&lt;p&gt;If you run it and something looks wrong, open an issue. I'd much rather hear about it loudly.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>security</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>We are building an OS for AI-built software. Here's what that means</title>
      <dc:creator>Gautam K</dc:creator>
      <pubDate>Thu, 09 Apr 2026 02:59:35 +0000</pubDate>
      <link>https://dev.to/gautamtalksdev/we-are-building-an-os-for-ai-built-software-heres-what-that-means-2gk</link>
      <guid>https://dev.to/gautamtalksdev/we-are-building-an-os-for-ai-built-software-heres-what-that-means-2gk</guid>
      <description>&lt;p&gt;Every AI code generator on the market solves the same problem: turning a prompt into code. Lovable, Bolt, v0, Replit Agent, they all race to generate faster, generate better, generate more. And they're good at it. The generation problem is increasingly solved.&lt;/p&gt;

&lt;p&gt;But generation is maybe 20% of the software lifecycle.&lt;/p&gt;

&lt;p&gt;The other 80%, deploying, monitoring, debugging, patching, scaling, and governing, still falls entirely on you. The moment your AI-generated app hits production, you're back in the same ops hell that existed before AI: waking up at 2am to a PagerDuty alert, tracing through logs, writing a fix, pushing to CI, hoping the deploy works. AI has made building software faster. It did nothing about maintaining it.&lt;/p&gt;

&lt;p&gt;That's the problem we are building Solen to solve.&lt;/p&gt;

&lt;p&gt;What Solen actually is&lt;br&gt;
Solen is a platform that manages the full lifecycle of AI-built software. You describe what you want in plain language. Solen interprets it, plans the architecture, generates the code, builds a container, deploys it to a live URL, monitors it in production, and when something breaks, diagnoses the failure and repairs it autonomously.&lt;/p&gt;

&lt;p&gt;Not a code generator. A control plane for autonomous software.&lt;/p&gt;

&lt;p&gt;The key difference from everything else in the market: Solen doesn't stop at deployment. Deployment is where it starts. The most valuable work happens after your app is live, keeping it alive, keeping it healthy, improving it based on what actually happens at runtime.&lt;/p&gt;

&lt;p&gt;The architecture: four layers, one closed loop&lt;/p&gt;

&lt;p&gt;Solen is built as four subsystems that form a continuous cycle.&lt;br&gt;
The intelligence layer takes your natural language description and converts it into a structured deployment plan. It classifies the app type dashboard, landing page, CRUD app, API, e-commerce store, and loads a category-specific generation pipeline that produces production-quality output, not generic boilerplate. This isn't one prompt to one response. It's a multi-stage pipeline: intent parsing, architecture planning, spec generation, prompt construction, code generation, diff analysis, build execution, self-heal checks, deployment, runtime verification, impact assessment, and final validation. Twelve stages, each with its own logic.&lt;br&gt;
The execution engine runs the build pipeline with durable job queues, retry logic, and real-time status events streamed to the UI. When a build fails, the error context is automatically fed back to the AI, which generates a corrected version and retries. Not blind retries, intelligent self-correction with the actual error in context.&lt;/p&gt;

&lt;p&gt;The deployment runtime builds containers from the generated code, assigns subdomains, provisions SSL, runs health checks, and returns a live URL. The oversight layer monitors every deployed application continuously. When it detects a failure, a crash, an error spike, an out-of-memory event, or degraded latency, it triggers the self-healing loop.&lt;/p&gt;

&lt;p&gt;The self-healing loop: the part nobody else has built&lt;br&gt;
This is Solen's core differentiator. Here's how it works:&lt;br&gt;
Monitoring detects an anomaly and fires a structured event to the diagnostic engine with full error context stack trace, recent logs, and affected component. The AI diagnoses the root cause and returns a structured assessment: what broke, why, and a confidence score. If confidence is high enough, it generates a targeted code patch. The build pipeline applies the fix and rebuilds the container. The deployment runtime does a health-gated redeploy to the same URL. Monitoring confirms the app is healthy again and logs the entire action chain to an immutable audit trail.&lt;/p&gt;

&lt;p&gt;The whole loop runs in minutes. Every action is recorded. Users choose supervised mode (approve fixes before they're applied) or autonomous mode (the platform handles it end-to-end). As of today, Solen has completed 9 autonomous self-heals with a 100% build success rate. No human intervention required.&lt;/p&gt;

&lt;p&gt;Why this matters beyond the demo&lt;br&gt;
The maintenance burden is the largest cost in software engineering. Industry estimates put 60-80% of engineering time on maintaining existing systems rather than building new ones. Every tool that helps you build faster just creates more software you have to maintain. Solen inverts that. The more software you deploy on it, the more the platform handles for you. Each self-heal makes the system smarter about that failure pattern. The runtime data feeds back into the intelligence layer. It's not a static tool, it's a living system that improves from the software it manages.&lt;/p&gt;

&lt;p&gt;This is what we mean by "operating system for AI-built software." Not a tool you use once. An infrastructure layer your software lives on.&lt;br&gt;
The agentic era needs this&lt;/p&gt;

&lt;p&gt;Here's the bigger picture. We're heading towards a world where AI agents build and deploy software autonomously. When Claude or GPT or any future agent needs to ship something it created, it needs infrastructure that can receive that deployment, run it safely, monitor it, and fix it when it breaks without a human in the loop. That's the infrastructure layer Solen is building toward. An API that any AI agent can call to deploy, monitor, and manage software. The control plane for the agentic era.&lt;/p&gt;

&lt;p&gt;Where it stands today&lt;br&gt;
The dashboard lets you describe an app, watch it build in real time through the 12-stage pipeline, and get a live URL. The activity page shows deploy stats, active processes, and self-heal history. Governance controls let workspace owners set agent policies for what the AI can do autonomously versus what requires approval.&lt;/p&gt;

&lt;p&gt;It's early. The waitlist is open. We are building this in public.&lt;br&gt;
If you've ever shipped an AI-generated app and then spent the next week babysitting it in production, you know the problem. Solen is the fix.&lt;/p&gt;

&lt;p&gt;solenai.ca&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devops</category>
      <category>webdev</category>
      <category>buildinpublic</category>
    </item>
  </channel>
</rss>
