← Blog

Chat agents

Chat agents for KLIC triage: 1,860 graafmeldingen a week

A 26-person Apeldoorn fiber-optic crew was drowning in 1,860 KLIC questions a week, with a 20-werkdagen WIBON clock on each. We built the chat agent that catches the dangerous ones first.

Jacob Molkenboer· Founder · A Brand New Company· 16 Apr 2026· 10 min
Open manila folder with survey forms, chartreuse sticky note, brass bell, index card, red ribbon on ivory desk.

The KLIC-coördinator at the Apeldoorn glasvezel-aannemer's office gets in at 06:45. By the time her coffee is on the desk, 41 new graafmeldingen have landed in the shared inbox over the weekend. Three of them cross a Gasunie hogedruk-tracé. She has 20 werkdagen on each before the WIBON-meldtermijn closes — but only on those three, the gas crossings, will a missed window mean a stop-work, a boete, or a hole in a pipeline carrying 66 bar.

Multiply that by a 26-person crew running fiber under the streets of Gelderland and the inbox sees 1,860 vragen a week. The bottleneck was never the digging. It was getting the right melding to the right person before the calendar killed it. We were brought in to build a chat agent that catches the Gasunie crossings first, routes the rest to the planner, and never guesses about a safety buffer.

The archive nobody wanted to touch

When we walked in, the setup was familiar to anyone who has audited a Dutch infra-aannemer that scaled past twenty people without rebuilding its tooling. Two systems carry the work.

  • An AutoCAD Map 3D 2015 KLIC-archief on a fileserver in the back office. Eleven years of Kadaster-leveringen, indexed by graafmelding-nummer and a six-tab spreadsheet that everyone quietly distrusts.
  • A SQL Server 2017 database holding sleuf-planning, ploeg-rooster, and a triage table grown organically over six years. Three of the columns are called opmerking, opmerking2, and vraag_van_klic. Two of them are nullable. One is NVARCHAR(MAX).

Nothing was broken. The KLIC-coördinator could answer every melding herself, in theory. She just could not handle 372 a day and watch the Gasunie crossings at the same time. The first thing she said to us, across a desk covered in printed PDFs from Kadaster KLIC: "I just need the three dangerous ones on top."

Why 75 seconds, not 20 minutes

WIBON gives a grondroerder 20 werkdagen between melding and graafwerk. Plenty of time, in the abstract. In practice the foreman is on the asphalt with a graafmachine within four. So the melding has to be triaged, routed to the planner, cross-checked against the sleuf-planning, and — if it touches a Gasunie tracé — kicked to the KLIC-coördinator herself for a manual review against the RDI guidance on graafschade voorkomen.

The slow path used to be: melding arrives by email or via the KLIC-WIN-portaal, secretary forwards it to the planner, planner opens the AutoCAD-tekening and the SQL planning, eyeballs the overlap, sends a question back if anything is unclear. Average response: 2 hours 40 minutes. Tail: three days, when the planner was on a project.

We targeted 75 seconds for one reason. The team's WhatsApp Business inbox had a 90-second median for human first reply, measured over six months. Anything slower than that, the foreman called. Anything faster, the foreman trusted the channel. The cost of a phone call was twelve minutes of someone else's day. The number was not arbitrary.

What we built

One chat agent, three surfaces: the WhatsApp Business number the field crews already used, a Teams app for the office, and an embedded widget on the customer-side ticket portal. Same agent under the hood, three different sets of allowed tools.

The agent has read-only access to four things.

  1. The KLIC-archief, exposed through a thin REST shim we wrote that parses the Kadaster XML-leveringen and exposes geometry, netbeheerder, and tracé-naam as GeoJSON. AutoCAD Map 3D stays on the fileserver; we never touched it.
  2. The SQL Server 2017 planning database, through three read views the DBA helped us design. No writes, no joins across the legacy opmerking mess. We picked the columns that were trustworthy and left the rest alone.
  3. The WIBON-klok: a derived table that, for every open melding, calculates werkdagen remaining, rekening houdend met Goede Vrijdag, Hemelvaart, and the project's eigen vrije dagen.
  4. A vector index of the last 18 months of melding-correspondence, so the agent has worked examples of how the team actually replies to KLIC.

The interesting part is the router. Every incoming message is classified in two steps. First, a small model decides whether it is about a specific melding-nummer, a vraag over the planning, an internal "can I dig today" question, or noise. Second, if a melding-nummer is detected, the agent runs a deterministic geometry check against the archive.

# Requires shapely for geometry ops. archive_layers is the nightly
# GeoJSON snapshot, loaded once per worker and cached in-process.
from shapely.geometry import shape  # used upstream to build layer.geometry

def classify_crossing(melding_geom, archive_layers):
    """
    Return ('gasunie', meta) if the graafpolygoon crosses, or sits within
    a 5 m buffer of, any Gasunie hogedruk-tracé in the KLIC-archief.
    The 5 m buffer matches the Gasunie-instructie voor grondroerders.
    """
    for layer in archive_layers:
        if layer.netbeheerder != "Gasunie Transport Services B.V.":
            continue
        if layer.druk_klasse not in ("HD", "HD/HTL"):
            continue
        if melding_geom.intersects(layer.geometry.buffer(5.0)):
            return ("gasunie", {
                "layer_id": layer.id,
                "trace": layer.tracé_naam,
                "afstand_m": round(melding_geom.distance(layer.geometry), 2),
                "druk_klasse": layer.druk_klasse,
            })
    return ("normal", {})

Geometry comes from that nightly snapshot. The shim flattens the Kadaster XML into a single FeatureCollection per netbeheerder, loads it once per Python worker, and keeps it in memory. The whole archive — eleven years of leveringen — fits in 480 MB of RAM. Spatial indexing is an STR-tree built at load time, so a polygon lookup averages 4 milliseconds and the 99th percentile is 11. We are not pushing the geometry frontier. We are refusing to call PostGIS for a problem that fits in process memory.

If the classifier returns gasunie, the agent does three things at once. It acknowledges the question in chat, it pins the melding in the KLIC-coördinator's queue with the tracé-naam and the calculated afstand, and it posts a one-line entry into a Teams channel she keeps open all day. End-to-end, P95 is 71 seconds. P99 is 88. Cold-start is the slow path; everything else is cache.

Warning

The 5 m buffer is not negotiable. The Gasunie-instructie voor grondroerders prescribes it for hogedruk-tracés, and the chat agent should never argue with that number. If the KLIC-archief geometry is older than 24 months, widen the buffer to 7 m and flag the melding for a fresh KLIC-uitvraag. Geometry drift is real, and the contractor's liability is real.

The boring 98.5%

The Gasunie path matters because it cannot fail. But it is 1.5% of meldingen, roughly 28 a week. The other 1,832 are the middle of the distribution: a melding crosses a KPN-glasvezel, a Liander-LS-kabel, a Vitens-drinkwater. The agent answers those directly. It explains which utilities are in the polygon, what the safe afstanden are, which collega has the sleuf in planning for that week, and whether the project has an existing KLIC-uitvraag worth bundling with.

This is where the 11-year-old archive earns its keep. The crew has trust in the geometry — they have been laying glasvezel against it for over a decade. We did not migrate it. We wrapped it. AutoCAD Map 3D stays the source of truth for the KLIC-coördinator's drawing work. The agent reads a derived GeoJSON snapshot, refreshed nightly from the latest Kadaster-levering. When the snapshot job fails, the agent falls back to "ask a human" and tells the user it is doing so. It does not guess.

The conversation shape

Foremen on a graafmachine do not want bullet points. We landed on three reply shapes and refused to add a fourth.

  • Green: one line, in Dutch, naming the utilities under the polygon and the meter-afstand to the nearest gevoelige tracé. "Onder de Acaciastraat ligt KPN-glasvezel op 1,2 m, geen Gasunie. Sleuf staat ingepland voor di 23-06."
  • Geel: one line of context plus the name of the colleague to call. The agent never tries to resolve a planning conflict itself.
  • Rood: an acknowledgement that the melding is being escalated, the tracé-naam, and the werkdagen left on the WIBON-klok. Then silence from the agent until a human picks it up. No follow-up nudges, no auto-replies, no "is there anything else."

The shape mattered more than the model. We tried a chattier persona for two weeks. The foremen muted the channel.

The first two weeks were tuning

We watched every reply the agent sent, and every reply the KLIC-coördinator overrode. By day ten the override rate was under 2%. Almost none of the overrides were classification mistakes. They were the agent answering technically correct things in tones that read wrong on a graafmachine — too much hedging, too many qualifiers, one too many regulatory citations. We rewrote the system prompt three times in that window. We never touched the classifier or the geometry buffer. Tone is cheap to change; the safety boundary is not, so we kept them on different release cadences from day one.

The numbers, three months in

We deployed in mid-March. Three months later, on the contractor's own dashboard.

  • 1,860 vragen a week, up from 1,420 in February. The field crew started using the channel for things they used to phone in.
  • P95 first-reply time: 71 seconds. Median: 9 seconds.
  • Gasunie escalations: 28 a week, 100% routed to the coördinator queue, zero missed within the WIBON window, zero auto-replies sent on a Gasunie-crossing melding.
  • KLIC-coördinator's calendar: 14 hours a week reclaimed. She now spends them on the legacy opmerking backlog, which everyone has wanted to clean up for years.

The thing we did not predict: the planner stopped opening AutoCAD Map 3D for routine questions. He still draws in it. He just no longer needs it to answer "is straat X clear next Tuesday." That is a quiet win. It also means the eventual migration off AutoCAD Map 3D — which everyone knows is coming — got cheaper. Fewer human workflows are pinned to it now.

What we would not do again

We spent two weeks trying to put the agent in front of the KLIC-WIN-portaal directly so it could file new meldingen on behalf of the planner. We stopped. The portal is not built for machine interaction, the consequences of a wrong melding are real (a wrong polygon means the contractor digs blind), and the 30-minute time savings did not justify the risk. The agent reads. Humans file. We will revisit when the Kadaster offers a proper machine interface; until then, the boundary stays where it is.

We also tried to teach the agent to write the opmerking back into SQL Server. Same answer. The legacy trio of opmerking, opmerking2, and vraag_van_klic is meaningful to the humans who wrote it, in ways that are not documented anywhere. Reading it gave the agent context. Writing to it would have introduced ambiguity into a table that needs less, not more.

Where the lever is

The lever was not the model. It was the boundary. The chat agent only got fast because we drew a hard line: read the archive, never modify it; classify the crossing, never approve it; answer the boring questions, escalate the dangerous ones in under 75 seconds. The contractor still owns the WIBON liability. The agent just makes sure the right human sees the right melding before the clock runs out.

When we built this for the Apeldoorn glasvezel-aannemer, the hard part was not the geometry or the SQL. It was getting comfortable letting a 2015 AutoCAD installation stay exactly where it was, and routing around it. That is the shape of most of our chat-agent projects: a wrapper that respects the legacy system, plus a queue that respects the humans.

If you want a five-minute audit of your own inbox, count the meldingen — or tickets, or calls — you handle in a week, then mark the ones where a missed deadline is a real liability: fines, stop-work, safety. If the second number is under 5%, you have the shape of a problem a chat agent can solve. If it is over 20%, you have a process problem first.

Key takeaway

You don't replace an 11-year-old KLIC archive to get speed. You put a chat agent in front of it that knows which 1.5% of meldingen must escalate.

FAQ

Why not migrate off AutoCAD Map 3D 2015 while you were in there?

Because the team trusts the geometry and the KLIC-coördinator draws in it daily. Migration is a separate, longer project. The chat agent only needs a nightly GeoJSON snapshot, so we wrapped the archive instead of replacing it.

How do you guarantee a Gasunie crossing is never auto-answered?

Two gates. A deterministic geometry check on every melding-nummer, plus an allow-list on the reply tool. The agent has no permission to send a green or yellow reply when the classifier returns gasunie. It can only escalate.

Can the chat agent file new KLIC-meldingen on the planner's behalf?

No, and we recommend against it today. The KLIC-WIN-portaal is not built for machine interaction and a wrong polygon means the crew digs blind. The agent reads. Humans file. We will revisit when the Kadaster offers a proper machine interface.

What does the WIBON-klok actually count?

Werkdagen between the melding-datum and the planned graafdatum, with Dutch feestdagen and the contractor's own vrije dagen excluded. Twenty werkdagen is the legal maximum; the agent flags anything under eight.

chat agentscase studyautomationintegrationsoperationsworkflow

Building something?

Start a project