Here is how we did it, Edgemaxxing is really good (for performance).
Background you need first
What is a graph database?
A graph database stores two things:
- Nodes: things. For example: a paper, a finding, a hypothesis.
- Edges: connections between two things. For example: "this finding is evidence for this hypothesis".
Both nodes and edges can hold properties, which are small pieces of data such as year: 2026 or stance: "refutes".
FalkorDB is a graph database. You talk to it with a query language called Cypher. A Cypher pattern looks like a drawing:
(finding)-[:EVIDENCE_FOR]->(hypothesis)
That reads as "a finding, connected by an EVIDENCE_FOR edge, to a hypothesis".
Terms
These are relevant to the problems we are solving so its good to know, But it should work generally for other use cases as well!
| Word | Meaning | Example |
|---|---|---|
| Question | What the user types | "creatine on intelligence vs muscle gain" |
| Hypothesis | One testable claim from the question | H2: "Creatine increases muscle gain in adults" |
| Paper | A published article | Chen 2026 (a meta-analysis) |
| Study | One experiment inside a paper | An RCT with 15 vegans |
| Finding | One measured result from a study | "Skeletal muscle mass: no change" |
| Concept | A topic or measurement | "lean mass" |
Part 1: Edgemaxxing begins
The problem
Lets look at an example "Lean mass increased by 0.37 kg."
- For a hypothesis H2 (creatine increases muscle gain), this finding supports it.
- For a different hypothesis, such as "creatine harms kidneys", it is not relevant.
So "supports" is not a fact about the finding alone. It is a fact about the finding together with one hypothesis.
The bad solution
Store stance: "supports" on the finding node. Then the second hypothesis breaks: it sees "supports" too, which is wrong. You would have to copy the finding once per hypothesis, and the copies would drift apart.
What we did
We put the stance on the edge between the finding and the hypothesis i.e Edgemaxxing:
(Finding: "Lean mass ↑ 0.37 kg") -[:EVIDENCE_FOR {stance: "supports", weight: 0.67}]-> (H2)
- There is one finding node.
- There is one edge per hypothesis, and each edge has its own stance.
On the finding, “supports” can't say which hypothesis it means. On the edge, it can.
Relevant bit: many databases can't store properties on a connection, or make it awkward. FalkorDB is a property graph, so edges can hold data just like nodes can. This one feature makes the whole design possible.
Why this matters
Papers and findings are shared across all questions. When a new question needs a paper that was already read, the app does not read it again. It only adds new EVIDENCE_FOR edges. That saves LLM cost and time.
Part 2: Make each contradiction an edge, tied to one hypothesis
The problem
Two findings can disagree for one hypothesis and not for another.
- For a hypothesis H2, "lean mass ↑" (Naddafha 2026) and "skeletal muscle mass: no change" (Chen 2026) disagree.
- For a kidney hypothesis, the same two findings say nothing, so there is no disagreement.
What we did
Edgemaxxing again! A contradiction is an edge between two findings, and it records which hypothesis it belongs to:
(Naddafha finding) -[:CONTRADICTS {hypothesis: "H2", reason: "different population",
explained_by: "postmenopausal women vs all women"}]-> (Chen finding)
The query:
MERGE (a)-[e:CONTRADICTS {hypothesis:$k}]->(b)
SET e.reason = ..., e.severity = ..., e.explained_by = ...
Relevant bit:
MERGEmeans "create it only if it does not exist yet".
The index
To show the contradictions for H2, the app asks: "give me every CONTRADICTS edge where hypothesis = H2". Without help, FalkorDB would have to check every contradiction edge in the whole graph.
So we created an index on that edge property:
CREATE INDEX FOR ()-[r:CONTRADICTS]-() ON (r.hypothesis)
Relevant bit: an index is like the index at the back of a book. Instead of reading every page, you jump straight to the right page. Most databases only index nodes or rows. FalkorDB can also index edges, which is what makes this lookup fast.
Part 3: Recognise the same outcome under different names
The problem
Paper A says "fat-free mass". Paper B says "lean body mass". Paper C says "FFM". These are almost the same measurement. If the app only matched exact words, it would miss real disagreements between these papers.
What we did
- Each Concept node has a
synonymslist (other names for the same thing). - When the Stance agent (an LLM) judges a finding, it also answers: "does this finding measure the hypothesis's outcome, even under another name?" (
same_outcome: true/false). - If yes, the code adds a special edge from the finding to the hypothesis's own outcome concept:
(Finding: "FFM ↑ 1.15 kg") -[:MEASURES {hypothesis: "H2"}]-> (Concept: "muscle gain")
- The code also adds the finding's wording to the concept's synonyms. Next time, "FFM" is already known as a name for that outcome, with no LLM needed.
Relevant bit: this
MEASURESedge also carrieshypothesis, for the same reason as Part 2: "FFM counts as muscle gain" is true for H2, but maybe not for some other hypothesis. It has an edge index too. Again Edgemaxxing!!!!!
Why this matters
The graph learns names over time. Every question makes the synonym lists better for the next question.
Part 4: Contradiction camps with algo.WCC
What it does
Imagine the contradiction edges as strings tied between findings:
- If A contradicts B, and B contradicts C, then A, B and C are all tied together.
- Each group of tied-together findings is one "camp": one area where the evidence argues with itself.
WCC ("weakly connected components") is the standard algorithm that finds these groups.
Relevant bit: FalkorDB ships graph algorithms built in, so the app calls
CALL algo.WCC(...)inside the database. It doesn't have to download the whole graph and run the algorithm in Python.
The query:
CALL algo.WCC({nodeLabels: ['Finding'], relationshipTypes: ['CONTRADICTS']})
YIELD node, componentId
MATCH (node)-[e:EVIDENCE_FOR]->(h:Hypothesis)<-[:ASKS]-(:Question {id:$q})
WITH h, componentId, count(DISTINCT node) AS findings, collect(DISTINCT e.stance) AS stances
WHERE findings > 1
RETURN h.statement AS hypothesis, componentId AS camp, findings, stances
In plain words: find the camps across all findings, then keep only the ones in this question, and show each camp's size and stances.
One limitation: WCC runs over all
CONTRADICTSedges, for every hypothesis. If one finding has contradictions under two different hypotheses, those two camps can join into one.
How it all fits together
Paper ─DESCRIBES→ Study ─FOUND→ Finding ─EVIDENCE_FOR {stance}→ Hypothesis
│
├─MEASURES {hypothesis}→ Concept {synonyms}
│
└─CONTRADICTS {hypothesis, reason, explained_by}→ Finding
Parting Notes
Edges are the core feature of graph dbs. Using them effectively can make your life easier (and faster!!!).





Top comments (0)