Reference
SMART on FHIR was designed for applications whose behaviour is fixed before anyone grants them access. You can read an app's code, certify it, and know what it will ask for. An agent decides at runtime what to fetch, and that decision is shaped by the data it has just read.
Two things follow, and most of this page is a consequence of them. The scope you grant is the scope the agent may fully use. And the data becomes an input to control flow, which means retrieved content is untrusted in a way a database row never was.
Written from building HealthClaw, an open-source guardrail layer for exactly this problem.
01 / Patterns
The choice here determines what every later control can do. Policy you cannot enforce at the connection point has to be enforced in a prompt, and a prompt is a request rather than a constraint.
| Pattern | Shape | Policy granularity | Audit |
|---|---|---|---|
| Policy broker | Agent → broker → FHIR server | Per call | Central, complete |
| Scoped SMART app | Agent → FHIR server, OAuth token | Per session | Server-side, coarse |
| MCP server over FHIR | Agent → tools → FHIR server | Per tool call | Per tool call |
| Direct database or warehouseanti-pattern | Agent → SQL | None meaningful | Query logs at best |
02 / Scopes
SMART App Launch 2.0 replaced the coarse .read and .write with five letters — create, read, update, delete, search — which must appear in that order. The distinction matters for agents specifically: reading a known resource and searching across a type are different capabilities, and an agent exploring a record does far more of the second.
# Too broad. The agent may read and search every resource type
# in the compartment, for the life of the token.
patient/*.read
# Better. Read and search, two resource types, nothing else.
patient/Observation.rs
patient/Condition.rs
# Better still. Constrain with search parameters, so the grant
# cannot be widened by changing the query.
patient/Observation.rs?category=http://terminology.hl7.org/CodeSystem/observation-category|laboratory
# A write path, granted separately and deliberately.
patient/ServiceRequest.cRead access to the wrong resource is a privacy incident. Write access to the wrong resource changes a clinical record. Grant c, u and d in a separate token from the read path, so that widening the agent's reading never silently widens what it can change.
03 / Redaction
HIPAA's minimum necessary standard predates the idea that a request would be assembled by a statistical model, but it maps onto it cleanly: the agent should receive the least data that lets it do the job. The practical difficulty is that FHIR resources carry identity in more places than people expect.
Nearly every resource may carry a text.div — an XHTML restatement of its contents for human display. Strip Patient.name and leave the narrative untouched and you have redacted nothing; the name is still there, in markup, one field over.
{
"resourceType": "Patient",
"id": "example",
"text": {
"status": "generated",
"div": "<div xmlns=\"http://www.w3.org/1999/xhtml\">
Jane Q. Doe, MRN 44xxxx19, born 1974-03-02
</div>"
},
"name": [ { "_family": { "extension": [ /* redacted */ ] } } ],
"identifier": [ /* redacted */ ]
}The other reliable carriers are Patient.telecom, Patient.address and every free-text note on a clinical resource. Dates of service are quasi-identifiers too: a handful of them plus a postcode re-identifies a person more often than teams assume.
04 / Audit
A conventional access log answers “which client read this record.” For an agent that is not enough to answer the question you will actually be asked, which is why it read the record and who was accountable for the result.
FHIR gives you two resources for this and they do different jobs. AuditEvent records that an access happened. Provenance records where a piece of data came from, which is what you need when an agent contributes to the record rather than only reading it.
At minimum, an agent action should be reconstructable from the audit trail alone: which model and version, which system prompt revision, which tool was called with which parameters, which human approved it if a human did, and which patient compartment it ran in. If you cannot answer those from storage six months later, you do not have an audit trail — you have logs.
05 / Approval
The safest agent write path does not write. It proposes: the agent produces a candidate resource, the broker holds it, and a human approves or rejects it before anything reaches the FHIR server. The approval is itself recorded, so the clinical record carries a human's accountability rather than a model's.
This is unglamorous and it is the control that survives contact with a compliance review. It also degrades well: when the model is wrong, the failure is a rejected proposal rather than a corrected chart.
06 / Failure modes
Ordered roughly by how often they appear in real implementations rather than by severity.
Clinical free text is authored by many parties, and some of it is patient-supplied. A DocumentReference attachment, an Observation.note, or a Condition.note can contain text that reads as instruction to a model. The mitigation is architectural, not lexical: never let retrieved content reach the model in a position where it can be interpreted as instruction, and never give the model a tool whose blast radius you would not accept being triggered by a sentence in a scanned note.
Almost every FHIR resource can carry a text.div narrative — an XHTML restatement of the resource intended for human display. Redacting Patient.name and Patient.identifier while passing the resource through untouched leaves the same identifiers sitting in the narrative block. Redaction has to run over narrative and structured fields both, or it is theatre.
Patient/$everything returns the entire patient compartment in one call, and _include / _revinclude quietly widen a search well past what the query appears to ask for. An agent optimising for "have enough context" will find these. Bound them at the broker, not in the prompt.
A long-running session that touches two patients can carry the first one’s data into reasoning about the second. Bind a session to a patient compartment and make crossing it require a new session rather than a new instruction.
A token minted generously for one workflow gets reused for the next one because it is already there and it works. Mint narrowly, expire aggressively, and make issuing a new token cheaper than widening an old one.
Next
HealthClaw is the broker pattern described above, built in the open. Redaction, audit and human sign-off are enforced server-side rather than requested in a prompt, which is the whole argument of this page reduced to running code.