Graph Hacks 2026 · WeMakeDevs × FalkorDB · Try it live: domino.gautamkhosla.com · 3-minute video · Code
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.
Every one of those prescriptions was reasonable on its own. Together, four of her nine medicines may exist only because of the first one.
Beatrice is synthetic. The pattern is not. Geriatricians call it a prescribing cascade: 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 after a calcium channel blocker than before it.
I built Domino, a team of AI agents that find these chains in a FalkorDB graph, prove every link, and hand a pharmacist one question: could the swelling be from amlodipine?
The insight: a cascade is a path
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 connection, because the facts are written by different people, months apart.
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:
MATCH (p:Patient {id: $pid})-[:TAKES]->(m:Drug)
WITH p, collect(m.id) AS meds
MATCH (p)-[:TAKES]->(a:Drug)-[:CAUSES]->(:Effect)-[:TREATED_BY]->(:Drug)<-[:TAKES]-(p)
WITH DISTINCT a, meds
MATCH path = (a)-[:CAUSES|TREATED_BY*2..6]->(z:Drug)
WHERE z <> a AND all(n IN nodes(path) WHERE NOT n:Drug OR n.id IN meds)
RETURN [r IN relationships(path) | [startNode(r).id, type(r), endNode(r).id]] AS hops
On FalkorDB this returns all forty of Beatrice's candidate paths in about a tenth of a second.
Modelling choices that made the queries simple
Point both edges forward. CAUSES goes drug → effect and TREATED_BY 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.
One node type for side effects and diagnoses. A cascade is a side effect being treated as a diagnosis, so "ankle swelling" is the same Effect node whether amlodipine causes it or a patient HAS it. That makes "does the second drug have another reason?" a two-hop pattern: (p)-[:HAS]->(c:Effect)-[:TREATED_BY]->(b).
Dates as integers. TAKES.start 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.
Knowledge as nodes, not code. The 27 documented cascades are Pattern nodes linked to drug classes and the effect between them. Matching a link to the literature is a traversal: (a)-[:IN_CLASS]->()<-[:INDEX]-(pt)-[:VIA]->(e). 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.
Agents that use the graph as their tools
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 GRAPH.RO_QUERY, so the database itself refuses a write, even if a bug or a prompt injection tries one.
- The Detective 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.
- The Pharmacist 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.
- The Scribe writes review tasks, an SBAR note for the prescriber and a plain-language note for the family.
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.
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.
Does the graph actually matter? I test it live
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. Same question. Same model. One has a graph.
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: Zorbaxapril.
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.
Not in the graph is not the same as safe. 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.
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.
Memory and coordination, in the graph
Agents hand off through Handoff 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 MERGE with a guarded SET, atomic because FalkorDB runs writes one at a time per graph. Start twelve reviews of one patient at once and exactly one runs.
Memory comes in four kinds, all of them nodes and edges:
-
Episodic. Every run, every tool call (
Step) and the catalogQuerynodes it ran, every finding and handoff. Each run starts by saying what changed since the last review. -
Semantic. Clinicians mark a link confirmed, intentional or not a cascade. Those signed
Decisionnodes hang off the patient and off thePattern, 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. -
Procedural. A
Procedurenode 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 versionSUPERSEDESthe old one and links to every task itLEARNED_FROM. 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". -
Conversations. Every question and answer is a
Messagein aConversationowned by one person, so "what could replace it?" resolves days later.
And because all of it is edges, provenance is a traversal: 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 How Domino got here.
A care-team brain
Once tasks, notes, decisions and conversations live in one graph next to the prescriptions, the clinic itself becomes queryable. Who prescribed what (Prescriber nodes behind every TAKES), 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.
Did it work?
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:
| Signal | Precision | Recall |
|---|---|---|
| Graph links only | 0.30 | 1.00 |
| + time order | 0.44 | 1.00 |
| + onset window | 0.59 | 1.00 |
| + other reasons | 0.58 | 0.96 |
| Domino | 0.75 | 0.96 |
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 and in deliberate pairs, so it doesn't help on its own. Only the pattern layer, which knows gastroprotection is good care, separates them.
Then the question that matters more: would it have prevented 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.
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%.
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.
What I learned
-
Let an algorithm decide, and say why. FalkorDB's
algo.betweennessfound 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. - Sequence symmetry without a graph is noisy. "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.
- The best place to break a cascade is before it starts. 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.
- Fact-check the model with the graph. 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.
- An honest gap beats a perfect score. 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.
Try it
Open domino.gautamkhosla.com and press Take the 3-minute tour, or press Run it live 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.
The code, the graph model and every Cypher query are on GitHub, and the demo is on YouTube.
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.
Top comments (0)