AI agents
Burr vs LangGraph vs XState: de kern van je claims-agent
Het is 21:13 op een donderdag in Mechelen. De claims-agent vraagt om een polisnummer dat drie beurten geleden al binnenkwam. Welk framework je zes maanden terug koos, bepaalt wat je nu fixt.

Het is 21:13 op een donderdag in Mechelen. De dienst-telefoon van een verzekeringsmakelaar van 17 mensen trilt één keer. Hun claims-intake-agent heeft net een polishouder, beleefd en in het Nederlands, om een polisnummer gevraagd dat drie beurten eerder al in de oorspronkelijke e-mail van die klant stond. De junior met dienst heeft een laptop, een koffie en ongeveer vijfenveertig minuten voordat de volgende batch nachtelijke mail binnenkomt en het sneeuwbaleffect begint.
Wat die junior in die vijfenveertig minuten mag doen, hangt bijna volledig af van welk orchestratie-framework iemand zes maanden geleden heeft gekozen. Toen de makelaar ons vroeg hun intake-pipeline opnieuw te bouwen, deden we dus een serieuze bake-off met drie kandidaten: Burr, LangGraph en een zelfgebouwde XState v5-machine op Bun. Dezelfde agent. Dezelfde prompts. Hetzelfde model. Drie orchestrators.
De vraag was niet "welk framework is het beste". Die vraag is onbeantwoordbaar. De vraag was: welke overleeft een donderdag om 21:13 met een junior achter het toetsenbord.
Wat claims-intake daadwerkelijk moet doen
De agent is niet exotisch. Hij leest een inkomende e-mail of een webformulier, identificeert de polis, vraagt om eventueel ontbrekende informatie (foto's van de schade, gegevens van een tegenpartij, een schets van het ongeval bij een autoclaim), stelt een gestructureerd dossier op en parkeert dat in de wachtrij van de schadebehandelaar. Ongeveer twaalf stappen, vier tools (polis-opzoeken, OCR voor het schadeformulier, een IBAN-validator op zijn Belfius', en de dossier-writer) en drie vertakkingspunten. Een claim duurt op de klok tussen twee minuten en drie dagen, afhankelijk van hoe snel de klant antwoordt.
De agent is dus langlopend, stateful, conversationeel en geaudit. FSMA en AVG kijken mee. Verliezen we state, dan zeuren we óf opnieuw bij een klant die ons de gegevens al heeft gegeven, óf, erger nog, we laten stilletjes een stuk bewijs vallen. Beide eindigen in een klachtendossier.
Dat is de echte klus. Nu de drie kandidaten.
Burr: replay als eerste-klas burger
Burr is al ruim een jaar een rustig volwassen project van DAGWorks. Het modelleert de agent als een state machine waarin elke actie een Python-functie is die van een getypeerd state-object leest en ernaar schrijft. De Burr Tracker, een kleine lokale web-app, toont elke stap, elk state-delta, elke tool call, en laat je een run forken vanaf elke eerdere stap.
Forken vanaf stap drie om een fix te testen ziet er zo uit:
from burr.core import ApplicationBuilder, expr, default
from burr.tracking import LocalTrackingClient
app = (
ApplicationBuilder()
.with_state(claim_id="CL-8821", policy_number=None, photos=[])
.with_actions(
extract_policy_number,
fetch_policy,
request_missing_info,
compile_dossier,
)
.with_transitions(
("extract_policy_number", "fetch_policy", expr("policy_number is not None")),
("extract_policy_number", "request_missing_info", default),
)
.with_tracker(project="claims-intake")
.with_identifiers(partition_key="CL-8821")
.build()
)
# Fork from step 3 of yesterday's run
forked = app.fork(parent_partition_key="CL-8821", sequence_id=3)
Het goede: replay is hier het product, geen feature die er bovenop is geplakt. De Tracker-UI is de beste agent-debug-interface die we hebben getest. Het schema is impliciet maar leesbaar, omdat het state-object een getypeerde dict is en je de vorm afleest uit de signatures van de acties.
Het minder goede: het is Python. Voor een Belgische makelaar wiens bestaande stack TypeScript draait op de front, op de back en in de Outlook-add-in, betekent een Python-service erbij een tweede deployable, een tweede dependency manager (in ons geval uv) en een tweede on-call-rotatie. Niets daarvan is fataal. Het kost een junior wel telkens een extra uur context-switch zodra ze het aanraken.
LangGraph: checkpoints, threads en een betaalde observability-laag
LangGraph modelleert de agent als een gerichte graaf. Nodes zijn functies die state krijgen en een partiële update teruggeven. Edges, voorwaardelijk of vast, bepalen waar je heen gaat. State is een TypedDict met reducer-annotaties op velden die moeten mergen in plaats van overschrijven. Persistentie gaat via een checkpointer (SQLite, Postgres of custom), gekoppeld aan een thread_id.
from langgraph.checkpoint.sqlite import SqliteSaver
checkpointer = SqliteSaver.from_conn_string("checkpoints.db")
graph = builder.compile(checkpointer=checkpointer)
config = {"configurable": {"thread_id": "CL-8821"}}
# Walk every checkpoint in this thread
for cp in graph.get_state_history(config):
print(cp.config["configurable"]["checkpoint_id"], cp.values, cp.next)
# Resume from a specific checkpoint
graph.invoke(
None,
config={"configurable": {
"thread_id": "CL-8821",
"checkpoint_id": "1ef..."
}},
)
Het goede: thread-level checkpointing is betrouwbaar, goed gedocumenteerd en overleeft een process restart netjes. De TypeScript-port bestaat echt en wordt onderhouden. LangSmith, de betaalde observability-laag, is goed zodra je 'm hebt aangesloten.
Het minder goede: zodra de vorm van je state TypedDict verandert, worden oude checkpoints een valkuil. Er is geen first-class verhaal voor migraties. Je versioneert je state, of je versioneert je thread_ids, of je accepteert dat de claims die nu in vlucht zijn na de volgende deploy kunnen ontploffen.
Als je het type van een veld in je LangGraph-state aanpast of een key hernoemt, wordt elke bestaande lopende thread een kleine landmijn. Voeg vanaf dag één een schema_version-veld toe aan je state en laat je reducer daarop vertakken. Wij hebben dat op de moeizame manier geleerd.
En verder: om 21:13 een junior die in LangSmith zit te zoeken naar welke node policy_number weer op None heeft gezet, doet forensisch werk in een aparte web-app. De Tracker van Burr houdt je dichter bij de code.
XState op Bun: types van boven tot onder
De derde optie was de agent-frameworks helemaal overslaan en orchestratie behandelen als wat het werkelijk is: een statechart. XState v5 is een volwassen TypeScript-bibliotheek voor statecharts. Bun geeft ons een snelle TS-runtime met bun:sqlite ingebouwd. Persistentie is een append-only event log; replay is simpelweg de events teruggeven aan de actor.
import { createActor } from 'xstate';
import { Database } from 'bun:sqlite';
import { claimsIntakeMachine } from './machine';
const db = new Database('events.db');
function rehydrate(claimId: string, upToSeq?: number) {
const rows = db.query(
`SELECT event FROM intake_events
WHERE claim_id = ?1 ${upToSeq ? 'AND seq <= ?2' : ''}
ORDER BY seq`
).all(claimId, upToSeq) as { event: string }[];
const actor = createActor(claimsIntakeMachine);
actor.start();
for (const row of rows) actor.send(JSON.parse(row.event));
return actor;
}
// Inspect state at step 3, then run forward
const actor = rehydrate('CL-8821', 3);
console.log(actor.getSnapshot().value, actor.getSnapshot().context);
Het goede: één taal door de hele stack. De machine-definitie is het schema. Stately Studio, gratis, tekent de machine als een diagram dat een niet-ingenieur kan lezen en goedkeuren. Dat bleek belangrijker dan verwacht toen de compliance officer van de makelaar de intake-flow wilde aftekenen. Een junior met dienst past één TypeScript-bestand aan, draait bun test en deployed.
De prijs: je schrijft de persistentielaag, de replay-UI, de checkpoint-en-resume-semantiek en de cancellation-logica zelf. Voor een state machine van twaalf stappen met vier tools is dat ongeveer driehonderd regels, maar het zijn driehonderd regels die jij bezit en correct moet houden. Burr geeft je dat gratis.
Scoren tegen donderdagavond
Met alle drie de versies die dezelfde agent draaiden op een fixture van honderd herspeelde claims, viel de score zo uit.
Replay-vanaf-stap debuggen
Burr wint glansrijk. De Tracker toont je de run als een lijst stappen, elke klik onthult het state-delta en de tool-I/O, en je forkt vanaf elke stap met één knop. LangGraph zit er vlak achter als je voor LangSmith hebt betaald, en pijnlijk daarachter als dat niet zo is. XState is wat jij ervan bouwt. Onze zelfgebouwde replay had een kleine terminal-viewer nodig voor 'ie even bruikbaar was als de Tracker van Burr out of the box, en we hebben nog steeds geen manier om state tussen runs te diffen zoals Burr dat doet.
Wie eigenaar is van de schema-migratie
XState wint, omdat het schema de machine is en de events onveranderlijke feiten zijn. Verandert het schema, dan deploy je óf een nieuwe versie van de machine en route je nieuwe claims daarnaartoe, óf je schrijft een eenmalige event-rewriting migratie. Beide zijn saai en voor de hand liggend. Burr is er eerlijk over: state is een getypeerd Python-object, migraties zijn jouw code, persistentie is jouw keuze. LangGraph hoopt stilletjes dat je de vorm nooit verandert. Dat ga je wel doen.
Wat een junior om 21:00 kan hot-fixen
Dit criterium besliste het project. We zetten de meest junior dev van de makelaar neer, gaven 'm een ge-seede versie van de bug van 21:13, en klokten drie runs.
- Burr: 34 minuten. Tracker lezen, de actie gevonden die de verkeerde state teruggaf,
extract_policy_numberaangepast, de Python-service opnieuw uitgerold, de getroffen partitie opnieuw afgespeeld. - LangGraph: 58 minuten. De bug was een reducer die
policy_numberoverschreef terwijl 'ie de bestaande waarde had moeten behouden. In LangSmith vinden ging prima. Zichzelf overtuigen dat de fix de bestaande checkpoints niet zou vergiftigen, kostte de rest van het uur. - XState op Bun: 22 minuten. De bug was een guard die de verkeerde tak teruggaf. Eén bestand aanpassen,
bun testliet twee falende transitions zien, fixen, deployen.
Het voordeel van XState was geen framework-magie. Het was juist de afwezigheid van een framework. Een junior die kijkt naar een state machine in hun eigen taal, met hun eigen persistentiemodel, hoeft niemand excuses aan te bieden als ze iets aanpassen. Ze passen het gewoon aan.
Wat we hebben uitgerold
Voor deze klant rolden we XState v5 op Bun uit, met een append-only event log in Postgres (niet SQLite; de makelaar draait al Postgres en wilde geen tweede store), een kleine replay-endpoint, en Stately Studio-diagrammen ingecheckt naast het machine-bestand. We hebben ruim vier dagen werk gestopt in de persistentie- en replay-UI die we anders gratis bij Burr hadden gekregen. Die tijd verdienden we de eerste maand al terug in hot-fix-uren, en we kregen er iets bij dat Burr niet biedt: een diagram dat de compliance officer zelf kan lezen.
Had dezelfde klant een Python-first stack gehad, dan hadden we zonder aarzelen Burr gekozen. Replay-als-product is écht een feature die een categorie definieert, en de Tracker-UI is geld waard. We hadden LangGraph niet gekozen voor een agent die schema-wijzigingen mid-flight moet overleven. Wel voor een chat-assistent die aan het einde van elke sessie reset.
Kies geen agent-framework op benchmarks. Kies op wat een junior dev om 21:00 op een donderdag veilig kan aanpassen. Dat is de dag dat de agent in productie staat.
De hot-fix uit de opening van deze post duurde 22 minuten in de gesimuleerde run. De fix die er écht toe deed, was de architecturale beslissing van zes maanden eerder: intake modelleren als statechart, events persisten en in één taal blijven. Toen we de claims-intake-AI-agent voor de Mechelse makelaar bouwden, liepen we vooral aan tegen dat het debug-verhaal van je orchestrator je on-call-leven bepaalt. We hebben het opgelost door de orchestrator iets te maken dat een junior vóór de lunch kan doorlezen.
Wil je weten of je huidige agent-stack de 21:13-test doorstaat? De goedkoopste audit die je vanavond kunt doen: pak een stap drie beurten ver in een echte run, en zeg hardop hoe je daarvandaan zou replayen, welke state verkeerd zou staan als je volgende week het schema aanpaste, en wie in je team een guard-expressie zonder hulp kan fixen. Doen twee van die drie antwoorden je gezicht vertrekken, dan heb je een donderdagavond-probleem dat staat te wachten.
Kern
Kies een agent-orchestrator op wat een junior dev om 21:00 op een donderdag veilig kan aanpassen. Dat is de dag dat de agent in productie staat.
FAQ
Waarom niet gewoon het nieuwste agent-framework van een grote vendor pakken?
Vendor-frameworks neigen naar chat-assistenten die aan het einde van een sessie resetten. Langlopende, geaudite agents zoals claims-intake straffen die aanname af zodra state een process restart, een schema-wijziging of een hot-fix om 21:00 moet overleven.
Maakt Bun uit, of voldoet Node ook?
Node voldoet. Bun gaf ons native TypeScript, ingebouwde SQLite en een snellere cold start voor de replay-endpoint. De architectuur werkt op elke moderne JS-runtime; we zouden een werkende Node-service niet herschrijven puur om Bun te gebruiken.
Hoe groot moet een team zijn voordat deze vergelijking er niet meer toe doet?
Rond de vijftig engineers verdienen frameworks zichzelf terug, omdat meer mensen profiteren van gedeelde infrastructuur. Onder de twintig is de prijs van één extra deployable en één extra taal de doorslaggevende factor, niet de feature-lijst.
Zou je LangGraph dan ergens voor kiezen?
Ja. Kortdurende chat-assistenten, RAG-copilots, interne tools waar state elke sessie reset. Het is het schema-migratieverhaal dat pijn doet op productieschaal voor langlopende agents die zich over dagen uitstrekken.