Agent-observability: hooks, Alloy en Grafana
> We hebben Claude Code en Codex verbonden met één Grafana-stack via OpenTelemetry en Alloy, en gebruikten vervolgens traces en logs om agentgedragsproblemen bij de bron op te sporen en te verhelpen.
Agentsystemen falen op vreemde manieren.
Soms is het model het probleem. Soms is de tool het probleem. Soms is je MCP-server prima in orde, maar koos de agent de verkeerde specialist, besteedde hij de halve sessie aan shellwerk dat je niet had verwacht, of verbrandde hij stilzwijgend kosten in een lus die van buitenaf productief leek.
Als je het verschil niet kunt zien, beheer je eigenlijk geen agentsysteem. Je gokt.
Dus bouwden we een observability-stack voor onze eigen workflow: Claude Code, Codex, Claude-hookevents, Codex-notifyevents, native OpenTelemetry, Grafana Alloy, en aan de andere kant Grafana Cloud.
Het interessante deel is niet “we hebben een dashboard gemaakt”. Het interessante deel is dat we telemetrie moesten opsplitsen in twee verschillende streams, omdat geen enkele feed op zich het volledige beeld gaf.
Het probleem: agenttelemetrie is gefragmenteerd
Moderne coding agents zenden al enige telemetrie uit. Dat helpt, maar het is niet genoeg.
Native OTEL is goed in het beantwoorden van vragen zoals:
- Hoeveel requests hebben we gedaan?
- Wat heeft een sessie gekost?
- Waar bevinden zich de spans en traces?
- Piekte de latency?
Het is veel minder goed in het beantwoorden van vragen zoals:
- Op welke MCP-server leunde de agent?
- Zat deze fout in
Bash, een ingebouwde bestandstool, of een MCP-call? - Welke skill werd daadwerkelijk geactiveerd?
- Welk type subagent werd ingezet?
- Deed de sessie nuttig werk, of ploeterde ze maar wat?
Die tweede categorie vragen ligt dichter bij hooks dan bij traces.
Maar het omgekeerde geldt ook: sommige van de belangrijkste performancevragen liggen dichter bij traces dan bij hooks.
Als je wilt weten waar de latency zich daadwerkelijk opstapelde, welke spans traag waren, of de sessie tijd verbrandde aan modelcalls versus tooluitvoering, heb je trace-data nodig naast semantische events.
De architectuur waar we op uitkwamen
We draaien twee telemetriepaden naast elkaar.
Claude Code
native OTEL -> Alloy -> Grafana Cloud
hooks -> send_event.py -> Grafana Cloud Loki
Codex
native OTEL -> Alloy -> Grafana Cloud
notify hook -> codex_notify.py -> shared Loki schema
Die opsplitsing is bewust.
Het is ook asymmetrisch. Claude Code geeft ons een veel rijker lifecycle-hookoppervlak. Codex geeft ons native OTEL plus een notify-oppervlak, dus normaliseren we dunnere turn-completion-events naar hetzelfde logschema in plaats van te doen alsof beide runtimes dezelfde controls bieden.
Native OTEL geeft ons de basisstream: logs en traces van de runtime zelf, plus metrics waar de runtime die daadwerkelijk uitzendt.
Hook- en notify-events geven ons de semantische laag: dingen zoals PreToolUse, PostToolUse, PostToolUseFailure, UserPromptSubmit, SubagentStop, SkillActivated, en de geclassificeerde metadata waar het ons daadwerkelijk om gaat bij het debuggen van agentgedrag. Claude Code levert hier de rijkere eventstream. Codex levert een dunnere maar nog altijd nuttige genormaliseerde stream.
Waarom hooks überhaupt bestaan
Onze hookpipeline verrijkt events voordat ze Loki bereiken.
In plaats van simpelweg te zeggen “er is een tool uitgevoerd”, classificeren we het event in velden zoals:
tool_type: builtin, mcp, skill, agent, bashmcp_server: welke MCP-backend de call afhandeldebash_cli: de shellcommandofamiliesubagent_type: welk type specialist werd ingezetagent_tool: of de bron Claude Code of Codex was
Dat betekent dat we vragen kunnen stellen die operationeel van belang zijn:
{service_name="claude-code-hooks"} | agent_tool="codex-cli"
{service_name="claude-code-hooks"} | tool_type="mcp"
{service_name="claude-code-hooks"} | json | bash_cli="git"
Dat zijn geen ijdelheidsvelden. Ze maken het verschil tussen “de agent voelde traag” en “de agent besteedde de laatste tien minuten aan shell-zware git-operaties met een hoog toolfoutpercentage.”
Eén implementatiedetail dat vreemder oogt dan het is: de gedeelde Loki-stream gebruikt nog altijd service_name="claude-code-hooks" als label, zelfs wanneer het event van Codex kwam. De echte opsplitsing tussen runtimes gebeurt op agent_tool.
Waarom Alloy in het midden zit
Grafana Alloy is in deze opzet niet zomaar een forwarder. Het is de beleidsgrens.
We wijzen de native OTEL-stream van Claude Code en Codex naar een lokale Alloy-proxy op localhost:4318, en laten Alloy vervolgens de payload opschonen voordat die wordt doorgestuurd naar Grafana Cloud.
Dat is belangrijk omdat ruwe agenttelemetrie vol zit met high-cardinality-velden die nuttig zijn voor analyse maar vreselijk als geïndexeerde labels:
session_idprompt_id- tokenaantallen
- duraties
- tool-parameterblobs
Als je alles indexeert, krijg je een labelexplosie en een rotdag.
Dus doet Alloy drie dingen voor ons:
- Houdt een zeer kleine set low-cardinality-labels geïndexeerd.
- Verplaatst ruizige maar nuttige velden naar gestructureerde metadata.
- Laat pure ruis volledig vallen.
Het belangrijkste idee is simpel: observeer meer, indexeer minder.
Waarom de hookstream Alloy omzeilt
De hookstream is al gevormd voor Loki.
Tegen de tijd dat send_event.py een event pusht, hebben we al besloten welke velden een labelbehandeling verdienen en welke thuishoren in de gestructureerde JSON-body. Die stream gaat rechtstreeks naar de OTLP-gateway van Grafana Cloud, zonder nog een keer via Alloy te lopen.
Het systeem heeft dus een duidelijke taakverdeling:
- Alloy temt de ruwe native OTEL-stream.
- Hookverrijking maakt semantische events doorzoekbaar.
Dat houdt de architectuur eenvoudiger dan proberen alles via één pad te forceren.
Wat het dashboard daadwerkelijk laat zien
De onderstaande screenshot komt van een van de observability-dashboards achter onze agentworkflow. Het is geen benchmark, en de cijfers zijn slechts een momentopname. Het gaat om de vorm van de data: activiteitenfeed, toolcalls, fouten, prompts, en uitsplitsingen per agent, ingebouwde tools, MCP-gebruik, shellcommando’s en skills.
Het nuttige deel is dat dit dashboard op dezelfde Grafana-stack leeft als de rest van de agenttelemetrie. We kunnen filteren op bronagent en toolfamilie en over runtimes heen kijken zonder voor elk systeem een ander observability-verhaal te verzinnen.
Waarom traces belangrijker zijn dan ze op het eerste gezicht lijken
Logs vertellen ons welke categorie werk plaatsvond. Traces vertellen ons hoe het werk zich in de tijd ontvouwde.
Dat onderscheid is belangrijk in agentsystemen omdat “traag” te bot is om nuttig te zijn.
Een trace kan ons vertellen of de pijn kwam van:
- modellatency
- tooluitvoeringstijd
- herhaalde retries
- één bijzonder dure MCP-interactie
- een lange staart aan kleine operaties die afzonderlijk onschuldig leken
In de praktijk gebruiken we de hookstream en Tempo-traces samen.
- Hooklogs beantwoorden: wat voor soort dingen gebeurde er?
- Traces beantwoorden: waar ging de tijd naartoe?
Die combinatie is wat observability verandert van een dashboard in een verklaring.
Waar Codex in past
Codex maakt deel uit van dezelfde stack, maar is niet identiek aan Claude Code.
Voor Codex koppelen we twee onderdelen:
- Native OTEL van Codex naar Alloy
- Een notify-webhook naar
codex_notify.py, die turn completions afbeeldt op hetzelfde Loki-schema dat we voor hookevents gebruiken
Dat geeft ons een uniform filter zoals agent_tool="codex-cli" binnen dezelfde logstream.
De eerlijke kanttekening: de notify-payload van Codex is momenteel dunner dan de hookpayload van Claude Code, omdat het niet hetzelfde soort integratieoppervlak is. In onze huidige opzet kunnen Codex-turn-completions worden genormaliseerd naar het gedeelde schema, maar rijke tool-voor-tool-extractie werkt nog altijd beter in de native OTEL-stream dan in de notify-bridge.
Dat is geen reden om deze post te vermijden. Het is precies het punt van deze post. Echte observability-systemen worden opgebouwd uit imperfecte signalen.
Grafana via MCP verandert het spel
De grotere verschuiving is dat Grafana niet alleen een plek is die mensen in een browser bezoeken.
In deze repo stellen we Grafana ook beschikbaar via MCP. Dat betekent dat een agent Loki, Prometheus en Tempo rechtstreeks kan bevragen, in plaats van te wachten tot een mens eerst handmatig de dashboards inspecteert.
Dat maakt van observability een actieve input voor de workflow.
Een agent kan vragen:
- Welke toolfamilies faalden het meest in het afgelopen uur?
- Welke MCP-server domineerde een sessie?
- Verminderden recente wijzigingen het aantal toolfouten, of verplaatsten ze het werk alleen naar meer shell-zware paden met dezelfde fouten?
- Welke traces vertonen de hoogste latency of herhaalde retries?
Zodra je dat hebt, ben je heel dicht bij een zelfverbeteringslus.
Van dashboard naar feedbacklus
Dit is het deel dat we het interessantst vinden.
Zodra de observability-stack bevraagbaar is vanuit de agentlaag, houdt telemetrie op een passief rapportageoppervlak te zijn en wordt het een besturingssignaal.
De lus ziet er zo uit:
- Agentactiviteit genereert traces, metrics en verrijkte hooklogs.
- Grafana slaat het bewijsmateriaal op in Loki, Tempo en Prometheus waar metrics bestaan.
- Agents bevragen dat bewijsmateriaal via Grafana MCP.
- Het systeem identificeert een slechte toolmix, broze skills, zwakke routering, of shell-zware workflows die steeds vermijdbare fouten blijven produceren.
- Agents of operators passen prompts, agentconfiguraties, skillbeschrijvingen, routeringsregels of tooltoegang aan.
- De volgende sessie produceert een nieuwe telemetrievorm, en de cyclus herhaalt zich.
Zo beweeg je van “interessant dashboard” naar “meetbaar verbetersysteem”.
Het doel is niet om één toolcategorie te maximaliseren. Het doel is uit te komen op de juiste mix van CLI, ingebouwde tools, MCP-calls en skills voor het werk dat daadwerkelijk wordt gedaan.
Wat dit ons laat beantwoorden
Zodra beide runtimes in dezelfde Grafana-stack terechtkomen, kunnen we operationele vragen veel sneller beantwoorden:
- Concentreren fouten zich in één toolfamilie?
- Veroorzaken shell-zware workflows vermijdbare fouten op plekken waar een hoger-niveau-tool zou moeten bestaan?
- Welke MCP-servers dragen de werklast?
- Betalen we voor agentactiviteit die geen betekenisvolle voortgang oplevert?
- Is een sessie ongezond vanwege het model, de tools, of de orchestratielaag?
Dit is vooral nuttig in multi-agent-workflows, waar “de agent was bezig” je bijna niets vertelt.
Als één specialist steeds wordt ingezet en hoge foutpercentages oplevert, is dat een routerings- of prompt-vormgevingsprobleem.
Als één MCP-server alle calls domineert, kan dat goede architectuur zijn, of een teken dat al het andere dood gewicht is.
Als toolfouten pieken terwijl de kosten hoog blijven, heb je een operationeel probleem, geen kwaliteitsprobleem.
Als shellwerk steeds op voorspelbare, vermijdbare manieren mislukt op plekken waar een hoger-niveau-tool zou moeten bestaan, is dat een productsignaal.
Als één skill voortdurend wordt geactiveerd maar de resultaten niet verbetert, is dat een prompt- of routeringssignaal.
De echte les
De diepere les hier is dat agent-observability zowel runtime-telemetrie als workflow-telemetrie nodig heeft.
Runtime-telemetrie vertelt je wat het systeem deed.
Workflow-telemetrie vertelt je wat de agent dacht te doen.
We hebben beide nodig.
Als je alleen traces en counters bijhoudt, mis je de semantische laag. Als je alleen hookevents bijhoudt, mis je latency, spans en het bredere runtimebeeld.
En als je beide bijhoudt maar ze nooit terugvoert naar de agentlaag, heb je monitoring, geen aanpassing.
Die combinatie is wat het systeem uitlegbaar genoeg maakt om te beheren en afstembaar genoeg om te verbeteren.
Wat nog altijd niet perfect is
Er zijn nog altijd ruwe randjes.
- Niet elk hookevent bevat de duratie- en tokendata die we zouden willen.
- Sommige van de beste timingweergaven komen nog altijd van Tempo-traces, niet van de hooklogs.
- Codex is momenteel semantisch minder rijk dan Claude Code in de verrijkte eventstream.
- De dashboardscreenshot is een live operations-oppervlak, geen gepolijst marketingartefact.
Dat laatste punt is bewust. We laten liever het echte instrumentenpaneel zien dan doen alsof agentsystemen magisch vanzelfsprekend zijn.
Waarom dit belangrijk is voor Maguyva
Maguyva draait om agents betere code intelligence geven. Maar zodra agents daadwerkelijk nuttig werk doen, duikt er meteen een nieuwe vereiste op: je moet kunnen zien hoe ze zich gedragen.
Zoekkwaliteit, routeringskwaliteit, toolselectie en contextefficiëntie worden allemaal observeerbare vraagstukken.
Daarom denken we dat dit de moeite van het beschrijven waard is. De toekomstige agentstack is niet alleen prompts en tools. Het is prompts, tools, en de instrumentatielaag die je vertelt of het geheel werkt.
Als je serieuze agentworkflows bouwt, is observability geen optionele infrastructuur. Het is onderdeel van het product.
Gerelateerde artikelen
Meer uit het bouwlogboek van Maguyva
Waarom we code search hebben geüpgraded naar voyage-4-large_
We hebben onze code-embeddings verplaatst naar voyage-4-large — momenteel bovenaan het publieke RTEB code-retrieval-leaderboard. De eerlijke versie: de afweging die we maken, wat we daadwerkelijk indexeren, en waarom we betalen voor premium embeddings.
Recursieve zelfverbetering voor taal: Code Intelligence slijpen over ~280 talen_
We ondersteunen code intelligence voor ~280 talen. Geen mens kan dat handmatig auditen. Dus bouwden we een recursieve zelfverbeteringslus voor taal — steekproeven nemen, LLM-as-judge, één ding repareren, opnieuw valideren — en draaien die met een vloot geïsoleerde agents totdat extractie daadwerkelijk klopt, niet alleen groen is.
Multimodale fusiezoek: voor elke query de juiste retriever kiezen_
Een query zoals 'waar is parseConfig gedefinieerd' vraagt om een ander soort zoeken dan 'hoe werkt auth'. Maguyva classificeert de intentie, weegt vier retrievalmodaliteiten dienovereenkomstig, en voegt de resultaten samen met gewogen Reciprocal Rank Fusion.