---
{
  "title": "Utiliser FHIR comme cadre de développement agentique",
  "description": "Pourquoi les projets natifs FHIR sont particulièrement bien adaptés au développement assisté par IA — et ce que nous avons appris en construisant deux fois un vrai Dossier de santé personnel avec Claude Code.",
  "date": "2026-08-04",
  "author": "Aleksandr Kislitsyn",
  "reading-time": "8 min read",
  "tags": ["AI / Agents", "FHIR Standard", "Aidbox", "Health Samurai Lab"],
  "tldr": "Les agents de codage IA écrivent du code plausible rapidement, mais sans cadre solide, ils dérivent, inventent des structures de données et ne peuvent pas vérifier leur propre travail — un vrai problème en santé. FHIR s'avère être un ensemble de rails particulièrement efficace : un modèle de données fixe de 150+ ressources, une validation côté serveur à chaque écriture, une terminologie intégrée et des types générés donnent à l'agent une structure sur laquelle s'appuyer et une boucle de rétroaction serrée pour s'autocorriger. Nous avons reconstruit notre PHR interne deux fois avec Claude Code — de zéro, puis sur FHIR — et la version FHIR était plus petite, plus cohérente, et la seule qui valait la peine d'être conservée.",
  "utm-campaign": "ai",
  "utm-content": "fhir-agentic-coding"
}
---

> For the complete documentation index, see [llms.txt](https://www.health-samurai.io/llms.txt).
> Use it to discover all available pages before guessing URLs.

---

Les LLM écrivent du code plausible, et ils l'écrivent vite. Mais quiconque a piloté un agent de codage sur un projet dépassant le stade du jouet connaît le mode d'échec : donnez-lui assez de corde et il dérive. Les noms de champs changent d'une session à l'autre, le modèle de données mute, et les conventions d'hier se réinventent discrètement. Pour une page d'accueil, c'est une nuisance. Pour un logiciel de santé, « improviser » n'est pas une option.

Nous avons passé du temps au laboratoire de Health Samurai à tester une hypothèse : **FHIR n'est pas seulement un standard de données — c'est un cadre inhabituellement bien adapté au développement assisté par IA.** Cet article explique pourquoi, et ce qui s'est passé quand nous avons construit le même produit deux fois pour le vérifier.

## Le développement agentique a besoin de rails

Pointez un agent capable vers une pile générique et quatre problèmes reviennent sans cesse :

- **Les agents dérivent.** Sans modèle fixe, chaque session invente des noms de champs, des formes et des conventions légèrement différents. Les modifications cessent de se composer — chaque nouvelle fonctionnalité entre en conflit avec la précédente.
- **Des structures de données hallucinées.** Demandez à un agent « d'ajouter des allergies » et il inventera joyeusement un schéma. Rien ne rejette une structure invalide avant qu'elle n'atteigne la production et que quelqu'un ne remarque que les données sont incorrectes.
- **Pas d'auto-validation.** Une pile générique ne donne à l'agent aucun moyen de vérifier sa propre production au-delà de « est-ce que ça compile ? » — ce qui n'est pas la même question que « s'agit-il de données de santé correctes ? »
- **Les cadres génériques ne connaissent pas la santé.** React, Rails et Django ne savent rien de `Patient`, `Encounter` ou `Observation`. Chaque projet réinvente la même modélisation du domaine de zéro, et l'agent la réinvente un peu différemment à chaque fois.

Le fil conducteur est l'absence de *rails* — un cadre solide et opinionné qui contraint ce que l'agent peut produire et lui indique immédiatement quand il se trompe. Plus les rails sont bons, moins l'agent a de marge pour dériver, et plus vite il peut se corriger.

## FHIR comme cadre de travail

Voici l'idée clé : FHIR fournit déjà presque tous ces rails, et il les fournit spécifiquement pour la santé. Un modèle de données fixe répond au problème de dérive ; la validation côté serveur répond aux structures hallucinées et donne à l'agent un moyen de se vérifier lui-même ; et l'ensemble est natif au domaine de la santé par construction, de sorte que rien n'a besoin d'être remodélisé d'un projet à l'autre.

Une pile native FHIR comporte trois parties qui travaillent ensemble — le serveur FHIR est le backend, l'application est construite sur des SDK FHIR, et le développeur travaille aux côtés d'un copilote IA qui parle déjà FHIR. La majeure partie de cette pile est *prête* avant même que vous n'écriviez une ligne de code applicatif ; la seule partie que vous construisez réellement est l'application qui relie les pièces FHIR entre elles.

<div class="my-8 not-prose overflow-x-auto">
  <div class="flex items-stretch gap-2.5 min-w-[600px]">
    <!-- Developer -->
    <div class="flex-1 flex flex-col gap-2.5 p-4 bg-bg-secondary rounded-2xl">
      <div class="flex items-center gap-2 text-text">
        <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><circle cx="12" cy="8" r="3.5"/><path d="M5 20c0-3.5 3-6 7-6s7 2.5 7 6" stroke-linecap="round"/></svg>
        <span class="typo-body16 font-semibold leading-none">Developer</span>
      </div>
      <div class="flex flex-wrap gap-1.5 mt-0.5">
        <span class="px-2.5 py-1 rounded-lg bg-bg-primary border border-border-secondary text-[13px] text-text whitespace-nowrap">AI Copilot</span>
        <span class="inline-flex items-center gap-1 px-2.5 py-1 rounded-lg bg-bg-primary border border-border-secondary text-[13px] text-text whitespace-nowrap">Agent Skills <span class="inline-flex w-3.5 h-3.5 rounded-full bg-fg-success-primary text-white items-center justify-center"><svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3.5" aria-hidden="true"><path d="M4 12l5 5L20 6" stroke-linecap="round" stroke-linejoin="round"/></svg></span></span>
      </div>
    </div>
    <!-- builds -->
    <div class="flex flex-col items-center justify-center text-primary shrink-0">
      <span class="typo-mono text-[9px] uppercase tracking-widest mb-1 whitespace-nowrap">builds</span>
      <svg width="24" height="12" viewBox="0 0 26 14" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="M1 7h22" stroke-linecap="round"/><path d="m18 2 5 5-5 5" stroke-linecap="round" stroke-linejoin="round"/></svg>
    </div>
    <!-- PHR App -->
    <div class="flex-1 flex flex-col gap-2.5 p-4 bg-bg-secondary rounded-2xl border border-primary/40">
      <div class="flex items-center gap-2 flex-wrap">
        <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" class="text-text" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7z"/></svg>
        <span class="typo-body16 font-semibold leading-none text-text">PHR App</span>
        <span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-primary text-white typo-mono text-[9px] uppercase tracking-wider whitespace-nowrap"><svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M4 20h4L18.5 9.5a2.1 2.1 0 0 0-3-3L5 17v3z" stroke-linejoin="round"/></svg> You build this</span>
      </div>
      <div class="flex flex-col gap-1">
        <p class="typo-mono text-text-light text-[10px] uppercase tracking-wider flex items-center gap-1">FHIR SDK <span class="inline-flex w-3 h-3 rounded-full bg-fg-success-primary text-white items-center justify-center"><svg width="8" height="8" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3.5" aria-hidden="true"><path d="M4 12l5 5L20 6" stroke-linecap="round" stroke-linejoin="round"/></svg></span></p>
        <div class="flex flex-wrap gap-1.5">
          <span class="px-2.5 py-1 rounded-lg bg-bg-primary border border-border-secondary text-[13px] text-text whitespace-nowrap">FHIR Types</span>
          <span class="px-2.5 py-1 rounded-lg bg-bg-primary border border-border-secondary text-[13px] text-text whitespace-nowrap">FHIR Client</span>
        </div>
      </div>
      <div class="flex flex-col gap-1">
        <p class="typo-mono text-text-light text-[10px] uppercase tracking-wider flex items-center gap-1">UI <span class="inline-flex w-3 h-3 rounded-full bg-fg-success-primary text-white items-center justify-center"><svg width="8" height="8" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3.5" aria-hidden="true"><path d="M4 12l5 5L20 6" stroke-linecap="round" stroke-linejoin="round"/></svg></span></p>
        <div class="flex flex-wrap gap-1.5">
          <span class="px-2.5 py-1 rounded-lg bg-bg-primary border border-border-secondary text-[13px] text-text whitespace-nowrap">React Components</span>
        </div>
      </div>
    </div>
    <!-- FHIR REST -->
    <div class="flex flex-col items-center justify-center text-primary shrink-0">
      <span class="typo-mono text-[9px] uppercase tracking-widest mb-1 whitespace-nowrap">FHIR REST</span>
      <svg width="24" height="12" viewBox="0 0 26 14" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="M1 7h22" stroke-linecap="round"/><path d="m18 2 5 5-5 5" stroke-linecap="round" stroke-linejoin="round"/></svg>
    </div>
    <!-- FHIR Server -->
    <div class="flex-[1.35] flex flex-col gap-2.5 p-4 bg-bg-secondary rounded-2xl border border-border-success">
      <div class="flex items-center gap-2 flex-wrap">
        <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" class="text-text" aria-hidden="true"><rect x="3" y="4" width="18" height="7" rx="1.5"/><rect x="3" y="13" width="18" height="7" rx="1.5"/><path d="M7 7.5h.01M7 16.5h.01" stroke-linecap="round"/></svg>
        <span class="typo-body16 font-semibold leading-none text-text">FHIR Server</span>
        <span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-fg-success-primary text-white typo-mono text-[9px] uppercase tracking-wider whitespace-nowrap"><svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" aria-hidden="true"><path d="M4 12l5 5L20 6" stroke-linecap="round" stroke-linejoin="round"/></svg> Ready</span>
      </div>
      <div class="flex flex-wrap gap-1.5">
        <span class="px-2.5 py-1 rounded-lg bg-bg-primary border border-border-secondary text-[13px] text-text whitespace-nowrap">Data Model</span>
        <span class="px-2.5 py-1 rounded-lg bg-bg-primary border border-border-secondary text-[13px] text-text whitespace-nowrap">Validation</span>
        <span class="px-2.5 py-1 rounded-lg bg-bg-primary border border-border-secondary text-[13px] text-text whitespace-nowrap">Terminology</span>
        <span class="px-2.5 py-1 rounded-lg bg-bg-primary border border-border-secondary text-[13px] text-text whitespace-nowrap">SDC</span>
        <span class="px-2.5 py-1 rounded-lg bg-bg-primary border border-border-secondary text-[13px] text-text whitespace-nowrap">SQL on FHIR</span>
        <span class="px-2.5 py-1 rounded-lg bg-bg-primary border border-border-secondary text-[13px] text-text whitespace-nowrap">Subscriptions</span>
        <span class="px-2.5 py-1 rounded-lg bg-bg-primary border border-border-secondary text-[13px] text-text whitespace-nowrap">Access Control</span>
      </div>
    </div>
  </div>
</div>

<div class="not-prose flex flex-wrap items-center gap-x-6 gap-y-1.5 mb-8 text-[13px] text-text-light">
  <span class="flex items-center gap-2"><span class="inline-flex w-2.5 h-2.5 rounded-full bg-dot-success"></span><strong class="text-text">Ready</strong> — FHIR spec, server, types, SDKs, AI copilots, agent skills</span>
  <span class="flex items-center gap-2"><span class="inline-flex w-2.5 h-2.5 rounded-full bg-primary"></span><strong class="text-text">You build</strong> — the app itself, wiring FHIR pieces together</span>
</div>

Examinons ce que chaque couche apporte à l'agent.

### Un modèle de données hérité, non conçu

FHIR R4 définit plus de 150 ressources de santé — `Patient`, `Condition`, `Observation`, `Encounter`, `MedicationStatement`, `DocumentReference`, et bien d'autres. Stockées nativement par un serveur FHIR comme [Aidbox](https://www.health-samurai.io/aidbox), elles n'exigent aucune conception de schéma.

<figure class="markdown-figure narrow">
  <img src="image-1.png" alt="FHIR R4 resource index — every resource grouped by category" loading="eager" decoding="async" />
  <figcaption>La liste des ressources FHIR R4 : deux décennies de modélisation du domaine de la santé que vous héritez gratuitement.</figcaption>
</figure>

Cela compte plus qu'il n'y paraît au premier abord. Quand l'agent a besoin de stocker des allergies, il n'invente pas une table — il utilise `AllergyIntolerance`, qui possède déjà les bons champs, les bonnes cardinalités et les bonnes liaisons. Les concepts de domaine personnalisés deviennent des ressources FHIR personnalisées — dans notre propre application, un `PhrUser` — avec leur propre `StructureDefinition`, et non pas une table sur mesure avec des colonnes inventées par l'agent. Vous héritez de deux décennies de modélisation du domaine de la santé, et l'agent aussi.

### La validation comme boucle de rétroaction

Chaque écriture sur un serveur FHIR est validée par rapport à la `StructureDefinition` de la ressource : cardinalité, types, liaisons, invariants. Les données invalides n'entrent jamais dans la base de données.

Pour un développeur humain, c'est un filet de sécurité. Pour un agent, c'est quelque chose de plus précieux — une **boucle de rétroaction précise et serrée**. Au lieu d'une erreur 500 trois couches plus bas, l'agent reçoit quelque chose comme :

```
OperationOutcome: Patient.gender must be a code from
http://hl7.org/fhir/administrative-gender (male | female | other | unknown)
```

C'est exactement le type de signal sur lequel un agent s'autocorrige. La validation basée sur les profils vous permet de resserrer les règles pour votre propre projet — champs obligatoires, systèmes de codage fixes — sans écrire une seule ligne de code de validation. L'agent écrit moins, et le serveur lui indique immédiatement quand il se trompe.

### Terminologie, formulaires et analytique — déjà résolus

Un serveur FHIR expédie de nombreux problèmes difficiles déjà résolus, et chacun est un problème que l'agent tenterait autrement de construire manuellement :

- **Terminologie.** `ValueSet`, `CodeSystem` et les opérations comme `$expand`, `$validate-code` et `$lookup` sont intégrés. LOINC, SNOMED, RxNorm, ICD — tous accessibles via des points de terminaison FHIR standard. Votre application ne *fournit* pas un système de codage ; elle en interroge un (par exemple, [Termbox](https://www.health-samurai.io/termbox), un serveur de terminologie FHIR dédié). La terminologie cesse d'être un billet « À faire : trouver une bibliothèque ».
- **SQL on FHIR.** Définissez une `ViewDefinition` qui aplatit les ressources FHIR en colonnes tabulaires, puis interrogez la vue avec du SQL ordinaire. Les équipes d'analytique obtiennent du SQL pour les rapports ; les données restent en FHIR — pas de pipeline de transformation à écrire. Cela abaisse aussi la barre pour les tableaux de bord intégrés à l'application : un développeur d'application qui peine à agréger des ressources FHIR imbriquées peut écrire un simple `GROUP BY` contre une vue, de sorte qu'un graphique dans le produit et un rapport pour les analystes lisent à partir de la même définition. Et une `ViewDefinition` est elle-même une ressource FHIR, ce qui signifie que l'agent peut en écrire une — déclarer des colonnes est une tâche bien plus restreinte que de construire manuellement une logique de traversée sur des tableaux imbriqués.
- **Structured Data Capture (SDC).** Les ressources `Questionnaire` FHIR décrivent des formulaires ; les utilisateurs les remplissent et les réponses sont stockées comme `QuestionnaireResponse` — liées, validées et consultables comme toute autre donnée FHIR. Les implémentations SDC apportent des constructeurs de formulaires, des moteurs de rendu, une logique d'extraction et des galeries de formulaires prêts à l'emploi.

<figure class="markdown-figure narrow">
  <img src="image-2.png" alt="Aidbox Form Builder — designing and testing a FHIR-native form" loading="lazy" decoding="async" />
  <figcaption>SDC en pratique : des formulaires natifs FHIR construits et testés dans Aidbox Form Builder.</figcaption>
</figure>

### Types et compétences pour l'agent

Les couches ci-dessus contraignent les *données*. Deux autres ferment la boucle autour de l'*agent*.

- **SDK typés.** Parce que chaque ressource possède une `StructureDefinition` lisible par machine, les types client peuvent être générés plutôt qu'écrits — pour TypeScript, Python, C#, Java et d'autres. Le bénéfice pour un agent dépasse l'autocomplétion : un champ mal orthographié ou une mauvaise énumération échoue au niveau du type, avant qu'une requête ne soit envoyée, et une source de vérité unique générée est partagée par le serveur et le client, de sorte que les deux côtés d'une fonctionnalité ne peuvent pas diverger. Des générateurs disponibles publiquement font de cela une étape de construction, pas un projet.
- **Compétences d'agent.** La couche la plus récente est une documentation rédigée pour les agents plutôt que pour les humains. Parce que FHIR est un standard public avec des API de serveur publiques, cette connaissance est réutilisable d'un projet à l'autre — comment formuler une requête de recherche, comment fonctionnent les politiques d'accès, quand recourir à SQL on FHIR — plutôt que quelque chose que chaque équipe doit réenseigner. Pointer un agent vers une documentation à jour l'empêche également de s'appuyer sur ce qu'un modèle a absorbé au moment de l'entraînement, ce qui, pour une spécification qui publie de nouvelles versions, est une vraie source de code faux mais affirmé avec assurance.

Ces deux éléments se configurent une seule fois, et l'agent en bénéficie ensuite à chaque tâche.

## L'expérience : la même application, une fondation différente

La théorie ne coûte pas cher, alors nous l'avons testée. Notre terrain d'expérimentation était le **Dossier de santé personnel (PHR)** interne de Health Samurai — un vrai produit pour notre propre équipe et les familles dont elle prend soin. Sa portée est véritablement non triviale : gérer des dossiers pour soi-même et ses personnes à charge, saisir des conditions, médicaments, allergies et procédures, téléverser des PDF médicaux, des consultations par clavardage avec des médecins, un résumé du patient, et une assistance IA ancrée dans les propres données FHIR du patient.

Nous l'avons construit **deux fois avec Claude Code — même portée, fondation différente.** La première fois, nous avons laissé l'agent construire de zéro ; la deuxième fois, nous avons reconstruit toute l'application avec FHIR comme cadre.

| | **v1 — de zéro** | **v2 — sur FHIR** |
|---|---|---|
| Pile | React + Node + Postgres ordinaires | Backend Aidbox, types FHIR générés, composants UI Aidbox open source, compétences d'agent |
| Modèle de données | Inventé par l'agent, par session | FHIR R4, fixe |
| Validation | Ce que l'agent a écrit | Côté serveur, à chaque écriture |
| Nouvelle fonctionnalité | Réenseigner les conventions dérivées | Choisir une ressource, générer les types, câbler l'interface |
| Résultat | Continuait à dériver | Plus petite, cohérente — celle qui valait la peine d'être conservée |

Dans **v1**, l'agent a inventé son propre modèle de données, sa propre forme d'API, ses propres règles de validation. Chaque nouvelle fonctionnalité signifiait réenseigner à l'agent les conventions dont il s'était déjà éloigné. Dans **v2**, reconstruire la même application sur FHIR a produit une base de code plus petite et plus cohérente — l'agent s'est appuyé sur FHIR plutôt que de le réinventer.

En pratique, cela se manifeste dans le code ordinaire. Les types générés combinés à un ensemble de [compétences Claude Code open source](https://github.com/HealthSamurai/phr/tree/main/.claude/skills) pour travailler avec Aidbox ont permis à l'agent d'écrire une création de patient comme celle-ci, sans aucun schéma à concevoir :

```typescript
// 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.
```

Rien de remarquable, ce qui est précisément le but : il n'y avait aucune décision à prendre sur les noms de champs ou le stockage, et rien dont l'agent pourrait s'éloigner à la prochaine session.

Trois enseignements se sont démarqués :

1. **Moins de code.** Le cadre gère les parties ennuyeuses — schéma, API, validation — de sorte que l'agent en écrit simplement moins.
2. **Une boucle de rétroaction plus serrée.** Le serveur rejette les écritures invalides avec des erreurs précises, et l'agent s'autocorrige — pas d'erreur 500 mystérieuse trois couches plus bas.
3. **Les fonctionnalités deviennent des conversations, pas des projets.** Ajouter un concept clinique est une seule étape — choisir une ressource FHIR, générer les types, câbler l'interface — pas cinq.

L'exemple le plus clair : nous avons demandé une fonctionnalité de **résumé du patient**. Sur une pile générique, cela signifie concevoir un schéma de résumé, décider comment référencer les dossiers sous-jacents, et construire une API autour de cela. Sur FHIR, l'agent a utilisé la ressource [`Composition`](https://build.fhir.org/composition.html) — le propre modèle du standard pour un document clinique structuré et sectionné — et l'a implémentée proprement : des sections référençant les ressources `Condition`, `MedicationStatement` et `AllergyIntolerance` existantes du patient, sans schéma sur mesure inventé. Une fonctionnalité qui aurait été un petit projet est devenue une seule conversation, parce que FHIR l'avait déjà modélisée.

## Comment l'essayer vous-même

Si vous souhaitez donner à votre agent les mêmes rails, la recette de départ est courte :

- **Choisissez un serveur FHIR** comme backend (par exemple, [Aidbox](https://www.health-samurai.io/aidbox)).
- **Générez les types FHIR** avec [`@atomic-ehr/codegen`](https://github.com/atomic-ehr/codegen) pour que l'agent code contre de vraies structures.
- **Installez les [compétences Claude Code](https://github.com/HealthSamurai/phr/tree/main/.claude/skills)** pour que l'agent parle FHIR dès le départ.

Le code source du PHR est également ouvert, si vous souhaitez voir un exemple complet : [github.com/HealthSamurai/phr](https://github.com/HealthSamurai/phr).

## Et ensuite : retirer le programmeur de la boucle ?

Voici la question que l'expérience a soulevée. Si FHIR donne à un agent suffisamment de structure pour construire une vraie application — que se passe-t-il si l'utilisateur de l'agent n'est pas du tout un développeur ?

Imaginez une plateforme où **les médecins construisent leurs propres applications**, en itérant avec un assistant IA en langage courant, et ces applications se branchent directement sur l'infrastructure existante de l'organisation de santé :

- **Les médecins comme bâtisseurs** — pas d'équipe de développement intermédiaire ; décrivez le flux de travail, obtenez une application fonctionnelle.
- **Itérer avec l'IA** — modifier un formulaire, ajuster une règle, ajouter un résumé. Une conversation, pas un billet.
- **S'intègre à la pile hospitalière** — parle FHIR au DSE, respecte l'authentification, l'audit et les politiques existants. Pas un silo, mais un citoyen de l'infrastructure.

C'est le même pari que toute cette expérience, un niveau au-dessus : **FHIR comme fondation qui permet à l'IA de construire des logiciels de santé pour les personnes qui en ont réellement besoin.**

---

*Vous souhaitez discuter de l'application de cette approche à votre propre pile ? Contactez [Aleksandr Kislitsyn](https://www.linkedin.com/in/aleksandrkislitsyn/), ou explorez le dépôt [PHR](https://github.com/HealthSamurai/phr) open source et ses [compétences Claude Code](https://github.com/HealthSamurai/phr/tree/main/.claude/skills).*