Er is een misvatting die bijna elk AI-agent project trager maakt dan nodig: de aanname dat het kiezen van het juiste model het grootste deel van het werk is. In werkelijkheid is het model misschien 25% van het totale probleem. De overige 75% bestaat uit infrastructuur, routing, beveiliging, observability en deployment. Dat deel is minder zichtbaar, maar het is ook het deel dat bepaalt of een agent in productie levensvatbaar is.
Na tientallen AI-agent projecten zien we steeds dezelfde acht problemen terugkomen. Niet als variaties op een thema, maar letterlijk dezelfde uitdagingen, dezelfde edge cases, dezelfde workarounds. Op een gegeven moment is het slimmer om ze één keer goed op te lossen in een gedeelde laag, dan om ze per project opnieuw te bouwen. Dat is de gedachte achter de Laava SDK: een platform-layer die de infrastructurele problemen afhandelt, zodat de agent-logica zelf zo eenvoudig mogelijk blijft.
Dit artikel loopt door die acht problemen heen, met concrete uitleg over wat er telkens misgaat en hoe de SDK het aanpakt.
1. Channel routing
Een agent die alleen via één kanaal bereikbaar is, is eigenlijk geen agent maar een chatbot met een smal toepassingsgebied. In de praktijk willen klanten dezelfde agent bereiken via Slack, via Microsoft Teams, via e-mail en soms via een directe API-integratie. De vraag is hoe je dat organiseert zonder dat de agent-logica verweven raakt met kanaalspecifieke code.
Het klassieke anti-patroon is de grote if-else boom: als het een Slack-bericht is, doe X, als het een e-mail is, doe Y. Dat werkt tot het moment dat je een derde kanaal toevoegt, en dan nog een vierde. De agent-logica zit dan vol met routing-code die er eigenlijk niet thuishoort.
De SDK lost dit op met een uniforme IncomingMessage interface. Elk kanaal, of het nu Slack, Teams, e-mail of server-sent events is, produceert hetzelfde object. De agent-logica roept agentGraph.invoke() aan op dat object en ziet nooit het specifieke kanaal. Het kanaal is irrelevant voor de logica.
import { IncomingMessage, SlackMessageContext } from '@laava-ai/sdk/channels'
import { TeamsMessageContext } from '@laava-ai/sdk/channels'
import { parseEmailBody, formatEmail } from '@laava-ai/sdk/channels/email'Wat er achter die interface zit, verschilt per kanaal. Slack heeft zijn eigen event-structuur, Teams werkt via Activity-objecten, e-mail vereist parsing van MIME-content. De SDK handelt die vertaling af in de channel-adapters. Het resultaat is dat je een agent kunt bouwen die volledig kanaalagnostisch is. Wil je er later een kanaal bij? Je voegt een adapter toe, niet een nieuwe vertakking in je agent-logica.
2. Authenticatie en autorisatie
Authenticatie is één van die problemen waarbij het gemakkelijk is om een eerste versie te bouwen die lijkt te werken, maar die bij nader inzien vol gaten zit. Webhook-endpoints zonder signature validation, JWT's zonder expiry-checks, reviewer identities die uit de request body komen in plaats van uit een vertrouwde bron. Elk van deze fouten is al voldoende om een aanvaller grip te geven op je systeem.
Het eerste probleem is de webhook signature. Slack en Microsoft 365 ondertekenen hun webhooks met een gedeeld geheim. Als je die handtekening niet valideert, vertrouw je feitelijk elk verzoek dat binnenkomt op dat endpoint. De SDK valideert standaard de signature voor zowel Slack als M365.
Het tweede probleem is replay protection. Zelfs als je de signature valideert, kan een aanvaller een ondertekend verzoek onderscheppen en opnieuw versturen. De SDK biedt twee implementaties van replay protection: een in-memory versie voor enkelvoudige instanties en een Redis-backed versie voor gedistribueerde deployments.
import { createReplayProtector, createRedisReplayProtector } from '@laava-ai/sdk/cache'Het derde probleem is timing attacks. De M365 clientState-validatie gebruikt timingSafeEqual() in plaats van een gewone string-vergelijking. Dat klinkt als een detail, maar een timing-gevoelige vergelijking lekt informatie over hoe ver de strings overeenkomen. Bij een geheim dat je wilt beschermen, is dat niet acceptabel.
Het vierde probleem zit in de health endpoints. Veel projecten maken /health en /ready equivalent, maar dat zijn twee verschillende dingen. /health is een liveness check: draait het proces nog? /ready is een dependency readiness check: zijn PostgreSQL en Redis beschikbaar? Kubernetes en andere orchestrators maken dit onderscheid actief. De SDK implementeert beide endpoints met de juiste semantiek.
JWT-middleware voor de /chat en review routes zorgt er verder voor dat reviewer identity nooit uit de client-request komt. Die identity wordt altijd afgeleid uit het JWT-token, niet uit wat de client beweert te zijn.
3. Rate limiting en cost control
Een AI-agent zonder rate limiting is een open rekening. Elke request kost tokens, elke token kost geld, en als er geen rem op zit, kan een enkele misbehaving client of een kleine bug in je frontend je maandrekening naar een onacceptabel niveau brengen. Dit is geen theoretisch risico; het is iets dat in de praktijk geregeld misgaat.
Het eerste niveau van rate limiting is per route configureerbaar via environment variables.
CHAT_RATE_LIMIT_MAX=50
CHAT_RATE_LIMIT_WINDOW_MS=60000
MAX_REQUEST_BODY_BYTES=65536MAX_REQUEST_BODY_BYTES zit op alle mutable ingress routes. Een grote request body kan al vóór de model-aanroep schade aanrichten, bijvoorbeeld door het parsen te vertragen of geheugen te consumeren.
Het tweede niveau is cross-instance rate limiting. Als je één instantie draait, werkt in-memory rate limiting prima. Zodra je horizontaal schaalt, werkt het niet meer: elke instantie heeft zijn eigen teller en de limiet wordt effectief vermenigvuldigd met het aantal instanties. Door REDIS_URL te configureren, schakelt de SDK automatisch over naar gedeelde rate limiting via Redis. Hetzelfde geldt voor replay protection. Dezelfde configuratieverandering, hetzelfde resultaat.
Dit is een voorbeeld van het soort infrastructurele beslissing die je eigenlijk maar één keer hoeft te nemen, maar die in de praktijk per project opnieuw wordt genomen, soms goed, soms niet.
4. Gestructureerd loggen en observability
Loggen is het meest onderschatte onderdeel van een productie-agent. In development voldoet console.log(). In productie, met meerdere gebruikers, meerdere sessies en meerdere instanties, heb je gestructureerde logs met consistent trace-IDs nodig, anders is debuggen een kwestie van geluk.
De SDK biedt een logger via @laava-ai/sdk/observability die standaard structured JSON schrijft. Elke log entry bevat conversationId, userId en traceId, zodat je een volledige sessie kunt reconstrueren vanuit je logging-infrastructuur.
import { createLogger } from '@laava-ai/sdk/observability'
import { getLangfuseClient } from '@laava-ai/sdk/observability'Langfuse-integratie is ingebouwd. Langfuse is een open-source observability platform voor LLM-applicaties dat traces, spans en scores bijhoudt per aanroep. In een agent die meerdere LLM-aanroepen doet per user-request, wil je precies weten welke aanroep hoeveel tijd kostte, welke prompt werd gebruikt en wat de output was. Langfuse maakt dat inzichtelijk.
Sentry hooks zijn aanwezig voor error tracking. Prometheus-style metrics via @laava-ai/sdk/metrics geven inzicht in latency, request volume en error rates. De SDK levert een docker-compose.observability.yml die een Grafana-dashboard opzet met de juiste datasources en panelen. Je kunt in een nieuwe deployment binnen een paar minuten een werkend dashboard hebben.
Het verschil tussen een agent die je kunt beheren en een agent die je alleen kunt herstarten als het fout gaat, is vrijwel altijd observability.
5. Deployment en infrastructuur
Elke serieuze deployment vereist op een gegeven moment Docker-images, Kubernetes-manifesten, secrets management, een CI/CD pipeline en health checks. Dat zijn stuk voor stuk problemen die oplosbaar zijn, maar ze kosten tijd, ze hebben hun eigen best practices en ze zijn foutgevoelig als je ze opnieuw uitvogelt per project.
De SDK heeft een CLI die scaffolding biedt voor een volledige deployment-setup.
laava new platform
laava new agentlaava new platform genereert de platform-layer: Helm charts voor infrastructuur en secrets, GitHub Actions workflows, Docker-configuratie. laava new agent genereert een agent workspace die er naast kan worden geplaatst.
Secrets worden strikt gescheiden. Er is een runtime secret, beheerd door het platform, en een app secret, beheerd door de operator van de agent. Die scheiding voorkomt dat agent-code direct toegang heeft tot platform-level credentials, en dat platform-beheerders toegang hebben tot applicatie-specifieke geheimen.
De /ready endpoint controleert echte dependencies: PostgreSQL en Redis moeten bereikbaar zijn voordat de container als ready wordt aangemerkt. Kubernetes wacht op die readiness voor het routeren van traffic. Dit voorkomt een klasse van opstartproblemen waarbij de container draait maar de agent nog niet functioneel is.
6. Vector store en retrieval
Retrieval-augmented generation is voor veel agent-toepassingen de kern van wat de agent "slim" maakt. De agent weet niet alles uit zijn basistraining; hij haalt relevante context op uit een vector store. Maar de implementatie van die retrieval-laag heeft meer haken en ogen dan het op het eerste gezicht lijkt.
Het eerste probleem is chunking. Documenten moeten worden opgesplitst in stukken die passen in een embedding-model en die zinvol zijn als losstaande eenheden. Te groot en je verliest precisie. Te klein en je verliest context. De juiste chunkstrategie hangt af van het type document en het type queries.
Het tweede probleem is metadata filtering. In een systeem met meerdere klanten of meerdere documenttypen wil je kunnen filteren op metadata voordat je semantisch zoekt. Anders geef je de agent context die niet relevant is voor de specifieke query of, erger, context die niet voor die gebruiker bestemd is.
Het derde probleem is citation tracking. Als een agent een antwoord geeft op basis van opgehaalde documenten, wil je kunnen traceren welke passages zijn gebruikt. Dat is zowel een auditability-vereiste als een gebruiksvriendelijkheidsfeature.
De SDK biedt integratie via @laava-ai/sdk/db, @laava-ai/sdk/storage en @laava-ai/sdk/extractors. Qdrant wordt ondersteund als vector store via QDRANT_URL. De search-resultaten worden doorgegeven als een gestructureerd array in de agent state:
searchResults: Array<{ id: string, content: string, score: number, metadata: Record }> Die structuur maakt het eenvoudig om in de agent-logica te filteren op score, metadata te inspecteren en citaties samen te stellen op basis van de gebruikte passages.
7. Human-in-the-loop en review flows
Niet elke agent-output is geschikt voor directe verzending aan de eindgebruiker. In veel toepassingen, denk aan juridische documenten, klantenservice-antwoorden of financiële adviezen, is er een reviewstap vereist. De vraag is hoe je die reviewstap inbouwt zonder dat het een bottleneck wordt en zonder dat het de architectuur van je agent compliceert.
Het fundamentele patroon is een confidence gate. De agent berekent een confidence score voor zijn output. Als die score boven een drempel ligt, gaat het antwoord direct door. Als de score onder de drempel ligt, gaat het naar een review queue, waar een menselijke reviewer het kan goedkeuren, afwijzen of aanpassen.
import { evaluateConfidenceGate } from '@laava-ai/sdk/review'De confidenceScore zit in de output van de agent. De evaluateConfidenceGate functie evalueert die score ten opzichte van een configureerbare drempel en routeert de output naar de juiste bestemming.
Reviewer identity is een bijzonder gevoelig onderdeel van dit systeem. Als de reviewer zijn identiteit kan opgeven via de request body, is het systeem kwetsbaar voor manipulatie. De SDK haalt reviewer identity altijd uit het JWT-token, nooit uit de client-request. Dit koppelt de review-acties aan geauthenticeerde gebruikers en maakt het systeem auditeerbaar.
De review queue zelf is een persistent store van items die op review wachten, inclusief de originele input, de agent-output, de confidence score en de tijdstempel. Reviewers kunnen items ophalen, bekijken en afhandelen via een API die de SDK biedt.
8. Repo-structuur die meegroeit
De meeste AI-agent projecten beginnen klein. Een enkele service, een enkele repository, alles door elkaar. Dat is prima voor een prototype. Het probleem ontstaat als het project groeit: meer agents, meer integraties, meer teams. Dan wordt de initiële structuur een rem.
Het patroon dat goed blijkt te werken, is een expliciete scheiding tussen een platform-layer en agent workspaces. De platform-layer bevat alles wat gedeeld is: infrastructuur, secrets management, CI/CD, monitoring. Agent workspaces bevatten de domeinspecifieke logica van één agent.
platform/
helm/infra/
helm/secrets/
.github/workflows/
docker/
agents/my-agent/
packages/db/
services/agent/
services/api/
docker/laava new platform scaffoldt de platform-layer. laava new agent voegt een agent workspace toe die de platform-laag als dependency heeft, maar er niet in zit verstrengeld.
Die scheiding heeft een aantal praktische voordelen. Infrastructure-changes, zoals een Kubernetes-upgrade of een nieuw Helm-chart, raken alle agents tegelijk via de platform-layer. Agent-changes zijn geïsoleerd tot de eigen workspace. Teams kunnen parallel werken zonder elkaars code te raken. En een nieuwe agent toevoegen is een kwestie van een nieuwe workspace scaffolden, niet van de bestaande structuur aanpassen.
De SDK-modules sluiten aan op deze structuur. @laava-ai/sdk/db, @laava-ai/sdk/auth, @laava-ai/sdk/observability zitten in de platform-layer. De agent workspace importeert wat hij nodig heeft.
Waarom dit een platform-layer vereist
Al deze problemen zijn individueel oplosbaar. Webhook signature validation is niet zo ingewikkeld. Een Redis-backed rate limiter schrijf je in een middag. Een Langfuse-integratie is een kwestie van een paar uur. Het probleem is niet de individuele complexiteit; het is de cumulatieve complexiteit.
Als je elk van deze acht problemen per project oplost, ben je bij elk nieuw project een week of twee kwijt aan infrastructuur voordat je ook maar een regel agent-logica hebt geschreven. En dan zijn er nog de inconsistenties: de ene agent heeft replay protection, de andere niet. De ene heeft een /ready endpoint dat echte checks doet, de andere niet. Over tijd groeien de projecten uit elkaar, en je hebt geen gedeeld fundament meer.
De Laava SDK is het antwoord op die cumulatieve last. Niet een framework dat je dwingt tot een bepaalde architectuur, maar een platform-layer die de infrastructurele problemen één keer oplost en vervolgens beschikbaar maakt via consistente interfaces. De agent-logica blijft autonoom. De SDK handelt de plumbing af.
Het resultaat is dat een nieuw agent-project kan beginnen met laava new agent, een werkende scaffolding heeft binnen een paar minuten, en direct kan focussen op het domeinspecifieke probleem dat de agent moet oplossen. De authenticatie werkt. De rate limiting werkt. De observability werkt. De deployment-structuur staat klaar.
Dat is de 75% die anders per project opnieuw wordt gebouwd. Één keer goed oplossen, en daarna nooit meer.
