Skip to main content
This agent lets a verified patient book an appointment and read their medications, allergies, and conditions from a FHIR-compatible EMR API. A preprocessor holds every message until the patient has consented, a postprocessor appends a disclaimer to every reply, and the patient id comes from the user profile, never from the conversation. It is six files, and every TypeScript file compiles against lua-cli 3.33.0; run it with the steps on Running any example. The example shows the integration shape; it is not a compliance review. Verified against lua-cli 3.33.0.

The conversation

  1. The patient’s first message is blocked by hipaa-consent with a request to reply “I consent”. When they do, the preprocessor records the consent on their profile and lets the message through; later messages pass without a check.
  2. The patient asks for an appointment. schedule_appointment reads patientId from the profile and posts a FHIR Appointment for that patient and the chosen practitioner.
  3. The patient asks about their medications. view_medical_records reads the matching FHIR resources for the same patient id.
  4. medical-disclaimer appends one fixed sentence to every reply.

Primitives and channels

  • Skill and tools: patient-portal, with schedule_appointment and view_medical_records.
  • Preprocessor hipaa-consent at priority 10, and postprocessor medical-disclaimer.
  • Runtime objects: User.get, user.update, and env for EMR_API_URL and EMR_API_KEY.
  • Channels: the web widget inside the signed-in portal, where the portal can write patientId to the profile. Any other channel works once that field is set.

The code

The tools refuse when the profile has no patientId; the skill’s context tells the model what to do then.
src/skills/tools/ScheduleAppointmentTool.ts
The records tool searches one FHIR resource type by patient and returns the resources for the model to summarize.
src/skills/tools/ViewMedicalRecordsTool.ts
The skill never asks for a patient id.
src/skills/patient-portal.skill.ts
The preprocessor runs before the model sees a message; block ends the turn with its response, and proceed lets the message through.
src/preprocessors/hipaaConsent.ts
The postprocessor receives the reply and returns the text to send.
src/postprocessors/medicalDisclaimer.ts
The agent registers the skill and both processors.
src/index.ts

First run

Run the preprocessor with a first message on the web widget’s channel, pop; it blocks and returns the consent request.
Output
The same command with "message":"I consent" proceeds and writes hipaaConsentGiven to the test user’s profile. Test a tool with lua test --ci skill --name view_medical_records --input '{"recordType":"Condition"}'; without patientId on the profile it returns the not-verified reason. For the full loop with the model, send lua chat --ci -e sandbox -m "Can I see my medications?" -t; it uploads the .env values with the sandbox version. Then release it with lua push all --ci --force, lua version create --ci -m "<message>", and lua version promote <n>; Release an agent to production explains what each command changes.

Ways to make it yours

  • Write patientId to the profile after your portal verifies identity, either from a verification tool with user.update({ patientId }) or through the user data REST endpoints. The tools trust that field and nothing the model says.
  • Replace the consent text and the disclaimer with the wording your compliance team approves; both are strings in one file each.
  • Vary the disclaimer by channel with the fourth argument of the postprocessor’s execute (pop for the web widget, whatsapp for WhatsApp), for example a shorter sentence on WhatsApp.
  • Summarize FHIR resources into plain fields inside view_medical_records when the model’s replies are too raw; the tool’s return value is what the model reads.

Next steps

Add a processor

Order, priority, blocking, and testing preprocessors and postprocessors.

Identify users

The profile, custom fields, and cross-channel identity.

Call your API

Keys in env(), errors, and timeouts when a tool calls an external service.