← Blog

Chat agents

Dispatcher chat agent: 2,640 route changes a week, one queue

A 23-person Groningen logistics vendor wired a dispatcher chat agent into a 13-year-old Transics stack. It now handles 2,640 weekly route changes without breaking ELD writes.

Jacob Molkenboer· Founder · A Brand New Company· 12 Sept 2025· 8 min
Brass mail-sorter with five pigeonholes of cream slips, one tied with green ribbon, bell and leather ledger on ivory desk.

A driver radios in at 14:23 on a Tuesday. The Volvo FH he is driving has been waiting twenty minutes behind a closed bridge near Bremen. He needs a re-route, and he needs it before his break window closes at 15:00. The dispatcher in Groningen has three other drivers in the queue, two phone lines blinking, and a planner asking about a class-3 fuel delivery that was supposed to be on the road an hour ago.

That is the situation our client walked into every weekday afternoon. The vendor sells dispatch software to mid-size Dutch and German hauliers, and they had been getting buried in the same operational pattern since 2013: when something changes on the road, the dispatcher becomes a switchboard, and the planner becomes a bottleneck.

This is the story of how we put a chat agent in front of the dispatcher inbox, what we had to do to make it talk to a stack that predates most of the people on the operations team, and which constraints we kept versus which ones we relaxed.

The stack we inherited

The client runs Transics for telematics. The original units in the cabs are from 2013 and 2014, with a few newer TX-SKY units sprinkled in after a 2019 refresh. Routing comes from PTV xServer, the on-premise version, running on a single Windows Server 2019 box in their Groningen data centre. Driver hours go through a tachograph integration. The whole thing talks to a custom .NET dispatch UI through what the team calls "the FMS-bus," which is essentially a RabbitMQ broker with thirteen years of schemas glued to it.

The ELD-write endpoint (the one that updates a driver's logged route in the tachograph record) is the most sensitive part. If you write a wrong route assignment to the ELD, you create a compliance problem that the client's insurance underwriter will read about at the next quarterly audit. Nobody on the team would let us touch that endpoint without a human approval layer in front of it.

What the chat agent actually does

The agent sits in front of the dispatcher inbox, not in front of the driver. Drivers still talk to dispatchers the way they always have: short messages over the Transics in-cab terminal, or voice over a separate radio channel that does not matter for this story.

When a driver sends a message like "brug Bremen dicht, kan ik via A28," the message lands in the dispatch UI and the chat agent reads it at the same time. The agent does three things:

  1. It classifies the message. Is this a route change request, a delay report, a load issue, a break query, or something it should kick back to a human?
  2. If it is a route change, it pulls the active rit from PTV xServer, runs the proposed detour, and prepares a write payload for both PTV (the new route geometry) and Transics (the new ETAs).
  3. It checks the load manifest for ADR cargo. If the rit carries anything in ADR class 3 (flammable liquids: most of what this client moves is fuel, solvents, and adhesives), the agent does not write. It posts the proposal to a planner-approval queue and tells the driver "wacht op planner."

For non-ADR cargo, the agent writes directly to the FMS-bus and confirms to the driver. For ADR cargo, the planner clicks one button and the same write happens, with the planner's user ID stamped on the audit row.

The volume the agent handles right now is 2,640 route-change requests per week, measured over the four weeks before this post. Before the agent, dispatchers were handling roughly the same volume with three full-time dispatchers and an evening on-call rotation. The agent does not replace the dispatchers; it handles the routine cases and lets them focus on the messy ones (multi-stop reroutes, customer call-backs, anything involving a border crossing or an exceptional load permit).

The 800 ms latency budget

The FMS-bus has a hard latency budget. If a write to the bus takes longer than one second, the in-cab terminal shows the driver a "vertraagd" warning, which makes them call the dispatcher to confirm. That defeats the whole point of the agent.

We measured the existing dispatcher round-trip at a median of 320 ms (dispatcher reads message, types reply, presses send, write hits the bus). The agent had to fit inside the same envelope. We set our budget at 800 ms end-to-end, from message arrival to bus write confirmation.

The two big costs were the LLM call and the PTV route computation. The route computation we could not compress: PTV xServer takes 180 to 240 ms for a typical detour in Northern Germany, and we were not going to swap a 13-year-old routing engine for this project. So we cut the LLM call as hard as we could:

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

That left us with roughly 450 ms of headroom on a clean run, which we burned through quickly the first time the FMS-bus queued up behind a Transics firmware push and our writes started timing out. We added a circuit breaker that drops the agent into "ask the dispatcher" mode if bus latency exceeds 600 ms for more than ten seconds. The dispatcher gets a banner that says "agent paused, FMS-bus slow." We have not had to use the breaker since week three.

What we did not let the agent do

The list of things the agent is explicitly forbidden from doing is longer than the list of things it does. This is the part of every chat-agent project that gets cut from the demo and then becomes the thing that saves you in production.

Warning

If a chat agent has write access to a regulated system (tachograph records, financial ledgers, customer PII), the approval queue is not optional. It is the load-bearing wall of the whole design.

The agent cannot:

  • Write to the ELD endpoint for any rit carrying ADR class 3 cargo without planner approval.
  • Confirm a route that crosses a border the driver does not have the cabotage permit for. (We cache permit data nightly; the agent reads from the cache.)
  • Override a customer-imposed time window without raising a flag in the dispatcher queue.
  • Reply to a driver in a language other than the one set on their cab terminal. The agent respects the driver's locale even when the planner is typing in Dutch.

The first three rules came from the client. The fourth came from a Polish driver in week two who got a Dutch reply, called his dispatcher in confusion, and undid the whole "we just saved you a phone call" pitch in one minute.

The message log problem

Every message the agent reads, classifies, and acts on gets logged. Driver message, agent classification, route proposal, write payload, bus response, planner action (if any). The log is the audit trail for the ELD writes and the planner approvals, and the client's insurance underwriter wants to see it.

The log grows by about 95,000 rows a week across drivers, agent steps, and bus events. After six months that is a table you cannot maintain. We hit the problem the Hacker News crowd has been writing about for a decade: the only scalable delete in Postgres is DROP TABLE. Running a nightly DELETE WHERE created_at < now() - interval '90 days' would tank query latency for hours and bloat the table indefinitely.

We partitioned the log by ISO week. Old partitions get detached and either archived to cold storage or dropped, depending on the retention policy attached to the row class. ELD-write audit rows live for seven years (compliance requirement under EU tachograph regulation 165/2014). Driver chitchat lives for thirty days. Two different partition trees, one log table per tree, weekly cron job that detaches and either archives or drops.

This is dull infrastructure work and it is the difference between a chat agent you can run for a year and one you have to rebuild in February.

What broke in week one

Three things broke. They are worth naming because they are the things that will break in every comparable project.

The first was timezone drift. The PTV xServer is configured to Europe/Amsterdam. The Transics units report in UTC. The .NET dispatch UI does its own conversion. Our agent picked the wrong source of truth on the first day and confirmed a 14:00 ETA to a driver whose cab was reading 12:00. The driver thought he had two extra hours; the customer had a missed slot.

The second was the difference between a route that exists and a route a truck can drive. PTV will happily compute a "shortest path" through a village street that the truck physically cannot turn into. The default xServer profile has truck dimensions in it, but the agent was passing the wrong profile ID on detours. We caught it on the second day after a driver politely radioed in "deze kan ik niet."

The third was a classifier edge case. The agent was treating "moet ik tanken?" (should I refuel?) as a route change request, because the model latched onto the implied detour. We added a small set of canonical examples to the classifier and the false positive rate dropped from 4% to under 0.3%.

The pattern under all three is the same: the hard part of putting a chat agent in front of a legacy stack is not the LLM. It is the seven small assumptions baked into the old system that the agent has to learn to respect.

Where the numbers landed

The dispatcher team has not shrunk. The same three dispatchers and the same evening on-call rotation are still in place. What changed is what they do during the day. The afternoon spike (between 14:00 and 17:00, when drivers hit traffic and try to renegotiate their evening) used to require all three dispatchers on chat and a planner pulled in to help. Now one dispatcher covers the floor, the agent handles the routine reroutes, and the planner only sees ADR rides.

The 2,640 weekly route changes break down to roughly 78% handled fully by the agent, 15% routed to a planner for ADR approval (then written), and 7% kicked to a human dispatcher because the agent was not confident. That last bucket is the one we watch most closely; if it drifts up, the agent is degrading and we need to look at the classifier.

The smallest useful thing you can do this week

If you run a dispatch operation on a stack older than your newest hire, measure the read-to-write latency budget for your most sensitive endpoint. Not the average. The 99th percentile, on the worst hour of the worst day. Whatever number you get back is the budget any future automation has to fit inside, and it is almost always smaller than people think.

When we built this dispatcher agent for the Groningen vendor, the thing we kept coming back to was the planner-approval queue: the agent only earned the right to write to ELD because we wired in a human gate for the class of cargo that matters. We use that pattern in most of our AI agent projects, because it is the only way the agent gets to keep its write access after the first audit.

Key takeaway

The hard part of putting a chat agent in front of a legacy stack is not the LLM. It is the seven small assumptions baked into the old system.

FAQ

Does the chat agent replace the dispatchers?

No. The team size did not change. The agent handles routine route changes so dispatchers can focus on multi-stop reroutes, border crossings, customer call-backs, and other messy cases.

Why route ADR cargo through a planner instead of letting the agent write?

ADR class 3 is flammable cargo. A wrong ELD entry becomes a compliance and insurance problem at audit. The planner approval is the gate that keeps the agent's write access defensible.

Where does the 800 ms latency budget come from?

The Transics in-cab terminal shows a 'vertraagd' warning if a bus write takes over a second, which prompts the driver to call. We set 800 ms internally so we never trip that warning.

Why partition the message log instead of deleting old rows?

A nightly DELETE on a fast-growing Postgres table bloats indexes and locks rows. Partitioning by week lets us drop old partitions instantly and still meet the seven-year tachograph retention rule.

Could this approach work without PTV xServer?

Yes. The pattern is stack-agnostic: classify the driver message, prepare a write, gate sensitive cargo through a human queue, write to whatever routing engine and telematics system you already run.

ai agentschat agentscase studyintegrationsoperationsarchitecture

Building something?

Start a project