AI agents
Chat agent evals: zeven harnesses die je zelf herdraait
Na twee Hacker News-threads over ontspoorde agents stelde elke klant ons dezelfde vraag: hoe weten we dat onze chat agent gisteravond niets stoms heeft gezegd?

Het is dinsdag, 09:14. Marleen draait de operations bij een Nederlands logistiek bedrijf van 30 mensen. In haar inbox staan vier tickets die allemaal een variant zijn van "de chat agent zei gisteren iets raars". Eén klant kreeg een leverdatum genoemd die niet bestaat. Eén kreeg te horen dat het kantoor zondag open is. Eén vroeg om een terugbetaling en kreeg een recept voor stamppot. Ze heeft geen data scientist in dienst. Ze heeft een laptop, een koffie en negentig minuten tot de standup van 10:30.
Voor dit moment hebben we evaluation harnesses gebouwd.
De afgelopen twee weken maakten dat verhaal onvermijdelijk. Twee Hacker News-threads op de voorpagina, weken uit elkaar, beschreven agents in productie die zich gedroegen op een manier die hun beheerders niet hadden kunnen voorspellen: eentje lekte system-prompt-inhoud onder een nieuwe jailbreak (een fouttype dat Simon Willison al ruim twee jaar in kaart brengt), een ander brandde een cloudrekening van vier cijfers op in een runaway loop die een hobbynetwerk scande. Niets hiervan is verrassend. Alles is operationeel duur. De les is niet "stop met agents". De les is "draai evals die je daadwerkelijk op een dinsdag opnieuw kunt draaien".
Wat volgt is de veldgids die we nu meeleveren bij elke klantgerichte chat agent. Zeven evaluation harnesses, op volgorde van "Marleen herdraait dit in drie minuten" tot "Marleen herdraait dit als ze een uur vrijhoudt en onze notities erbij pakt". Alle zeven draaien op haar laptop. Geen ervan vereist een notebook, een GPU, of iemand met de titel MLE. De volgorde is bewust: hoe goedkoper een harness opnieuw te draaien is, hoe hoger hij staat.
1. Golden conversation replay
Dit is de basis. Veertig tot tachtig echte gesprekken uit de afgelopen maand, met toestemming vastgelegd, end-to-end opnieuw afgespeeld tegen de agent bij elke modelupgrade of promptwijziging. We diffen het nieuwe transcript tegen het goedgekeurde transcript en markeren alles boven een gelijkenisdrempel.
Marleen herdraait dit door één commando te typen:
npm run evals:golden
# walks /evals/golden/*.json, posts each turn,
# diffs assistant output against approved snapshot,
# prints a green/red table with the failing lines highlighted.
Is de diff groen, ship maar. Is hij rood op een gesprek dat Marleen herkent, dan opent ze het falende snapshot ernaast en bepaalt of het nieuwe gedrag beter, slechter of gelijkwaardig is. Dit vangt ongeveer 70% van de regressies in onze portfolio.
Het lastige is het kiezen van die veertig gesprekken. We sturen de set richting edge cases: de boze klant, de klant die halverwege van taal wisselt, de klant die de agent iets vraagt wat hij niet kan, de klant die drie alinea's van een concurrentensite in de chat plakt. Een golden set van veertig "goedemorgen, wat zijn jullie openingstijden"-gesprekken zegt je niks. We bouwen deze set elk kwartaal opnieuw op uit de tickets van het vorige kwartaal, en we halen elk gesprek eruit waarvan het onderliggende beleid sindsdien is veranderd.
2. PII- en policy-regex sweep
Elke reply van de agent uit de afgelopen zeven dagen, door een batterij regexen. Het Nederlandse BSN-formaat. IBAN. Mail en telefoon. Vermelding van concurrent-merknamen. De drie woorden die een senior bij de klant persoonlijk heeft gemarkeerd als "dat zeggen we nooit".
npm run evals:pii -- --since=7d
# scans logs/agent-replies/*.jsonl
# matches against /evals/regex/*.yaml
# writes a CSV of hits to /reports/pii-YYYY-MM-DD.csv
De CSV opent in Excel. Marleen scant 'm. Geen rijen, dan sluit ze 'm. Eén rij, dan opent ze het gesprek en bepaalt of de agent iets heeft gelekt of dat de klant het zelf heeft geplakt. We draaien dit 's nachts in cron, maar de waarde van een dinsdagse herrun is dat Marleen dezelfde view ziet als wij.
De regex-lijst is een gezamenlijk product. Elke keer dat de klant in een Slack-thread zegt "dit zouden we nooit moeten zeggen", wordt die zin een nieuwe pattern. Na zes maanden heeft een typische lijst 40 tot 60 entries en leest hij als een stille opname van de werkelijke merkdiscipline van het team.
3. Tool-call audit diff
Agents die dingen kunnen doen (een slot boeken, een bestelling terugbetalen, een offerte sturen) laten een spoor van tool calls achter. We loggen de JSON van elke tool call naast het gesprek dat 'm triggerde. De harness speelt het gesprek opnieuw af in dry-run mode en diffeen de nieuwe tool-call-sequentie tegen het origineel.
npm run evals:tools -- --since=7d --dry-run
# replays each conversation against the live agent
# with the tool runtime swapped for a recorder
# diffs the new tool-call JSON against the logged one
Wat dit vangt: de agent die eerst één verduidelijkingsvraag stelde voor het boeken, boekt nu meteen. De agent die alleen terugbetaalde op een specifieke reden-code, betaalt nu terug bij elke klacht. Dit zijn de fouten die geld kosten voordat iemand het doorheeft.
De dry-run runtime moet realistische antwoorden teruggeven, geen nulls. Een agent die null terugkrijgt van check_inventory gedraagt zich totaal anders dan de productie-agent. We houden een snapshot bij van recente tool-responses en spelen die af. Sla je dit over, dan liegt je eval tegen je.
4. Kosten- en latency-plafond
Voor elk golden gesprek leggen we p50 en p95 vast voor tokens-in, tokens-out en wall-clock. De harness laat de build falen als het nieuwe model of de nieuwe prompt het plafond met meer dan 20% overschrijdt.
npm run evals:budget
# runs the golden set, records token + latency stats,
# compares against /evals/budget.json,
# exits 1 if any ceiling exceeded by >20%.
De drempel van 20% is geen magisch getal. Hij is breed genoeg dat normale variatie in retrieval-context-grootte hem niet aftrapt, en strak genoeg dat een gedragsverandering van welke omvang dan ook dat wel doet. We begonnen met 10% en werden moe van het onderzoeken van ruis. We probeerden 30% en misten een regressie die de kosten per gesprek verdubbelde op een long tail van edge cases. 20% is nu al twee jaar de juiste instelling.
Dit is de harness die het runaway-bill-incident had gevangen voordat het echt geld kostte. Niet omdat hij een loop in productie zou stoppen (dat doet hij niet), maar omdat hij de beheerder had laten zien dat de nieuwe prompt tien tool calls triggerde waar de oude er twee triggerde. Dat is het signaal stroomopwaarts. Een doorbroken plafond is bijna nooit een geïsoleerd kostenverhaal. Het is een gedragsverhaal met een prijskaartje eraan.
5. Knowledge base grounding check
Voor agents met een RAG-laag moet elke feitelijke claim in het antwoord te herleiden zijn naar een opgehaalde chunk. We halen noun phrases uit het assistant-antwoord, zoeken die op in de retrieval-context, en markeren claims zonder bron.
npm run evals:grounding -- --since=7d
# for each reply, runs an extractor over the assistant text,
# checks each extracted claim against the retrieval context,
# writes /reports/ungrounded-YYYY-MM-DD.csv
Marleen hoeft niet elke claim te lezen. Ze leest de rijen waar de score onder 0.6 zit. In een typische week zijn dat zo'n 12 rijen. De helft is onschuldig ("ons team helpt je graag" heeft geen bron nodig). De andere helft is de agent die met overtuiging dingen verzint. Dit is de harness die Nederlandse ops-leads het meest waardevol vinden, want verzonnen feiten in het Nederlands klinken exact zo vloeiend als echte, en een Nederlandse klant citeert de agent op papier terug.
6. Prompt-injection batterij
Een samengestelde suite van zo'n 200 bekende injectiepogingen, getrokken uit de OWASP LLM Top 10-playbook, publieke CTF write-ups en de echt creatieve pogingen die onze eigen klanten hebben geprobeerd. We herdraaien de volledige suite tegen de agent bij elke promptwijziging.
npm run evals:injections
# posts each attack payload as a user turn,
# checks the response against a pass/fail rubric,
# prints a category breakdown (system-prompt leak,
# tool-misuse, refusal bypass, exfiltration, etc).
Elke promptwijziging, elke modelwijziging, krijgt de volledige batterij. Marleen herdraait 'm voordat ze een prompt-edit ship. Het duurt zo'n 11 minuten voor 200 payloads tegen een streaming agent.
Eén nuance: de batterij is geen pass/fail-score voor het model. Het is een regressietest voor jouw specifieke system prompt en tool-oppervlak. De payload die vorige week veilig was, is dat deze week misschien niet meer, omdat het model onder je voeten is veranderd. Dat is precies het punt. Vendors kondigen niet altijd aan dat ze een checkpoint hertrainen, en de publieke benchmarks die ze noemen dekken zelden je precieze threat model.
7. Toon- en persona-drift
Deze vereist het meeste oordeel, en daarom staat hij als laatste. De agent heeft een gedefinieerde stem (we schrijven een voice constitution van één pagina met elke klant). We samplen 20 recente antwoorden, voeden ze door een kleine LLM-judge met de constitution in context, en vragen: klinkt dit antwoord als het merk?
npm run evals:voice -- --sample=20
# samples replies from the last 7 days,
# scores each one against /evals/voice-constitution.md,
# writes a CSV with score, rationale, and reply text.
Scores onder 7/10 krijgen een leesbeurt. De rationale klopt meestal. De antwoorden die falen vormen vaak clusters: te formeel Nederlands op een merk dat gewoon Nederlands spreekt, Engelse stopwoorden op een Nederlands-only merk, excuses waar de klant niks fout deed. Dit is de harness die de langzame drift laat zien die je van dag tot dag niet voelt.
De zeven op volgorde van moeite
De volgorde doet ertoe. De eerste vijf draait Marleen als één make-target voor de lunch. Zes en zeven zijn wekelijks, door ons gedraaid met een samenvatting in een Slack-kanaal richting de klant. Het punt van de volgorde is niet dat de latere er minder toe doen, het is dat de eerdere de evals zijn die een ops-lead daadwerkelijk blijft draaien. Een eval die niet wordt herdraaid is geen eval. Het is een eenmalig moment.
Bouw je deze week maar één van deze, bouw dan de golden replay. Bouw je er twee, voeg de PII-sweep toe. Bouw je er drie, voeg de tool-call diff toe. Die drie vangen het grootste deel van de fouten die we in productie zien, en alle drie passen ze op een laptop.
Toen we vorig kwartaal de support-agent bouwden voor een Nederlands facility-management-bedrijf, was het de tool-call diff die een vier-cijferig probleem ving voordat het live ging: een prompt-tweak die alleen beleefder moest klinken, stuurde uiteindelijk bij elke "bedankt, doei" een monteur op pad. Na die ervaring schreven we onze bredere veldnotities op over hoe we AI-agents bouwen en in productie houden.
Het kleinste wat je vandaag kunt doen: open een terminal, maak /evals/golden/ aan, plak vijf echte gesprekken uit de logs van je agent, en schrijf de loop die ze opnieuw afspeelt. Twintig minuten werk. Elke modelupgrade daarna wordt goedkoper.
Kern
Een eval die op dinsdag niet wordt herdraaid, is geen eval. Het is een eenmalige actie. Bouw eerst de vijf die op een laptop draaien, daarna pas de rest.
FAQ
Heb ik een data scientist nodig om deze evaluation harnesses te draaien?
Nee. De eerste vijf draaien als command-line scripts tegen logs die je al hebt. De twee die oordeel vereisen (injections, voice drift) zijn nog steeds leesbaar voor een ops-lead zonder ML-achtergrond.
Hoe vaak moet ik de golden conversation replay opnieuw draaien?
Voor elke promptwijziging, elke modelupgrade en als nightly cron. De kosten zijn klein, de regressies die hij vangt niet.
Wat als ik nog geen gelogde gesprekken heb om een golden set mee te bouwen?
Begin met synthetische gesprekken die je supportteam schrijft. Vervang ze door echte vastgelegde gesprekken (met toestemming) zodra je een week productieverkeer hebt.
Waarom rangschik je de harnesses op moeite in plaats van op belang?
Omdat belang niet voorspelt of een harness opnieuw gedraaid wordt. Moeite wel. De harness die elke dinsdag draait vangt meer dan de harness die je één keer draait.