← Blog

Chat agents

Chat agents voor havenlogistiek: een Rotterdamse case

Het is 06:47 in Rotterdam, de Maersk ETA is vannacht vier uur verschoven en een dispatcher zit op haar derde koffie. Wij vervingen die ochtend met één chat agent.

Jacob Molkenboer· Oprichter · A Brand New Company· 20 sep 2024· 8 min
Messing scheepstelegraaf, gevouwen manifest, groene seinvlag, ijzeren label en lakzegel op ivoorkleurig vloeiblad.

Het is 06:47 in Rotterdam. Linda, dispatcher bij een havenlogistieke broker die we Havenlijn zullen noemen (niet hun echte naam; in de afspraak mogen we de bouw delen, niet het merk), zit op haar derde browsertab en haar tweede koffie. De Maersk ETA voor de MSC Aniello is vannacht vier uur verschoven. Haar cross-dock partner op Maasvlakte II moet voor 07:30 weten of het slot van 14:00 nog staat. Haar spreadsheet heeft 38 regels. Twee zijn rood. Ze doet dit elke dinsdag, al negen jaar.

Toen Havenlijn ons in februari belde, vroegen ze niet om een AI-project. Ze vroegen om "een ding dat zorgt dat mensen om zes uur 's ochtends niet meer met de hand Maersk hoeven te checken". Vier dispatchers in vaste dienst, elk verantwoordelijk voor een deel van het carrier-portfolio, elk met hun eigen versie van hetzelfde Excel-bestand. Cross-dock slots bij de partnerfaciliteit werden telefonisch geboekt, per mail bevestigd en in WhatsApp nog eens nagebeld. Drie maanden later doet één Telegram chat agent dezelfde doorvoer. De vier dispatchers werken er nog. Hun werk is veranderd.

Het werk vóór de agent

Een havenlogistieke broker als Havenlijn verplaatst zelf geen containers. Ze zitten tussen de deepsea-carriers (Maersk, MSC, CMA CGM, Hapag-Lloyd) en de inland-operator die de container ophaalt bij de cross-dock en hem naar een magazijn in Tilburg of Venlo rijdt. De hele business zit in het gat tussen "het schip meert af bij ECT Delta" en "de truck staat op het juiste dock op het juiste uur". Mis je de timing, dan rekent de cross-dock demurrage of staat de chauffeur stil. In beide gevallen kost het de broker geld.

Elk van de vier dispatchers volgde één of twee carriers. Elke 30 tot 45 minuten tijdens hun shift openden ze het tracking-portaal van de carrier, plakten het containernummer erin, lazen de ETA, kopieerden die in de master-spreadsheet, herberekenden de slotboeking en belden of appten dan de cross-dock om aan te passen als er iets verschoven was. Op een drukke dinsdag handelden ze samen ongeveer 80 containers af. Het spreadsheet had 14 tabbladen.

Dit is geen kwestie van intelligentie. Het is een kwestie van geduld. Spreadsheets geven geen alarm als een getal in de nacht vier uur opschuift; mensen moeten blijven kijken. In de derde maand vroegen de dispatchers de oprichter openlijk of ze "in elk geval het staar-gedeelte" niet konden automatiseren.

Waarom Telegram het van het custom dashboard won

Onze eerste schets was een webdashboard. Dat is altijd zo. De dispatchers schoten het in de eerste workshop neer. "We zitten niet aan een bureau. We staan op de kade, in de cabine, op weg naar de printkamer. We hebben dit op onze telefoon nodig, in de chat die we toch al gebruiken." De operationele groep van Havenlijn leefde in één Telegram-groep genaamd Loods. Boekingsbevestigingen, foto's van beschadigde zegels, voice notes van chauffeurs die vastzaten bij de douane. Twaalf mensen, ongeveer 200 berichten per dag.

We hebben er even over gediscussieerd. Een dashboard geeft je filters, zoekfunctie, geschiedenis. Een chat agent geeft je aanwezigheid. De dispatchers hadden gelijk. Informatie die niet verschijnt op de plek waar ze toch al kijken, wordt niet gelezen. We bouwden tegen de Telegram Bot API en hielden een dunne webview achter de hand als back-up voor de oprichter, die van spreadsheets houdt.

Drie carrier-feeds, één waarheid

Maersk, MSC en CMA CGM hebben alle drie een container tracking API. De interfaces hadden niet verschillender kunnen zijn. Het developer-portaal van Maersk publiceert een nette OAuth2-flow en een gedocumenteerd Track & Trace endpoint dat voorspelbare JSON teruggeeft. De myMSC API is smaller en wordt harder gerate-limit dan de docs doen vermoeden. Het eBusiness-portaal van CMA CGM vraagt om credentials per klant en levert op sommige plekken nog SOAP-achtige XML terug.

De eerste taak van de bouw was een normaliser. Alles wat eronder hangt (de agent, de slotboeker, de cross-dock notifier) leest dezelfde vorm:

// carriers/normalize.ts
type CanonicalEta = {
  containerNo: string
  carrier: 'MAERSK' | 'MSC' | 'CMA'
  vessel: string
  port: string          // UN/LOCODE, e.g. NLRTM
  etaUtc: string        // ISO 8601, always UTC
  confidence: 'high' | 'medium' | 'low'
  fetchedAt: string
}

export function fromMaersk(raw: any): CanonicalEta {
  const arrival = raw.events.find((e: any) => e.eventCode === 'ARRI')
  return {
    containerNo: raw.equipmentReference,
    carrier: 'MAERSK',
    vessel: arrival.transport.vesselName,
    port: arrival.location.UNLocationCode,
    etaUtc: arrival.eventDateTime,           // already UTC
    confidence: arrival.classifierCode === 'EST' ? 'high' : 'medium',
    fetchedAt: new Date().toISOString(),
  }
}

De normaliser verbergt de rommel. Toen MSC in week drie hun datumformaat stilletjes veranderde (van 2026-03-14T08:00:00Z naar 14/03/2026 08:00 UTC), hoefde er maar één bestand aangepast te worden. De rest van het systeem bleef draaien.

Drie carriers, drie eigenaardigheden om te kennen. Maersk geeft ETA's terug in UTC maar zet z'n eigen interne events in lokale Hamburg-tijd, dus dezelfde aankomst van een schip kan twee keer in de feed verschijnen met verschillende tijdzones als je niet oppast. MSC stuurt een 200 OK met lege body terug wanneer hun backend overbelast is; wij behandelen leeg als mislukt, niet als gelukt. CMA CGM stopt met nieuwe events versturen voor een container op het moment dat hij de terminal uit gepoort wordt, dus de dispatchers moesten leren dat "geen update voor twee uur" soms betekent "alles is goed gegaan en je kunt stoppen met kijken".

We pollen Maersk elke 15 minuten, MSC elke 30, CMA elke 20. De cadans wordt bepaald door de rate limits van de carrier, niet door onze voorkeur. Maersk publiceert z'n limieten openlijk; MSC en CMA throttlen allebei stilletjes en stoppen met data teruggeven als je voorbij een drempel duwt die ze niet willen noemen.

Van ETA naar geboekt slot in 110 seconden

Als een ETA meer dan 90 minuten verschuift, komt de agent in actie. De flow is kort:

  1. Detecteer de verschuiving in de genormaliseerde feed.
  2. Zoek de betreffende container op in het boekingssysteem van de broker, een PHP-backoffice uit 2019 die we niet hebben aangeraakt.
  3. Bereken het nieuwe cross-dock window volgens de huisregel: aanmeertijd plus een buffer van vier uur voor het lossen, naar boven afgerond op de eerstvolgende 30 minuten.
  4. Plaats in de Loods-groep: "Container TCNU 4632181 (MAERSK) ETA verschoven van 14:00 naar 18:30. Voorgesteld nieuw cross-dock slot: 22:30 Maasvlakte II, Lane 4. Reageer met OK om te bevestigen, ALT om een alternatief voor te stellen."
  5. Bij OK van een dispatcher op de allow-list: dien de nieuwe boeking in via de API van de cross-dock.
  6. Plaats de bevestiging als reply in dezelfde thread.

Mediaan tijd van ETA-verschuiving tot bevestigd nieuw slot, gemeten over de eerste acht weken: 110 seconden. De vorige mediaan, afgeleid uit de timestamps in het spreadsheet van de dispatchers zelf, was 47 minuten. De agent is niet sneller omdat hij sneller denkt. Hij is sneller omdat hij al aan het kijken is.

De drempel van 90 minuten was niet ons getal. Die hebben de dispatchers ons gegeven. Alles onder 90 minuten wilden ze stilletjes opvangen binnen de bestaande buffer van vier uur; alles erboven wilden ze actief weten. De agent respecteert die drempel en stuurt om 18:00 een rustige samenvatting met elke sub-90 verschuiving die hij die dag heeft opgevangen, zodat niemand het overzicht kwijtraakt van wat eronder gebeurt.

Kernpunt

De chat-agent-laag is de makkelijke helft. Het echte werk in deze bouw zit in de normaliser voor de carrier-API's, waar drie eigenaardigheden van leveranciers samenvallen tot één canonieke vorm.

De schrik in week twee

In week twee hadden we ons eerste incident. De telefoon van een dispatcher werd via een ongerelateerd phishing-bericht gecompromitteerd, en de aanvaller probeerde cross-dock boekingscommando's te versturen binnen de Loods-groep. De agent wees de eerste poging af omdat het commando van een Telegram-user-ID kwam die niet op de allow-list stond. De dispatcher merkte het binnen enkele minuten op, we trokken de sessie in, er ging geen boeking door, er bewoog geen geld.

Dit is het kleine lesje achter het verhaal dat deze week op Hacker News rondging, waarin Meta bevestigde dat duizenden Instagram-accounts gecompromitteerd waren via misbruik van hun eigen support-chatbot. Als de agent het interface is, is het trust-model van de agent ook het security-model. Het model in de loop beschermt je niet; de regels eromheen doen dat. We hadden er vanaf dag één drie en die hebben in die week hun nut bewezen:

  • Alleen Telegram user IDs op de allow-list (niet usernames; die kan de eigenaar zelf wijzigen) mogen commando's uitvoeren.
  • Elk commando, de user-ID die het verstuurde, de afgeleide canonieke actie en de response van de carrier worden weggeschreven in een append-only Postgres-tabel die de oprichter kan auditen.
  • De agent voert nooit een boeking uit die hij niet met één commando kan terugdraaien. Kost het ongedaan maken drie stappen, dan heb je daar menselijke ogen voor nodig.

Wat de vier dispatchers nu doen

Geen van hen is z'n baan kwijtgeraakt. Dit is het stuk dat oprichters verkeerd lezen als ze case studies als deze tegenkomen. De vier dispatchers doen nu het werk dat het spreadsheet wegdrukte: contactgesprekken met carrier-reps, exception-handling als de agent iets meldt wat hij niet kan oplossen, samen met de oprichter het volume voor het volgende kwartaal plannen. De agent nam het staren over; de mensen hielden het oordeel.

Cijfers uit de eerste 90 dagen, uit de access logs en de maandrapportage van de broker:

  • Containers per week verwerkt: ongeveer 410, was ongeveer 310.
  • Demurrage-kosten per maand: 38% omlaag.
  • Slotwijzigingen bij de cross-dock per week: zelfde aantal, in minuten uitgevoerd in plaats van uren.
  • Overuren van dispatchers per maand: van 64 naar 11.

De Hacker News-discussie van deze week over ontwerpen met Claude in plaats van met Figma voelt hier in het klein hetzelfde aan: het meeste heen-en-weer in deze bouw gebeurde in gewoon Nederlands binnen de Telegram-groep, met de agent zelf die ons liet zien wat werkte. De dashboard-mockups van het begin zijn ongebruikt gebleven. Het chat-transcript was het ontwerpdocument.

Waar de bouw nu staat

Drie maanden onderweg draait de agent op een VPS van veertig euro per maand, praat met drie carrier-API's, boekt slots bij één cross-dock en plaatst berichten in één Telegram-groep. Hapag-Lloyd en ONE staan op de planning voor de tweede fase, zodra de eerste drie carriers negentig opeenvolgende dagen schoon hebben gedraaid. De oprichter vraagt nu naar voice: kan een dispatcher in de cabine de agent op 70 km/u bellen en hetzelfde antwoord krijgen? Waarschijnlijk wel. Andere post.

Toen we dit voor Havenlijn bouwden, onderschatten we hoeveel van het werk in de normaliser zat in plaats van in het model. Drie leveranciers-API's, drie eigenaardigheden, één canonieke vorm: daar werd de bouw gewonnen of verloren. Kijk je naar een vergelijkbare bouw, dan is het kleinste nuttige wat je vandaag kunt doen: open het developer-portaal van één van je carriers, schets de JSON-vorm die ze teruggeven, en teken daarnaast de vier velden die er voor jouw operatie écht toe doen. Die tekening in twee kolommen is je spec. Het soort werk met AI-agents dat wij doen, valt of staat daar vaak op.

Kern

Als de chat het interface is, is het trust-model van de agent ook het security-model. Het model in de loop beschermt je niet; de regels eromheen wel.

FAQ

Boekt de agent ooit een slot zonder menselijke goedkeuring?

Nee. Elke boeking en wijziging vereist een OK-reply in de Loods Telegram-groep van een dispatcher op de allow-list. De agent stelt voor, een mens bevestigt, en daarna gaat de boeking pas door.

Welke carriers ondersteunt de agent op dit moment?

Maersk, MSC en CMA CGM. Hapag-Lloyd en ONE staan op de planning voor een tweede fase, zodra de eerste drie negentig opeenvolgende dagen schoon hebben gedraaid in productie.

Wat gebeurt er als een carrier-API uitvalt?

De agent valt terug op de laatst gecachte ETA, plaatst een duidelijk gelabelde waarschuwing over de verouderde feed in de groep, en de dispatcher beslist of hij de carrier rechtstreeks belt. Geen stille fouten.

Hoe lang duurde de bouw van begin tot eind?

Negen weken van kickoff tot eerste productiedraai, waarvan twee weken om API-toegang te regelen met één carrier en één week om de normaliser te herschrijven nadat een leverancier midden in de bouw z'n datumformaat veranderde.

chat agentsai agentsautomationintegrationscase studyworkflow

Iets bouwen?

Start een project