Spring til indhold
cd /blog

Agent-observability: Hooks, Alloy og Grafana

[Observerbarhed][Grafana][OpenTelemetry][Arkitektur]

> Vi koblede Claude Code og Codex sammen i én Grafana-stak med OpenTelemetry og Alloy, og brugte derefter traces og logs til at finde og rette agentadfærdsproblemer ved kilden.

Agentsystemer fejler på mærkelige måder.

Nogle gange er modellen problemet. Nogle gange er værktøjet problemet. Nogle gange er din MCP-server fin, men agenten valgte den forkerte specialist, eller brugte halvdelen af sessionen på shell-arbejde, du ikke havde forventet, eller brændte stille og roligt omkostninger af i en løkke, der så produktiv ud udefra.

Hvis du ikke kan se forskellen, driver du ikke rigtig et agentsystem. Du gætter.

Så vi byggede en observability-stak til vores egen arbejdsgang: Claude Code, Codex, Claude hook-events, Codex notify-events, native OpenTelemetry, Grafana Alloy og Grafana Cloud i den anden ende.

Den interessante del er ikke “vi lavede et dashboard.” Den interessante del er, at vi var nødt til at opdele telemetri i to forskellige strømme, fordi ingen enkelt feed gav os hele billedet.

Problemet: agent-telemetri er fragmenteret

Moderne kodeagenter udsender allerede noget telemetri. Det hjælper, men det er ikke nok.

Native OTEL er god til at besvare spørgsmål som:

  • Hvor mange requests foretog vi?
  • Hvor meget kostede en session?
  • Hvor er spans og traces?
  • Steg latensen?

Den er meget dårligere til at besvare spørgsmål som:

  • Hvilken MCP-server lænede agenten sig op ad?
  • Var denne fejl i Bash, et indbygget filværktøj eller et MCP-kald?
  • Hvilken skill blev rent faktisk aktiveret?
  • Hvilken subagent-type blev afsendt?
  • Udførte sessionen nyttigt arbejde, eller hakkede den bare rundt?

Den anden klasse af spørgsmål lever tættere på hooks end på traces.

Men det omvendte gælder også: nogle af de vigtigste performance-spørgsmål lever tættere på traces end på hooks.

Hvis du vil vide, hvor latensen faktisk akkumulerede sig, hvilke spans der var langsomme, eller om sessionen brugte tid på modelkald frem for værktøjseksekvering, har du brug for trace-data lige så vel som semantiske events.

Arkitekturen, vi endte med

Vi kører to telemetristier side om side.

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

Den opdeling er bevidst.

Den er også asymmetrisk. Claude Code giver os en langt rigere livscyklus-hook-flade. Codex giver os native OTEL plus en notify-flade, så vi normaliserer tyndere turn-completion-events ind i det samme log-skema i stedet for at foregive, at begge runtimes eksponerer de samme kontroller.

Native OTEL giver os basisstrømmen: logs og traces fra selve runtimet, plus metrikker der, hvor runtimet rent faktisk udsender dem.

Hook- og notify-events giver os det semantiske lag: ting som PreToolUse, PostToolUse, PostToolUseFailure, UserPromptSubmit, SubagentStop, SkillActivated og de klassificerede metadata, vi rent faktisk bekymrer os om, når vi debugger agentadfærd. Claude Code bidrager med den rigere event-strøm her. Codex bidrager med en tyndere, men stadig nyttig normaliseret strøm.

Hvorfor hooks overhovedet findes

Vores hook-pipeline beriger events, før de rammer Loki.

I stedet for bare at sige “et værktøj kørte”, klassificerer vi eventet i felter som:

  • tool_type: builtin, mcp, skill, agent, bash
  • mcp_server: hvilken MCP-backend håndterede kaldet
  • bash_cli: shell-kommandofamilien
  • subagent_type: hvilken slags specialist blev afsendt
  • agent_tool: om kilden var Claude Code eller Codex

Det betyder, at vi kan stille spørgsmål, der betyder noget driftsmæssigt:

{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"

De felter er ikke forfængelighed. De er forskellen mellem “agenten føltes langsom” og “agenten brugte de sidste ti minutter på shell-tunge git-operationer med en høj værktøjsfejlrate.”

Én implementeringsdetalje, der ser mærkeligere ud, end den er: den delte Loki-strøm bruger stadig service_name="claude-code-hooks" som label, selv når eventet kom fra Codex. Den reelle opdeling mellem runtimes sker på agent_tool.

Hvorfor Alloy sidder i midten

Grafana Alloy er ikke bare en videresender i denne opsætning. Det er politikgrænsen.

Vi peger den native OTEL-strøm fra Claude Code og Codex mod en lokal Alloy-proxy på localhost:4318 og lader derefter Alloy rydde op i payloaden, før den videresendes til Grafana Cloud.

Det betyder noget, fordi rå agent-telemetri er fuld af høj-kardinalitetsfelter, der er nyttige til analyse, men forfærdelige som indekserede labels:

  • session_id
  • prompt_id
  • token-antal
  • varigheder
  • værktøjsparameter-blobs

Hvis du indekserer alt, får du en label-eksplosion og en dårlig dag.

Så Alloy gør tre ting for os:

  1. Holder et meget lille sæt lav-kardinalitetslabels indekseret.
  2. Flytter støjende, men nyttige felter over i struktureret metadata.
  3. Dropper ren støj helt.

Den vigtige idé er simpel: observér mere, indeksér mindre.

Hvorfor hook-strømmen springer Alloy over

Hook-strømmen er allerede formet til Loki.

I det øjeblik send_event.py pusher et event, har vi allerede besluttet, hvilke felter der fortjener label-behandling, og hvilke der hører hjemme i den strukturerede JSON-krop. Den strøm går direkte til Grafana Clouds OTLP-gateway i stedet for at tage endnu en tur gennem Alloy.

Så systemet har en klar arbejdsdeling:

  • Alloy tæmmer den rå native OTEL-strøm.
  • Hook-berigelse gør semantiske events forespørgelige.

Det holder arkitekturen enklere end at forsøge at tvinge alt gennem én sti.

Hvad dashboardet rent faktisk viser

Skærmbilledet nedenfor er fra et af observability-dashboardene bag vores agent-arbejdsgang. Det er ikke en benchmark, og tallene er blot et øjebliksbillede. Pointen er formen på dataene: activity feed, værktøjskald, fejl, prompts og opdelinger efter agent, indbyggede værktøjer, MCP-brug, shell-kommandoer og skills.

Det nyttige er, at dette dashboard lever på den samme Grafana-stak som resten af agent-telemetrien. Vi kan filtrere efter kilde-agent og værktøjsfamilie og se på tværs af runtimes uden at opfinde en anden observability-historie for hvert system.

Grafana-dashboard, der viser et activity feed, antal værktøjskald, fejl, prompts og opdelinger efter agenter, indbyggede værktøjer, MCP-brug, CLI-kommandoer og skills for en agent-arbejdsgang.
Et af de live dashboards bag vores agent-arbejdsgang. Dette skærmbillede er blot ét udsnit af en delt Grafana-stak, der også modtager telemetri fra vores andre agent-runtimes og -systemer. Klik på billedet for versionen i fuld opløsning.

Hvorfor traces betyder mere, end de først ser ud til

Logs fortæller os, hvilken kategori af arbejde der skete. Traces fortæller os, hvordan arbejdet udfoldede sig over tid.

Den skelnen betyder noget i agentsystemer, fordi “langsom” er for groft til at være nyttigt.

Et trace kan fortælle os, om smerten kom fra:

  • modellatens
  • værktøjseksekveringstid
  • gentagne retries
  • én særligt dyr MCP-interaktion
  • en lang hale af små operationer, der isoleret set så harmløse ud

I praksis bruger vi hook-strømmen og Tempo-traces sammen.

  • Hook-logs besvarer: hvilken slags ting skete der?
  • Traces besvarer: hvor gik tiden hen?

Kombinationen er det, der forvandler observability fra et dashboard til en forklaring.

Hvor Codex passer ind

Codex er en del af den samme stak, men den er ikke identisk med Claude Code.

For Codex kobler vi to dele sammen:

  • Native OTEL fra Codex ind i Alloy
  • Et notify-webhook ind i codex_notify.py, som mapper turn-completions til det samme Loki-skema, vi bruger til hook-events

Det giver os et samlet filter som agent_tool="codex-cli" inden i den samme log-strøm.

Det ærlige forbehold: Codex’ notify-payload er i øjeblikket tyndere end Claude Codes hook-payload, fordi det ikke er den samme slags integrationsflade. I vores nuværende opsætning kan Codex’ turn-completions normaliseres ind i det delte skema, men rig værktøj-for-værktøj-udtræk er stadig bedre i den native OTEL-strøm end i notify-broen.

Det er ikke en grund til at undgå indlægget. Det er hele pointen med indlægget. Rigtige observability-systemer samles ud fra ufuldkomne signaler.

Grafana over MCP ændrer spillet

Det større skifte er, at Grafana ikke kun er et sted, mennesker besøger i en browser.

I dette repository eksponerer vi også Grafana gennem MCP. Det betyder, at en agent kan forespørge Loki, Prometheus og Tempo direkte i stedet for at vente på, at et menneske manuelt inspicerer dashboardene først.

Det gør observability til et aktivt input til arbejdsgangen.

En agent kan spørge:

  • Hvilke værktøjsfamilier fejlede mest i den seneste time?
  • Hvilken MCP-server dominerede en session?
  • Reducerede seneste ændringer værktøjsfejl, eller flyttede de bare arbejdet over i mere shell-tunge stier med de samme fejl?
  • Hvilke traces viser den højeste latens eller gentagne retries?

Når du har det, er du meget tæt på en selvforbedringsløkke.

Fra dashboard til feedbackløkke

Det er den del, vi finder mest interessant.

Når observability-stakken er forespørgelig fra agentlaget, holder telemetri op med at være en passiv rapporteringsflade og bliver et kontrolsignal.

Løkken ser sådan ud:

  1. Agentaktivitet udsender traces, metrikker og berigede hook-logs.
  2. Grafana gemmer beviserne i Loki, Tempo og Prometheus, hvor metrikker findes.
  3. Agenter forespørger de beviser gennem Grafana MCP.
  4. Systemet identificerer dårlig værktøjsmiks, skrøbelige skills, svag routing eller shell-tunge arbejdsgange, der bliver ved med at producere undgåelige fejl.
  5. Agenter eller operatører justerer prompts, agentkonfigurationer, skill-beskrivelser, routing-regler eller værktøjsadgang.
  6. Den næste session producerer en ny telemetriform, og cyklussen gentages.

Sådan bevæger man sig fra “interessant dashboard” til “målbart forbedringssystem.”

Målet er ikke at maksimere én værktøjskategori. Det er at lande på den rette blanding af CLI, indbyggede værktøjer, MCP-kald og skills til det arbejde, der rent faktisk udføres.

Hvad dette lader os besvare

Når begge runtimes lander i den samme Grafana-stak, kan vi besvare driftsspørgsmål meget hurtigere:

  • Er fejl koncentreret i én værktøjsfamilie?
  • Skaber shell-tunge arbejdsgange undgåelige fejl, hvor et højere-niveau-værktøj burde eksistere?
  • Hvilke MCP-servere bærer arbejdsbyrden?
  • Betaler vi for agentaktivitet, der ikke producerer meningsfuld fremdrift?
  • Er en session usund på grund af modellen, værktøjerne eller orkestreringslaget?

Det er særligt nyttigt i multi-agent-arbejdsgange, hvor “agenten var travlt optaget” fortæller dig næsten ingenting.

Hvis én specialist bliver ved med at blive afsendt og producerer høje fejlrater, er det et routing- eller prompt-formningsproblem.

Hvis én MCP-server dominerer alle kald, kan det være god arkitektur eller et tegn på, at alt andet er dødvægt.

Hvis værktøjsfejl stiger, mens omkostningerne forbliver høje, har du et driftsproblem, ikke et kvalitetsproblem.

Hvis shell-arbejde bliver ved med at fejle på forudsigelige, undgåelige måder, hvor et højere-niveau-værktøj burde eksistere, er det et produktsignal.

Hvis én skill aktiveres konstant, men ikke forbedrer resultaterne, er det et prompt- eller routing-signal.

Den reelle lektie

Den dybere lektie her er, at agent-observability har brug for både runtime-telemetri og arbejdsgangs-telemetri.

Runtime-telemetri fortæller dig, hvad systemet gjorde.

Arbejdsgangs-telemetri fortæller dig, hvad agenten troede, den var i gang med.

Vi har brug for begge dele.

Hvis du kun beholder traces og tællere, går du glip af det semantiske lag. Hvis du kun beholder hook-events, går du glip af latens, spans og det bredere runtime-billede.

Og hvis du beholder begge dele, men aldrig fodrer dem tilbage ind i agentlaget, har du monitorering, ikke tilpasning.

Kombinationen er det, der gør systemet forklarligt nok til at drive og finjusterbart nok til at forbedre.

Hvad der stadig er ufuldkomment

Der er stadig rå kanter.

  • Ikke ethvert hook-event inkluderer den varigheds- og token-data, vi gerne ville have.
  • Nogle af de bedste timing-visninger kommer stadig fra Tempo-traces, ikke hook-logs.
  • Codex er i dag mindre semantisk rig end Claude Code i den berigede event-strøm.
  • Dashboard-skærmbilledet er en levende driftsflade, ikke et poleret marketingartefakt.

Det sidste punkt er tilsigtet. Vi vil hellere vise det rigtige instrumentbræt end lade som om, agentsystemer magisk forklarer sig selv.

Hvorfor det betyder noget for Maguyva

Maguyva handler om at give agenter bedre code intelligence. Men når agenter rent faktisk udfører nyttigt arbejde, dukker der straks et nyt krav op: du skal kunne se, hvordan de opfører sig.

Søgekvalitet, routing-kvalitet, værktøjsvalg og kontekstefficiens bliver alle sammen observerbare problemer.

Det er derfor, vi mener, det er værd at skrive om. Fremtidens agent-stak er ikke bare prompts og værktøjer. Det er prompts, værktøjer og det instrumenteringslag, der fortæller dig, om det hele fungerer.

Hvis du bygger seriøse agent-arbejdsgange, er observability ikke valgfri infrastruktur. Det er en del af produktet.

Relateret læsning

Mere fra Maguyva-byggeloggen