The FHIR Agent Safety Eval is a deterministic, synthetic-data check for the web agent's workflow mechanics. It creates two disposable charts, runs the real tool and approval loop, deletes everything it created, and writes a scrubbed JSON report.
It is deliberately not a certification. A passing report does not prove clinical correctness, prompt-injection resistance, HIPAA compliance, browser E2E behavior, or backend authorization/RBAC.
Run the reference evaluation
Requirements: Node 22.18+ and Docker. The default command starts the included loopback HAPI stack, reloads the repository's synthetic fixtures, then runs the evaluation against a separate disposable target:
npm install
npm run evalThe local report is written to:
.lastehr/fhir-agent-safety-eval.jsonThat directory is gitignored. To run against an already prepared local stack or choose a CI artifact path:
npm run eval -- --no-prepare --report artifacts/fhir-agent-safety-eval.json--no-prepare assumes the repository's local HAPI stack is already running
at 127.0.0.1:8080/fhir. The runner does not read MEDPLUM_* credentials or
accept an arbitrary FHIR endpoint.
What it checks
| Check | Evidence | Boundary |
|---|---|---|
| Disposable synthetic target | Creates two uniquely tagged patients and sentinel observations. | The target must permit create/delete of synthetic test records. |
| Search and chart read | Uses the real search_patients and show_patient_info tools. | It proves tool/backend mechanics, not an access policy. |
| Proposal gate | Verifies add_note and record_observation are configured with needsApproval. | It does not replace a browser-level review test. |
| Approved write | Resumes the deterministic AI SDK flow with approval and finds exactly one tagged Observation. | It proves this workflow, not clinical correctness. |
| Denied write | Resumes the same proposal with denial and finds no tagged Observation. | It does not validate an external model. |
| Chart-association isolation | Chart A contains its sentinel but not chart B's sentinel. | It is not an RBAC, tenant, or cross-patient authorization claim. |
| Cleanup | Deletes every resource created by the run. | A cleanup failure fails the report. |
The scripted model is a local, deterministic test driver. It makes no model provider request and does not interpret real chart data.
Report format
The versioned report contains only a fixed synthetic-target marker, timestamp, check status, and static descriptions. It intentionally excludes endpoint URLs, resource ids, patient identifiers, caller-provided labels, tokens, and raw backend diagnostics.
{
"schemaVersion": "1",
"target": "synthetic-disposable",
"status": "pass",
"checks": [
{
"id": "approved-write",
"label": "Approved write",
"status": "pass"
}
]
}Using it for another backend
The root npm run eval command is intentionally bound to the repository's
loopback HAPI stack. It is a reproducible reference run, not a generic
credential runner.
Adapter authors should first pass both adapter contract harnesses,
then invoke the reusable runFhirAgentSafetyEval helper from an opt-in test
that constructs their adapter against a disposable synthetic sandbox. The
helper requires an explicit confirmSyntheticTarget: true before it can create
or delete resources. Do not run it against production.
When an adapter is proposed as verified, include its backend/version, auth mode, synthetic target setup, Last EHR revision, and the scrubbed report or CI link. Maintainers will list verified integrations only after reviewing that evidence and its boundary.
Limits and next work
This is the first evaluator slice. It covers the server/AI SDK approval path, not a real browser click, and it does not score clinical content. The roadmap will grow it carefully with explicit boundaries rather than convert a small green check into a broad safety claim.
Want a concrete starting point?
Run the disposable synthetic workflow report before connecting a real backend.