Chat agents
Dispatcher chat-agent: 2.640 ritwijzigingen per week
Een Gronings logistiek bedrijf met 23 mensen koppelde een dispatcher chat-agent aan een 13 jaar oude Transics-stack. Hij draait nu 2.640 ritwijzigingen per week zonder de ELD-writes te breken.

Een chauffeur meldt zich om 14:23 op een dinsdag. De Volvo FH die hij rijdt staat al twintig minuten stil achter een gesloten brug bij Bremen. Hij moet om, en wel voordat zijn pauzeraam om 15:00 dichtklapt. De dispatcher in Groningen heeft drie andere chauffeurs in de wachtrij, twee telefoonlijnen die knipperen, en een planner die vraagt naar een klasse-3 brandstoflevering die een uur geleden al onderweg had moeten zijn.
Dat is de situatie waar onze klant elke werkdagmiddag in stapte. De leverancier verkoopt dispatchsoftware aan middelgrote Nederlandse en Duitse vervoerders, en zat sinds 2013 vast in hetzelfde operationele patroon: zodra er iets verandert op de weg, wordt de dispatcher een telefooncentrale en de planner een bottleneck.
Dit is het verhaal van hoe we een chat-agent voor de dispatcher-inbox hebben gezet, wat we moesten doen om hem te laten praten met een stack die ouder is dan de meeste mensen op de operations-afdeling, en welke randvoorwaarden we hebben behouden versus losgelaten.
De stack die we erbij kregen
De klant draait Transics voor telematica. De originele units in de cabines zijn van 2013 en 2014, met hier en daar wat nieuwere TX-SKY units na een refresh in 2019. Routing komt van PTV xServer, de on-premise versie, draaiend op één Windows Server 2019-bak in hun Groningse datacenter. Rij- en rusttijden lopen via een tachograafintegratie. Het hele zaakje praat met een custom .NET dispatch-UI via wat het team "de FMS-bus" noemt, in essentie een RabbitMQ-broker met dertien jaar schema's eraan vastgeplakt.
Het ELD-write endpoint (degene die de gelogde route van een chauffeur in het tachograafrecord bijwerkt) is het meest gevoelige onderdeel. Schrijf je een verkeerde routetoewijzing naar de ELD, dan zit je met een compliance-probleem waar de verzekeraar van de klant bij de volgende kwartaalaudit over leest. Niemand in het team liet ons aan dat endpoint komen zonder een menselijke goedkeuringslaag ervoor.
Wat de chat-agent eigenlijk doet
De agent zit voor de dispatcher-inbox, niet voor de chauffeur. Chauffeurs praten nog steeds met dispatchers zoals ze dat altijd al deden: korte berichten via de Transics-terminal in de cabine, of via een apart radiokanaal dat voor dit verhaal niet uitmaakt.
Als een chauffeur een bericht stuurt als "brug Bremen dicht, kan ik via A28," landt het bericht in de dispatch-UI en leest de chat-agent het tegelijkertijd. De agent doet drie dingen:
- Hij classificeert het bericht. Is dit een verzoek tot routewijziging, een vertragingsmelding, een ladingissue, een pauzevraag, of iets dat hij naar een mens moet doorzetten?
- Is het een routewijziging, dan haalt hij de actieve rit op uit PTV xServer, berekent de voorgestelde omweg en bereidt een write-payload voor beide systemen: PTV (de nieuwe route-geometrie) en Transics (de nieuwe ETA's).
- Hij controleert de vrachtbrief op ADR-lading. Vervoert de rit iets in ADR-klasse 3 (brandbare vloeistoffen: het meeste wat deze klant rijdt is brandstof, oplosmiddelen en lijmen), dan schrijft de agent niet. Hij plaatst het voorstel in een plannerswachtrij en zegt tegen de chauffeur "wacht op planner."
Voor niet-ADR-lading schrijft de agent direct naar de FMS-bus en bevestigt hij naar de chauffeur. Voor ADR-lading klikt de planner één knop en gebeurt dezelfde write, met het user-ID van de planner op de audit-row gestempeld.
Het volume dat de agent nu verwerkt is 2.640 routewijzigingsverzoeken per week, gemeten over de vier weken vóór dit stuk. Voor de agent verwerkten dispatchers ongeveer hetzelfde volume met drie fulltime dispatchers en een avonddienst-bereikbaarheidsrooster. De agent vervangt de dispatchers niet; hij doet de routinegevallen en laat hen focussen op de rommelige (multi-stop omleidingen, klantcallbacks, alles met een grensovergang of een uitzonderlijke vervoersvergunning).
Het latency-budget van 800 ms
De FMS-bus heeft een hard latency-budget. Duurt een write naar de bus langer dan een seconde, dan toont de terminal in de cabine de chauffeur een "vertraagd"-waarschuwing, waarna hij belt om te bevestigen. Daarmee is het hele punt van de agent weg.
We hebben de bestaande dispatcher-roundtrip gemeten op een mediaan van 320 ms (dispatcher leest bericht, typt antwoord, drukt op verzenden, write komt op de bus aan). De agent moest binnen dezelfde envelope passen. We zetten ons budget op 800 ms end-to-end, van binnenkomst bericht tot bevestiging van de bus-write.
De twee grote kostenposten waren de LLM-call en de PTV-routeberekening. De routeberekening konden we niet inkorten: PTV xServer doet 180 tot 240 ms over een typische omweg in Noord-Duitsland, en we gingen geen 13 jaar oude routing-engine vervangen voor dit project. Dus we hebben de LLM-call zo hard mogelijk teruggesnoeid:
Message classification: small model, ~80 ms p50
Intent extraction: same call as classification, structured output
Route validation: deterministic code, no LLM, ~15 ms
ADR check: SQL lookup against load manifest, ~8 ms
Write to FMS-bus: ~40 ms when bus is not backed up
Dat liet ons ongeveer 450 ms speling op een schone run, die we de eerste keer dat de FMS-bus volliep achter een Transics-firmware-push snel opbrandden toen onze writes begonnen te timeouten. We voegden een circuit breaker toe die de agent in "vraag het de dispatcher"-modus gooit als de bus-latency langer dan tien seconden boven de 600 ms blijft. De dispatcher krijgt dan een banner met "agent gepauzeerd, FMS-bus traag." We hebben de breaker sinds week drie niet meer nodig gehad.
Wat we de agent niet hebben laten doen
De lijst met dingen die de agent expliciet niet mag doen is langer dan de lijst met dingen die hij wel doet. Dit is het deel van elk chat-agent-project dat uit de demo wordt geknipt en vervolgens in productie het ding wordt dat je hachje redt.
Heeft een chat-agent write-toegang tot een gereguleerd systeem (tachograafrecords, financiële grootboeken, klant-PII), dan is de goedkeuringswachtrij niet optioneel. Het is de dragende muur van het hele ontwerp.
De agent mag niet:
- Naar het ELD-endpoint schrijven voor een rit met ADR-klasse-3-lading zonder goedkeuring van de planner.
- Een route bevestigen die een grens overgaat waar de chauffeur geen cabotagevergunning voor heeft. (We cachen vergunningsdata 's nachts; de agent leest uit de cache.)
- Een door de klant opgelegd tijdvenster overschrijven zonder een vlag in de dispatcher-wachtrij te zetten.
- Een chauffeur antwoorden in een andere taal dan die op zijn cabineterminal staat ingesteld. De agent respecteert de locale van de chauffeur, ook als de planner in het Nederlands typt.
De eerste drie regels kwamen van de klant. De vierde kwam van een Poolse chauffeur die in week twee een Nederlands antwoord kreeg, in verwarring zijn dispatcher belde, en zo de hele "we hebben net een telefoontje voor je bespaard"-pitch in één minuut tenietdeed.
Het probleem met de message log
Elk bericht dat de agent leest, classificeert en op acteert wordt gelogd. Bericht van de chauffeur, classificatie van de agent, routevoorstel, write-payload, bus-respons, planner-actie (indien aanwezig). De log is het audittrail voor de ELD-writes en de plannergoedkeuringen, en de verzekeraar van de klant wil hem kunnen inzien.
De log groeit met ongeveer 95.000 rijen per week over chauffeurs, agent-stappen en bus-events. Na zes maanden is dat een tabel die je niet meer onderhouden krijgt. We liepen tegen het probleem aan waar de Hacker News-crowd al een decennium over schrijft: de enige schaalbare delete in Postgres is DROP TABLE. Een nachtelijke DELETE WHERE created_at < now() - interval '90 days' zou de query-latency urenlang om zeep helpen en de tabel oneindig laten opzwellen.
We hebben de log gepartitioneerd op ISO-week. Oude partities worden losgekoppeld en óf gearchiveerd naar cold storage óf gedropt, afhankelijk van het bewaarbeleid dat aan de row-class hangt. ELD-write audit-rows blijven zeven jaar (compliance-eis onder EU-tachograafverordening 165/2014). Chauffeursgekletsel leeft dertig dagen. Twee verschillende partitietrees, één log-tabel per tree, wekelijkse cron-job die loskoppelt en óf archiveert óf dropt.
Dit is saai infrastructuurwerk en het is het verschil tussen een chat-agent die je een jaar kunt laten draaien en eentje die je in februari moet herbouwen.
Wat er in week één stuk ging
Er gingen drie dingen stuk. Ze zijn het noemen waard omdat dit precies de dingen zijn die in elk vergelijkbaar project stuk zullen gaan.
Het eerste was timezone drift. De PTV xServer staat op Europe/Amsterdam. De Transics-units rapporteren in UTC. De .NET dispatch-UI doet zijn eigen conversie. Onze agent koos op dag één de verkeerde source of truth en bevestigde een ETA van 14:00 aan een chauffeur wiens cabine 12:00 las. De chauffeur dacht twee uur extra te hebben; de klant had een gemiste slot.
Het tweede was het verschil tussen een route die bestaat en een route die een truck kan rijden. PTV rekent vrolijk een "kortste pad" uit door een dorpsstraat waar de truck fysiek niet in kan draaien. Het standaard xServer-profiel heeft truckafmetingen, maar de agent gaf bij omleidingen het verkeerde profile-ID mee. We hadden het op dag twee in de gaten nadat een chauffeur beleefd over de radio meldde "deze kan ik niet."
Het derde was een classifier edge case. De agent behandelde "moet ik tanken?" als een routewijzigingsverzoek, omdat het model zich vastpinde op de impliciete omweg. We hebben een kleine set canonieke voorbeelden aan de classifier toegevoegd en het false-positive percentage zakte van 4% naar onder de 0,3%.
Onder alle drie zit hetzelfde patroon: het lastige aan een chat-agent voor een legacy stack zetten is niet de LLM. Het zijn de zeven kleine aannames die in het oude systeem zijn ingebakken en die de agent moet leren respecteren.
Waar de cijfers landden
Het dispatcher-team is niet gekrompen. Dezelfde drie dispatchers en hetzelfde bereikbaarheidsrooster draaien nog. Wat veranderde is wat ze overdag doen. De middagpiek (tussen 14:00 en 17:00, als chauffeurs in de file komen en hun avond proberen om te onderhandelen) vroeg vroeger alle drie de dispatchers op de chat en een planner erbij. Nu houdt één dispatcher de vloer, doet de agent de routine-omleidingen en ziet de planner alleen ADR-ritten.
De 2.640 wekelijkse routewijzigingen splitsen op in grofweg 78% volledig door de agent afgehandeld, 15% naar een planner voor ADR-goedkeuring (en daarna geschreven), en 7% terug naar een menselijke dispatcher omdat de agent niet zeker was. Die laatste bucket houden we het scherpst in de gaten; loopt hij op, dan degradeert de agent en moeten we naar de classifier kijken.
Het kleinste nuttige ding dat je deze week kunt doen
Draai je een dispatch-operatie op een stack die ouder is dan je nieuwste collega, meet dan het read-to-write latency-budget op je meest gevoelige endpoint. Niet het gemiddelde. De 99e percentiel, op het slechtste uur van de slechtste dag. Welk getal eruit komt, dát is het budget waar elke toekomstige automatisering in moet passen, en het is bijna altijd kleiner dan mensen denken.
Toen we deze dispatcher-agent bouwden voor het Groningse bedrijf, was het ding waar we steeds op terugkwamen de plannerswachtrij: de agent verdiende pas het recht om naar ELD te schrijven omdat we voor de ladingsklasse die ertoe doet een menselijke poort hadden ingebouwd. Dat patroon gebruiken we in de meeste van onze AI-agent projecten, omdat het de enige manier is waarop de agent zijn write-toegang houdt na de eerste audit.
Kern
Het lastige aan een chat-agent voor een legacy stack zetten is niet de LLM. Het zijn de zeven kleine aannames die in het oude systeem zijn ingebakken.
FAQ
Vervangt de chat-agent de dispatchers?
Nee. De teamgrootte is niet veranderd. De agent doet de routinematige routewijzigingen, zodat dispatchers zich kunnen richten op multi-stop omleidingen, grensovergangen, klantcallbacks en andere rommelige cases.
Waarom gaat ADR-lading via een planner in plaats van dat de agent zelf schrijft?
ADR-klasse 3 is brandbare lading. Een verkeerde ELD-entry wordt een compliance- en verzekeringsprobleem bij audit. De goedkeuring van de planner is de poort die de write-toegang van de agent verdedigbaar houdt.
Waar komt het latency-budget van 800 ms vandaan?
De Transics-terminal in de cabine toont een 'vertraagd'-waarschuwing als een bus-write meer dan een seconde duurt, waarna de chauffeur gaat bellen. We hebben intern 800 ms aangehouden zodat we die waarschuwing nooit triggeren.
Waarom de message log partitioneren in plaats van oude rijen verwijderen?
Een nachtelijke DELETE op een snelgroeiende Postgres-tabel blaast indexen op en lockt rijen. Partitioneren per week laat ons oude partities direct droppen en toch voldoen aan de zeven-jaars-bewaarplicht voor tachograafdata.
Werkt deze aanpak ook zonder PTV xServer?
Ja. Het patroon is stack-onafhankelijk: classificeer het chauffeursbericht, bereid een write voor, zet gevoelige lading door een menselijke wachtrij, en schrijf naar welke routing-engine en telematicasysteem je ook al draait.