|
8 min read
|

Using FHIR as a framework for agentic coding

Article Summary

AI coding agents write plausible code fast, but without a strong framework they drift, hallucinate data shapes, and can't verify their own work — a real problem in healthcare. FHIR turns out to be an unusually good set of rails: a fixed data model of 150+ resources, server-side validation on every write, built-in terminology, and generated types give the agent structure to lean on and a tight feedback loop to self-correct. We rebuilt our internal PHR twice with Claude Code — from scratch, then on FHIR — and the FHIR version was smaller, more coherent, and the one worth keeping.

Summarize this article with:
ChatGPTPerplexityClaudeGrok

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, or Observation. 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.

Developer
AI Copilot Agent Skills
builds
PHR App You build this

FHIR SDK

FHIR Types FHIR Client

UI

React Components
FHIR REST
FHIR Server Ready
Data Model Validation Terminology SDC SQL on FHIR Subscriptions Access Control
Ready — FHIR spec, server, types, SDKs, AI copilots, agent skills You build — the app itself, wiring FHIR pieces together

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.

FHIR R4 resource index — every resource grouped by category
The FHIR R4 resource list: two decades of healthcare domain modeling you inherit for free.

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 $lookup are 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 ViewDefinition that 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 flat GROUP BY against a view, so a chart in the product and a report for the analysts read from the same definition. And a ViewDefinition is 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 Questionnaire resources describe forms; users fill them in and answers are stored as QuestionnaireResponse — linked, validated, and searchable like any other FHIR data. SDC implementations bring form builders, renderers, extraction logic, and galleries of ready-made forms.
Aidbox Form Builder — designing and testing a FHIR-native form
SDC in practice: FHIR-native forms built and tested in Aidbox Form Builder.

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 scratchv2 — on FHIR
StackPlain React + Node + PostgresAidbox backend, generated FHIR types, open-source Aidbox UI components, agent skills
Data modelAgent-invented, per sessionFHIR R4, fixed
ValidationWhatever the agent wroteServer-side, on every write
New featureRe-teach the drifted conventionsPick a resource, generate types, wire the UI
ResultKept driftingSmaller, 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:

  1. Less code. The framework handles the boring parts — schema, API, validation — so the agent simply writes fewer of them.
  2. A tighter feedback loop. The server rejects invalid writes with precise errors, and the agent self-corrects — no mysterious 500 three layers deep.
  3. 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/codegen so 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.

Share this article
Comments
Comments
Sign in
Loading comments...
Subscribe to our blog

Get the latest articles on FHIR, interoperability, and healthcare IT.