LLMs write plausible code, and they write it fast. But anyone who has driven a coding agent through more than a toy project knows the failure mode: give it enough rope and it drifts. Field names shift between sessions, the data model mutates, yesterday's conventions get quietly reinvented. For a landing page, that's an annoyance. For healthcare software, "winging it" is not an option.
We spent some time at Health Samurai's Lab testing a hypothesis: FHIR is not just a data standard — it's an unusually good framework for AI-assisted development. This post walks through why, and what happened when we built the same product twice to find out.
Agentic coding needs rails
Point a capable agent at a generic stack and four problems show up again and again:
- Agents drift. Without a fixed model, every session invents slightly different field names, shapes, and conventions. Changes stop composing — each new feature fights the last one.
- Hallucinated data shapes. Ask an agent to "add allergies" and it will cheerfully invent a schema. Nothing rejects an invalid structure until it reaches production and someone notices the data is wrong.
- No self-validation. A generic stack gives the agent no way to check its own output beyond "does it compile?" — which is not the same question as "is this correct healthcare data?"
- Generic frameworks don't know healthcare. React, Rails, and Django know nothing about
Patient,Encounter, orObservation. Every project reinvents the same domain modeling from scratch, and the agent reinvents it a little differently each time.
The common thread is the absence of rails — a strong, opinionated framework that constrains what the agent can produce and tells it immediately when it's wrong. The better the rails, the less room the agent has to drift, and the faster it can course-correct.
FHIR as a framework
Here's the insight: FHIR already provides almost all of those rails, and it provides them for healthcare specifically. A fixed data model answers the drift; server-side validation answers the hallucinated shapes and gives the agent a way to check itself; and the whole thing is healthcare-native by construction, so nothing has to be re-modelled per project.
A FHIR-native stack has three parts working together — the FHIR server is the backend, the app is built on FHIR SDKs, and the developer works alongside an AI copilot that already speaks FHIR. Most of that stack is ready before you write a line of application code; the only part you actually build is the app that wires the FHIR pieces together.
FHIR SDK
UI
Let's look at what each layer gives the agent.
A data model you inherit, not design
FHIR R4 defines 150+ healthcare resources — Patient, Condition, Observation, Encounter, MedicationStatement, DocumentReference, and on down the list. Stored natively by a FHIR server like Aidbox, they need no schema design at all.
This matters more than it first appears. When the agent needs to store allergies, it doesn't invent a table — it reaches for AllergyIntolerance, which already has the right fields, cardinalities, and bindings. Custom domain concepts become custom FHIR resources — in our own app, a PhrUser — with their own StructureDefinition, not a bespoke table with agent-invented columns. You inherit two decades of healthcare domain modeling, and so does the agent.
Validation as a feedback loop
Every write to a FHIR server is validated against the resource's StructureDefinition: cardinality, types, bindings, invariants. Invalid data never enters the database.
For a human developer that's a safety net. For an agent, it's something more valuable — a tight, precise feedback loop. Instead of a 500 error three layers deep, the agent gets back something like:
OperationOutcome: Patient.gender must be a code from
http://hl7.org/fhir/administrative-gender (male | female | other | unknown)
That's exactly the kind of signal an agent self-corrects on. Profile-based validation lets you tighten the rules for your own project — required fields, fixed coding systems — without writing a single line of validation code. The agent writes less, and the server tells it immediately when it's wrong.
Terminology, forms, and analytics — already solved
A FHIR server ships a lot of hard problems pre-solved, and each one is a problem the agent would otherwise try to hand-roll:
- Terminology.
ValueSet,CodeSystem, and operations like$expand,$validate-code, and$lookupare built in. LOINC, SNOMED, RxNorm, ICD — all reachable over standard FHIR endpoints. Your app doesn't ship a coding system; it queries one (for example, Termbox, a dedicated FHIR terminology server). Terminology stops being a "TODO: find a library" ticket. - SQL on FHIR. Define a
ViewDefinitionthat flattens FHIR resources into tabular columns, then query the view with plain SQL. Analytics teams get SQL for reporting; the data stays in FHIR — no transformation pipeline to write. It also lowers the bar for in-app dashboards: an app developer who would struggle to aggregate over nested FHIR resources can write a flatGROUP BYagainst a view, so a chart in the product and a report for the analysts read from the same definition. And aViewDefinitionis itself a FHIR resource, which means the agent can write one — declaring columns is a much narrower task than hand-rolling traversal logic over nested arrays. - Structured Data Capture (SDC). FHIR
Questionnaireresources describe forms; users fill them in and answers are stored asQuestionnaireResponse— linked, validated, and searchable like any other FHIR data. SDC implementations bring form builders, renderers, extraction logic, and galleries of ready-made forms.
Types and skills for the agent
The layers above constrain the data. Two more close the loop around the agent.
- Typed SDKs. Because every resource has a machine-readable
StructureDefinition, client types can be generated rather than written — for TypeScript, Python, C#, Java and others. The payoff for an agent is bigger than autocomplete: a misspelled field or a wrong enum fails at the type level, before any request is sent, and one generated source of truth is shared by server and client so both sides of a feature cannot drift apart. Publicly available generators make this a build step, not a project. - Agent skills. The newer layer is documentation written for agents instead of humans. Because FHIR is a public standard with public server APIs, this knowledge is reusable across projects — how to shape a search query, how access policies work, when to reach for SQL on FHIR — rather than something each team has to re-teach. Pointing an agent at current documentation also keeps it from leaning on whatever a model absorbed at training time, which for a spec that ships new versions is a real source of confidently wrong code.
Both are things you configure once and the agent then benefits from on every task.
The experiment: same app, different foundation
Theory is cheap, so we tested it. Our proving ground was Health Samurai's internal Personal Health Record (PHR) — a real product for our own team and the families they care for. Its scope is genuinely non-trivial: managing records for yourself and your dependents, entering conditions/medications/allergies/procedures, uploading medical PDFs, chat-based consultations with doctors, a patient summary, and AI assistance grounded in the patient's own FHIR data.
We built it twice with Claude Code — same scope, different foundation. The first time we let the agent build from scratch; the second time we rebuilt the whole app with FHIR as the framework.
| v1 — from scratch | v2 — on FHIR | |
|---|---|---|
| Stack | Plain React + Node + Postgres | Aidbox backend, generated FHIR types, open-source Aidbox UI components, agent skills |
| Data model | Agent-invented, per session | FHIR R4, fixed |
| Validation | Whatever the agent wrote | Server-side, on every write |
| New feature | Re-teach the drifted conventions | Pick a resource, generate types, wire the UI |
| Result | Kept drifting | Smaller, coherent — the one worth keeping |
In v1, the agent invented its own data model, its own API shape, its own validation rules. Every new feature meant re-teaching the agent the conventions it had already drifted away from. In v2, rebuilding the same app on FHIR produced a smaller, more coherent codebase — the agent leaned on FHIR instead of reinventing around it.
In practice that shows up in ordinary code. Generated types plus a set of open-source Claude Code skills for working against Aidbox meant the agent wrote a patient create like this, with no schema of its own to design:
// The agent writes against generated types — the shape is not up for negotiation
const patient = await aidbox.create<Patient>({
resourceType: "Patient",
name: [{ given: [body.givenName], family: body.familyName }],
birthDate,
gender: body.gender || undefined,
active: true,
});
// ...and the server validates it on write.
Unremarkable, which is the point: there was no decision to make about field names or storage, and nothing for the agent to drift away from next session.
Three takeaways stood out:
- Less code. The framework handles the boring parts — schema, API, validation — so the agent simply writes fewer of them.
- A tighter feedback loop. The server rejects invalid writes with precise errors, and the agent self-corrects — no mysterious 500 three layers deep.
- Features become conversations, not projects. Adding a clinical concept is one step — pick a FHIR resource, generate types, wire the UI — not five.
The clearest example: we asked for a patient summary feature. On a generic stack that means designing a summary schema, deciding how to reference the underlying records, and building an API around it. On FHIR, the agent reached for the Composition resource — the standard's own model for a structured, sectioned clinical document — and implemented it cleanly: sections referencing the patient's existing Condition, MedicationStatement, and AllergyIntolerance resources, no bespoke schema invented. A feature that would have been a small project became a single conversation, because FHIR had already modeled it.
How to try it yourself
If you want to give your agent the same rails, the starting recipe is short:
- Pick a FHIR server as your backend (for example, Aidbox).
- Generate FHIR types with
@atomic-ehr/codegenso the agent codes against real shapes. - Install the Claude Code skills so the agent speaks FHIR out of the box.
The PHR source is open too, if you'd like to see a full example: github.com/HealthSamurai/phr.
What's next: taking the programmer out of the loop?
Here's the question the experiment opened up. If FHIR gives an agent enough structure to build a real app — what if the agent's user isn't a developer at all?
Imagine a platform where doctors build their own apps, iterating with an AI assistant in plain language, and those apps plug straight into the healthcare organization's existing infrastructure:
- Doctors as builders — no dev team in the middle; describe the workflow, get a working app.
- Iterate with AI — change a form, adjust a rule, add a summary. A conversation, not a ticket.
- Fits the hospital's stack — talks FHIR to the EHR, respects existing auth, audit, and policies. Not a silo, but a citizen of the infrastructure.
It's the same bet as this whole experiment, one level up: FHIR as the foundation that lets AI build healthcare software for the people who actually need it.
Want to talk through applying this on your own stack? Reach out to Aleksandr Kislitsyn, or explore the open-source PHR repository and its Claude Code skills.




