Chat agents
WhatsApp-triage voor thuiszorg: 540 berichten per dag
Een planningscoördinator in Almere opent dinsdagochtend 312 ongelezen WhatsApps voor 09:00. Dit is de agent die we bouwden om geen urgent bericht meer te missen.

De dinsdagochtend begint om 07:50. Marleen, planningscoördinator bij een thuiszorgorganisatie met 36 medewerkers in Almere, opent WhatsApp Business op haar laptop en ziet 312 ongelezen gesprekken. Voor 09:00 komen er nog 90 bij. Daarvan zijn er misschien vier urgent. Misschien moet er één binnen het uur door een verpleegkundige worden teruggebeld. De rest gaat over verzette diensten, parkeervragen, "kan de wijkverpleegkundige iets later komen", en drie of vier draadjes over een lekkend infuus.
Het oude systeem was Marleen die elk bericht las en de serieuze doorzette naar de dienstdoende verpleegkundige via een WhatsApp-groep van 12 mensen. Het nieuwe systeem is een Nederlandstalige agent die elk gesprek triageert, echte noodgevallen binnen 90 seconden escaleert, en nooit een zin met een BSN of medicijnnaam in een logfile laat lekken.
Dit is het draaiboek.
De vorm van 540 berichten per dag
Voordat we een regel code schreven, hebben we twee dagen naast Marleen gezeten en elk bericht van de afgelopen week gelezen. 3.780 gesprekken. De verdeling had de soort lange staart die triage de moeite van het automatiseren waard maakt:
- 61% logistiek: diensttijden, parkeercodes, toegang tot het pand, ophaaltijden door familie.
- 19% medisch, niet-urgent: herinneringen voor medicatieherhaling, wondzorg-follow-ups, bloeddrukmetingen.
- 14% emotioneel of sociaal: een dochter die even checkt, een cliënt die eenzaam is, een klacht over een eerdere dienst.
- 5% medisch, urgent: valpartijen, plotselinge verwardheid, bloed, ademnood, geen reactie bij de deur.
- 1% spam, verkeerd nummer, of de plaatselijke apotheek.
Die 5% is wat Marleen 's nachts wakker hield. Twee daarvan hebben binnen een uur een verpleegkundige nodig. De rest binnen vier uur. Als er eentje ongelezen blijft omdat hij binnenkwam tussen 76 berichten over parkeren, dan is dat het soort fout dat een thuiszorgvergunning kost.
De agent hoeft niet slim te zijn over parkeercodes. Hij moet paranoïde zijn over die 5%.
Routingregels die Nederlands serieus nemen
We hebben de agent gebouwd op het officiële WhatsApp Business Platform (de Cloud API die door Meta wordt gehost), niet op de consumenten-app. Dat verschil telt: alleen het Business Platform geeft je message webhooks, template messages, en een gedocumenteerd retentiemodel. De consumenten-app kun je niet legaal automatiseren. De officiële documentatie staat op developers.facebook.com/docs/whatsapp/cloud-api.
Elk inkomend bericht komt binnen op een webhook. De agent draait een classifier in twee stappen:
# stap 1: intentie + urgentie, prompt afgestemd op Nederlands
SYSTEM = """Je bent een triagemedewerker voor een thuiszorgorganisatie in Almere.
Klassificeer het bericht in een van de volgende categorieen:
L = logistiek (tijden, parkeren, sleutel, route)
MN = medisch niet-urgent (medicatie, wondzorg, controle)
E = emotioneel of sociaal
MU = medisch urgent (val, bloed, ademnood, verwardheid, geen reactie)
S = spam of verkeerd nummer
Antwoord met JSON: {"cat": "...", "confidence": 0.0-1.0, "reden": "..."}
Bij twijfel tussen MN en MU: kies MU.
"""
Die laatste regel doet veel werk. De prijs van een vals negatief op MU is een verpleegkundige die te laat komt. De prijs van een vals positief is een verpleegkundige die één bericht extra leest. Elke drempel in de agent helt naar die asymmetrie.
Stap twee is een deterministische regellaag. Zegt stap een MU met welke confidence dan ook, dan escaleer je. Zegt hij MN maar bevat het bericht een van de 47 Nederlandse rode-vlag-woorden ("bloedt", "ademt niet", "gevallen", "niet wakker", "blauw", "stuiptrekkingen", "verward"), dan upgrade naar MU. De lijst is geschreven door het hoofd verpleging, niet door ons.
Laat de LLM nooit de enige poort zijn tussen een urgent bericht en een verpleegkundige. Een keyword-tripwire na het model vangt die 0,3% van de gevallen waarin het model creatief wordt. Het kost je één regex per bericht.
Overdracht aan een verpleegkundige binnen 90 seconden
De 90-seconden-SLA wordt gemeten vanaf de WhatsApp-bezorgtijdstempel tot het moment dat een specifieke verpleegkundige de alert bevestigt. We halen dit op 97% van de MU-berichten in de eerste zes weken. Het pad ziet er zo uit:
- Inkomend WhatsApp-bericht arriveert, webhook vuurt af (mediaan 1,2s).
- Classifier stap een plus stap twee draait (mediaan 2,8s, p95 4,1s).
- Als MU: een gestructureerde alert komt tegelijk op drie plekken binnen. Een Telegram-groep voor de dienstdoende verpleegkundige, een SIP-call naar haar werktelefoon via Twilio, en een statusregel in Postgres.
- De verpleegkundige bevestigt met één tik. Geen ack binnen 60 seconden: de alert escaleert naar de back-up-verpleegkundige. Geen ack binnen 90 seconden: de dienstdoende manager wordt gepiept.
Die fan-out is belangrijk. WhatsApp-groepen zijn onbetrouwbaar als alertkanaal omdat notificaties per gesprek gedempt kunnen worden en Meta geeft de server van de verzender geen leesbevestiging. Telegram, SIP en een database-write samen betekenen dat minstens één kanaal binnen de SLA een mens bereikt.
// alert fan-out: fail loud, fail fast
async function escalateMU(msg: TriagedMessage) {
const alert = {
id: msg.id,
client: msg.clientRef, // never the name, only the internal ref
summary: msg.redacted, // PII-scrubbed one-liner
received_at: msg.ts,
nurse_on_call: rota.current(),
}
await Promise.allSettled([
telegram.send(alert.nurse_on_call.tgChat, render(alert)),
twilio.calls.create({ to: alert.nurse_on_call.phone, url: TWIML_ALERT }),
db.alerts.insert(alert),
])
// start the 60s ack timer
scheduleAckCheck(alert.id, 60_000)
}
Promise.allSettled in plaats van Promise.all is een bewuste keuze. Ligt Telegram plat, dan willen we nog steeds dat de SIP-call vertrekt, en andersom. We loggen het kapotte kanaal en gaan door.
Persoonsgegevens onder de AVG
De Nederlandse thuiszorg valt onder de AVG, gecontroleerd door de Autoriteit Persoonsgegevens. Gezondheidsgegevens zijn een bijzondere categorie. De naam van een cliënt plus het feit dat hij thuiszorg ontvangt is al een gezondheidsgegeven. Een medicijnnaam in een chatlog is een gezondheidsgegeven. Een BSN in een chatlog is een meldingsplichtig datalek als het uitlekt.
Onze regel: geen enkele cliënt-identificerende string bereikt ooit de LLM, bereikt ooit een externe loggingdienst, of staat ooit langer dan 14 dagen in cleartext op schijf.
Dat doen we met een redactiestap die op onze eigen infrastructuur draait voordat het bericht ooit de VPC verlaat:
import re
BSN = re.compile(r"\b\d{8,9}\b")
PHONE = re.compile(r"\b(?:\+31|0)[\s-]?6[\s-]?\d{8}\b")
IBAN = re.compile(r"\bNL\d{2}[A-Z]{4}\d{10}\b")
def redact(text: str, client_ref: str) -> tuple[str, dict]:
"""Replace identifiers with stable placeholders. Returns (text, vault)."""
vault = {}
def stash(match, kind):
token = f"<{kind}:{len(vault)}>"
vault[token] = match.group(0)
return token
text = BSN.sub(lambda m: stash(m, "BSN"), text)
text = PHONE.sub(lambda m: stash(m, "TEL"), text)
text = IBAN.sub(lambda m: stash(m, "IBAN"), text)
# client name lookup runs from a hashed bloom filter, not the full list
text = redact_known_names(text, client_ref, stash)
return text, vault
De vault blijft op onze server. De LLM ziet <BSN:0> en <TEL:1>. Moet het antwoord van de agent de cliënt bij naam aanspreken, dan vervangen we op de uitgaande weg terug, nooit op de inkomende. We kozen dit boven een slimme prompt aan de LLM-kant omdat regex niet in de war raakt door een vermoeid model om 03:00.
Specifiek voor het Nederlandse BSN kunnen we met de elfproef valideren voor we redacteren, zodat we geen vault-slot verspillen aan een telefoonnummer dat toevallig op een BSN leek. Het algoritme staat op de Wikipedia-pagina van het Burgerservicenummer.
Het agents.md-bestand dat alles aanstuurt
Op Hacker News loopt deze maand een rustige discussie over de vraag of agents.md-bestanden coding-agents echt helpen. De argumenten snijden beide kanten op. Onze ervaring is smaller en duidelijker: voor een productie-triage-agent die meerdere mensen onderhouden, is een enkel markdown-bestand in de root dat rollen, escalatieregels en rode-vlag-vocabulaire definieert het verschil tussen een systeem dat het hoofd verpleging vertrouwt en een systeem dat ze niet vertrouwt.
De onze is 240 regels. Hij dekt:
- De rol en de grenzen ("geef nooit medisch advies, bevestig of ontken nooit medicatiedoseringen").
- De vijf categorieën, met telkens drie voorbeeldberichten in het Nederlands.
- De rode-vlag-lijst, in versiebeheer bijgehouden door het hoofd verpleging.
- Het exacte JSON-schema dat de classifier moet teruggeven.
- De escalatiematrix.
- De zinnen die de agent letterlijk moet gebruiken bij overdracht ("Een verpleegkundige neemt binnen 15 minuten contact op").
Het bestand is tegelijk de prompt en de documentatie. Wil het hoofd verpleging "kortademig" toevoegen aan de rode-vlag-lijst, dan bewerkt ze de markdown, opent ze een pull request, en de wijziging gaat live na één review. Niemand raakt de classifier-code aan.
Wat we maten na week zes
De cijfers uit de laatste volledige rapportageweek (week 22 van 2026):
- 3.812 inkomende berichten.
- 187 geclassificeerd als MU. Elf daarvan werden door de dienstdoende verpleegkundige bij review opnieuw geclassificeerd als MN. False-positive-rate: 5,9%.
- Twee MN-berichten werden later door Marleen in haar dagafsluitende audit gemarkeerd als gemiste urgenties. False-negative-rate: 0,05% van MN, 1,1% van echte MU.
- Mediane tijd van inkomend bericht tot bevestiging door de verpleegkundige: 47 seconden. p95: 82 seconden. p99: 134 seconden.
- Vrijgekomen coördinatortijd: ongeveer 23 uur per week, gemeten met Marleens eigen urenstaat tegen de vier weken voor de livegang.
De 1,1% false-negative op MU is het cijfer dat we nog volgen. Twee gemiste urgenties in zes weken is er twee te veel. De fix die nu loopt is een tweede, kleiner model dat parallel draait op elke MN-classificatie en tegen het eerste stemt. Bij oneens escaleert het bericht.
Wat je vanmiddag kunt doen
Run je een operationeel team dat in WhatsApp leeft, dan is dit de audit van vijf minuten. Exporteer de laatste 200 inkomende gesprekken. Label elk bericht met de hand als MU, MN, L, E of S. Vraag jezelf af welk aandeel van de MU-berichten binnen je eigen SLA is bevestigd. Ligt het antwoord onder 95%, dan heb je een triageprobleem, geen personeelsprobleem.
Toen we deze chat-agent voor de Almeerse aanbieder bouwden, was de AVG-redactiestap het taaiste stuk. Elke prompt-iteratie verleidde ons om het model meer context te geven, en elke keer dat we het betrapten knipten we de context terug. Uiteindelijk gingen we live met minder context en strakkere regels, en dat houdt stand.
Kern
Bouw eerst het urgente pad en automatiseer de saaie 95% daarna; de agent verdient zijn loon op de berichten die een vermoeid mens zou missen.
FAQ
Waarom het WhatsApp Business Platform gebruiken en niet de consumenten-app automatiseren?
De consumenten-app kun je niet legaal automatiseren en breekt bij elke Meta-update. De Business Platform Cloud API geeft je webhooks, templates, een gedocumenteerd retentiemodel, en een duidelijke verwerkersrol onder de AVG.
Hoe gaat de agent om met Nederlands dialect en informele spelling?
De rode-vlag-lijst bevat veelvoorkomende verschrijvingen (gevalle, blod, kortasem), aangevuld door de verpleging zelf. De LLM doet de rest. We meten wekelijks regressie tegen een gelabelde fixture-set van 200 echte berichten.
Wat gebeurt er als de LLM offline of traag is?
De regex-rode-vlag-stap draait eerst en kan op eigen houtje escaleren zonder output van de classifier. Vallen beide uit, dan komt elk inkomend bericht zonder filter in het menselijke triagekanaal terecht, zodat er nooit stilzwijgend iets verloren gaat.
Hoe wordt de 90-seconden-SLA gemeten?
Vanaf het inkomende tijdstempel uit Meta's webhook-payload tot het moment dat de dienstdoende verpleegkundige op acknowledge tikt in Telegram of de Twilio SIP-call beantwoordt. Beide schrijven naar dezelfde Postgres-regel. We rapporteren mediaan, p95 en p99 wekelijks.
Werkt dezelfde architectuur ook voor huisartsen of tandartspraktijken?
Ja, met een herziene categorieënset en een andere rode-vlag-lijst. De classifier in twee stappen, de redactie-vault en het fan-out-alertpatroon blijven hetzelfde. De woordenschat en escalatiematrix moeten worden herschreven door de klinisch verantwoordelijke.