Als je mijn vorige posts hebt gelezen, weet je dat VorstersNV mijn eigen freelance-platform is: een verzameling AI-agents, een FastAPI-backend, een klantportal en een hele hoop YAML-prompts die samen het werk van een IT/AI-consultant ondersteunen. Sinds eind maart zijn er tien sprints gepasseerd (W27 tot W36, in mijn eigen sprint-nummering) en als ik nu terugkijk, is er meer veranderd dan ik dacht. Niet één grote ommekeer — gewoon tien kleine beslissingen die het platform stevig hebben gemaakt.
De rode draad
Alles wat ik dit kwartaal heb toegevoegd, is opt-in en observability-first. Niets blokkeert standaard. Alles laat sporen na. Dat klinkt saai, maar het is precies wat een platform betrouwbaar maakt.
🚪 W27–W28: van losse scripts naar één deur
Eind maart had ik een eerlijk probleem: elke agent praatte rechtstreeks met Ollama via z'n eigen HTTP-clientje. Werkte prima, totdat ik wou wisselen van model, of een timeout-policy wou veranderen, of überhaupt wou weten hoeveel tokens een sprint kostte. Toen begon W28-29 met wat ik intern de "LLM Gateway" noem: één punt waar alle modelaanroepen door gaan, met retries, timeouts en logging op één plek.
Niets bijzonders aan het idee — elke serieuze AI-stack heeft zoiets. Wat wel leuk was om mee te maken: zodra die gateway er stond, kreeg ik er bijna gratis kostentracering bij. Geen aparte module bouwen, gewoon één plek waar je tokens telt. Dat soort cadeautjes krijg je alleen als je vroeg genoeg een chokepoint inbouwt.
💬 W31: feedback uit het geheugen, in de database
Hiervoor had ik een feedback-loop in-memory: agents kregen na elke run een score, en die scores hielpen om prompts te itereren. Klinkt netjes. Probleem: zodra de Python-process herstart, was alle leerwerk weg. W31 was een vervelende, maar noodzakelijke sprint: dezelfde feedback-API behouden, maar er een echte database achter zetten zodat het over restarts heen blijft bestaan.
Backwards-compatibel migreren is geen heldenwerk. Het is gewoon de prijs die je betaalt om jezelf later niet te haten.
Wat ik onderweg heb geleerd: noem je named HITL-gates (de momenten waarop een mens beslist) écht expliciet in code, niet als "stap_3". Vier vaste namen — "review_prompt", "approve_release", "validate_output", "confirm_action" — maken het meteen veel makkelijker om dashboards en alerts op te bouwen. Klein detail, grote opluchting later.
🔐 W34: tenants, traces en testdata die mocht bestaan
W34 was eigenlijk drie kleine sprints in één. Eerst: de SSE-stream (server-sent events naar de klantportal) was vrolijk public. Niet handig als verschillende klanten hetzelfde platform delen. Ik heb een tenant-check ingebouwd die controleert dat de mailadres-header bij de juiste trace hoort. 401 als je niet ingelogd bent, 403 als je in een andere tenant zit, 404 als de trace niet bestaat — drie verschillende foutcodes die elk hun eigen verhaal vertellen.
Tweede stuk: een per-step tracer. Elke agent-stap krijgt een eigen ID met parent-relatie, latency, tokens in/uit, status, eventuele foutmelding. JSONL-bestanden, geen externe tool nodig, OpenTelemetry-compatibel schema. Mijn laptop heeft geen Datadog-budget, maar JSONL en jq doen meer dan je denkt.
Derde stuk — en daar ben ik het meest trots op — was de testdata-strategie. Ik wou eindelijk realistische Belgische data kunnen genereren zonder ooit een echte klant te kopiëren. Resultaat: een synthetic generator die geldige IBANs (mod-97 check), geldige BTW-nummers en correcte +32-mobielnummers spuwt. Plus een k-anonymity validator die mijn datasets toetst aan een drempel van k=5 op postcode + leeftijdsbucket + sector. Zodra ik dat had, was het plots veel rustiger in mijn hoofd om met klantdata-achtige sets te werken.
Belgische realiteit
Geen Lorem Ipsum-adressen meer. IBAN met geldige checksum, BTW-nr met juist controlegetal, postcodes uit echte gemeenten.
k-anonymity by default
k=5 op de quasi-identifiers postcode, leeftijdsbucket en sector. Faalt de check, dan faalt de test. Punt.
🚦 W35: twee CI-poortjes die ik nooit meer wil missen
Tegen W35 had ik twee parallelle agent-vloten: één voor Claude Code (in `.claude/agents/`) en één voor GitHub Copilot (in `.github/agents/`). Goed nieuws: ik kon platform-onafhankelijk werken. Slecht nieuws: ze gingen steeds verder uiteen lopen. Een agent die in Claude bestond maar niet in Copilot was — of erger, eentje met een andere naam.
De fix: een sync-checker in CI. Bij elke push verifieert een script dat élke Copilot-agent een Claude-tegenhanger heeft met dezelfde frontmatter-naam. Heel saai script, heel waardevol resultaat. En meteen erbij: een mini eval-harness die golden test cases uit YAML-bestanden door de agents draait en faalt als de score onder 0.85 zakt. Twee nieuwe CI-jobs, één nachtrust meer.
Klein Windows-lesje onderweg
Mijn CI-scripts gebruikten ✓ en ✗ als statusmarkers. Op Windows-console (cp1252) crashen die vrolijk. Sinds W35 is alles ASCII: [OK] en [DRIFT]. Saaier, maar werkt overal.
🧪 W36: A/B-testing en compliance, allebei opt-in
De afgelopen sprint (W36, deze week net gecommit) was de meest interessante. Ik had al langer twee mooie modules liggen die te weinig gebruikt werden: een prompt A/B-tester en een compliance-engine die outputs scant op GDPR, NIS2, BTW-regels en de EU AI Act. Het probleem was niet dat ze niet werkten — het was dat je ze handmatig moest aanroepen, en dat doe je dan dus nooit.
De oplossing was eenvoudiger dan ik dacht: twee mini-registries in de agent-runner. Wil je een agent in een A/B-test? Eén regel: `enroll_agent_in_ab_test("klantenservice_agent_v2", "prompt-test-A")`. Vanaf dat moment kiest de runtime automatisch een variant per run (deterministisch op session-id, dus reproduceerbaar), meet de latency met `time.perf_counter()`, en logt het resultaat met een quality-score-heuristiek. Wil je compliance-scanning aan? Ook één regel: `enable_compliance_for_agent("product_beschrijving_agent")`. Klaar.
Belangrijkste designkeuze: zonder enrollment verandert er niets. Géén ab_tester-calls, géén compliance-engine-calls, géén extra latency. Het platform blijft exact werken zoals voorheen. En als de compliance-engine zelf crasht? Dan logt de runner een warning en gaat door — observability-first, niet blokkeren tot het écht moet.
# Voor: handmatig overal A/B-logic
variant = tester.select_variant("test-A", seed=session_id)
result = await agent.run(input, prompt_override=variant.template)
tester.record_result(ABTestResult(...))
# Na: één registratieregel, runner doet de rest
enroll_agent_in_ab_test("klantenservice_agent_v2", "test-A")
result = await agent.run(input, session_id=session_id)📚 Wat ik hieruit meeneem
1. Bouw chokepoints zo vroeg mogelijk
Eén LLM-gateway, één agent-runner, één registratiepunt voor cross-cutting concerns. Als je later wil meten, loggen of routeren, ben je blij dat er één deur is.
2. Opt-in slaat opt-out
Een platform dat standaard niets doet maar alles kán doen mits één regel code, is veel rustiger om te onderhouden dan een platform vol globale features die je telkens moet uitzetten.
3. Observability vóór blokkering
Eerst zien dát het mis gaat. Dan beslissen of je het wil blokkeren. Andersom riskeer je een platform dat in productie het werk van klanten tegenhoudt door false positives.
4. Backwards-compat is een feature
Bij élke sprint heb ik mezelf de regel opgelegd: "oude code moet exact blijven werken". Saai om te schrijven, ongelofelijk fijn als je twee maanden later iets opzij wil zetten zonder een halve dag te debuggen.
5. CI is je beste consultant
Twee CI-poortjes (agent-sync + eval-harness) vangen problemen op die ik anders pas bij een klant zou ontdekken. Dat is de goedkoopste consultancy die ik ooit heb ingehuurd — zelfgeschreven, gratis, 24/7 beschikbaar.
🔭 Wat volgt
Op de backlog staat nog wat W12-werk uit Revisie 6: de gaming-desktop integreren als remote Ollama-endpoint (laptop-CPU haalt het echt niet voor de grotere modellen), een v2 cost-forecaster zodra ik genoeg historische data heb, en een portal-analytics-dashboard zodat ik zelf kan zien welke klanten welke agents het meest gebruiken. Daarnaast wil ik de compliance-engine van W36 van observability-only naar selectieve hard-blocking brengen — maar pas nadat ik een paar weken WARNING-logs heb verzameld om de false positive rate in te schatten.
En misschien wel het belangrijkste: ik ga deze patronen — opt-in registries, observability-first, named gates, synthetic Belgische testdata — bewust meenemen naar klantopdrachten. Want als ze voor mijn eigen platform werken, werken ze waarschijnlijk ook voor een Belgische KMO die niet zit te wachten op een AI-revolutie, maar op een agent die gewoon doet wat hij belooft, zonder dat er ooit een privacy-officer wakker van ligt.
Een platform is geen launch. Het is tien sprints op rij niets spectaculairs doen — en het toch elke week iets beter laten werken.

