The evidence map: why we built a graph instead of a chat
Clinical LLMs hallucinate when they reason against a transcript. They stop when they reason against a graph. Here is how the evidence map is structured and why every claim carries a receipt.

The first version of Nyra, the one we built and then rebuilt, was a chat. A patient reflected, the model summarized, the clinician read the summary. It worked well enough at the level of a demo. It fell apart the first time a clinician asked: where did that claim come from?
The chat surface had no answer. The model had read fourteen days of reflections and produced a paragraph. The paragraph was internally consistent. It was also impossible to verify, impossible to audit, and impossible to delete a single piece of evidence from without re-running the whole summarization.
That experience is why the current Nyra is a graph, not a chat. This post explains the structure of the graph, why every claim in it carries a source receipt, and what the design choice buys at the cost of a few extra database tables.
The two failure modes of clinical LLMs
Before getting into structure, the diagnosis. Production clinical LLM tools fail in two recognizable shapes.
Fabrication. The model says something that did not happen. "The patient reports improved sleep over the last week." The patient said nothing of the kind. The model interpolated from patterns in its training data and produced a plausible sentence with no anchor in the patient's actual reflections. A clinician who trusts the sentence is now operating on synthetic evidence.
Drift. The model says something that happened on day 3 and attributes it to day 12. The fact is true; the citation is wrong. A clinician building a treatment plan around a "recent" event that is actually two weeks stale is making a decision against the wrong reality.
Both failure modes share a root cause. The model was asked to reason against an unstructured corpus (the transcript of reflections, scale responses, and clinician notes) and produce a structured judgment (a summary, a plan, a recommendation). The transformation has no traceable path from output back to input. There is no way to ask, of any sentence in the output, which input it came from.
A graph closes that path by structure.
The shape of the graph
The Nyra evidence map is a directed graph with five node types:
Reflection nodes represent a single patient reflection: a free-text or voice-transcribed turn from a given day. Each carries an identifier, a timestamp, and the verbatim text.
Scale nodes represent a single administration of a validated instrument. PHQ-9 day 1, GAD-7 day 7, etc. Each carries the total score, the item-level responses, and the instrument version. Scale nodes are immutable; the underlying instruments are version-pinned and re-validated, never edited inline.
Claim nodes represent a single piece of clinical evidence inferred from one or more reflections or scales. "Sleep disruption signal, days 3 to 6." "Anhedonia present, items 1 and 2 of PHQ-9 day 7." Each carries a short summary, a confidence band (low and high, not a point), and a category from a closed taxonomy.
Audit nodes represent a single event: a patient submission, a classifier output, a scale capture, a clinician acknowledgment. Each carries a timestamp, an actor identity, and a hash of the prior audit node, which gives the audit chain its append-only property.
Clinician-action nodes represent a single committed change: a note, a plan, a follow-up. Only a licensed clinician can create one. The commit boundary is enforced at the auth layer, not in the prompt.
The edges between these nodes carry the trust relationships. A claim node cites one or more reflection nodes. A reflection node produced one or more claim nodes. An audit node records the creation of any other node. A clinician-action node references claim and reflection nodes. The graph is structured so that every claim, viewed from any direction, can be walked back to the verbatim patient turn that produced it.
We expose the read surface of this graph as the evidence_map.read function call in the agent SDK. An agent that reads a claim node also reads, by edge traversal, the reflection that produced it. The agent cannot output a claim without the source attached because the source is structurally inseparable from the claim.
What the API returns
Concretely, a single GET /v1/patients/:id/evidence-map returns a JSON-LD document that looks like this:
{
"patient_id": "pt_01h7QZ8Y2X",
"window": { "from": "2026-05-07", "to": "2026-05-20" },
"claims": [
{
"id": "clm_sleep_dis_03",
"summary": "Sleep disruption signal, days 3 to 6.",
"confidence": { "low": 0.62, "high": 0.81 },
"source": {
"reflection_id": "rfl_4a91",
"day": 4,
"snippet": "Up at 4 again, can't shut off..."
},
"audit_ref": "evt_8c12"
}
],
"scales": [
{ "instrument": "phq9", "day": 7, "score": 11, "item_9": 0 }
]
}
Three things to notice in that shape.
The confidence is a band, not a point. A model that returns a single confidence score of 0.74 is making a precision claim it cannot back up. A model that returns 0.62 to 0.81 is being honest about the range of plausibility. A clinician reading the claim treats the band the way they treat any clinical confidence interval, with appropriate uncertainty.
The source has a snippet. Not just an identifier. The reflection text fragment is inline so a clinician can read the source claim without making a second API call. This trades a little payload size for a lot of trust.
The audit_ref points at the immutable event log. If anyone, at any point, asks "what did the model see when it produced this claim," the answer is in the audit chain, hash-verifiable, never rewritten.
What this prevents
A few categories of failure that the chat version of Nyra allowed and the graph version does not.
Silent revision. In the chat version, every refresh re-summarized the conversation from scratch. The summary changed subtly between visits, in ways neither the clinician nor the patient could prove. In the graph version, claim nodes are immutable. A new reflection produces new claim nodes; old ones are not retroactively revised. A clinician who reads "sleep disruption signal" on Monday reads exactly the same claim on Friday.
Untraceable deletion. If a patient asks to delete a reflection, the chat version had no clean answer; the summary it produced was downstream of every reflection in the window. In the graph version, deleting a reflection deletes the claim nodes that cited it as their source. The deletion is verifiable. The clinician's evidence map updates with the missing source crossed out, not silently rewritten.
Cross-clinic leakage. Each evidence map is scoped to a single patient inside a single clinic. The agent's OAuth identity carries the clinic scope. The graph traversal cannot cross into another clinic's data, by structure, not by hope.
Hallucinated citations. This is the headline reason for the rewrite. In the graph, an agent cannot output a claim without a source attached because the source field is required by the schema. An agent attempting to produce a claim with no source is rejected at the API boundary. The category of "the model said something with no anchor" is closed.
What the graph still cannot do
Honest about the limits.
The graph does not eliminate model error inside a single claim. If the classifier reads "Up at 4 again, can't shut off the work spiral" and produces a claim of "sleep disruption, work-related stress, days 3-6," the underlying classification could still be wrong on the work-related part. The graph guarantees that the claim cites the source reflection; it does not guarantee that the claim correctly interprets it.
The graph does not eliminate the need for a clinician. Every claim is decision support; nothing in the structure makes a diagnosis. The commit boundary that requires a clinician to promote any draft to the live record is the architectural reflection of that constraint.
The graph does not solve for the long tail of clinical context. A patient's history with a particular medication, a family history of bipolar disorder, the relationship to a specific therapist: these live in the EHR and the clinician's head, not in the 14-day evidence map. The map is what happens between visits, not what happened before them.
The agent surface
For builders, the practical implication is that every read of the evidence map carries its own receipts. An agent that reads a claim has read the source reflection by structure. An agent that proposes a draft note must reference the claims it relied on, by identifier. The draft cannot be committed by the agent; it sits in a sandbox until a clinician (the only principal with the commit scope) promotes it.
Our /agents page walks through the full read and write surface, including the function-call schemas for evidence_map.read, simulation.run, and draft.create. The agent SDK docs cover OAuth, DPoP, audit, and the rate-limit posture. The shape of the read response above is the contract; the rest of the API is documented at the same depth.
Where to go next
If you are building a clinical agent and want sandbox access to the evidence-map API, the path is in the agent quickstart on the docs site. The sandbox tier is synthetic-only and does not require a clinic agreement.
If you are a clinician evaluating decision-support tools and the failure modes above sound familiar, the book-a-demo walkthrough includes a live read against a synthetic evidence map. The walkthrough takes thirty minutes and we answer questions, not pitch.