Hopp til innhold
cd /blog

Agentobservabilitet: Hooks, Alloy og Grafana

[Observerbarhet][Grafana][OpenTelemetry][Arkitektur]

> Vi koblet Claude Code og Codex til én Grafana-stack med OpenTelemetry og Alloy, og brukte deretter spor (traces) og logger til å finne og fikse agentatferdsproblemer ved kilden.

Agentsystemer feiler på merkelige måter.

Noen ganger er modellen problemet. Noen ganger er verktøyet problemet. Noen ganger er MCP-serveren din helt fin, men agenten valgte feil spesialist, eller brukte halve økten på shell-arbeid du ikke forventet, eller brant kostnader stille i en løkke som så produktiv ut utenfra.

Hvis du ikke kan se forskjellen, driver du ikke egentlig et agentsystem. Du gjetter.

Så vi bygde en observability-stack for vår egen arbeidsflyt: Claude Code, Codex, Claude hook-hendelser, Codex notify-hendelser, native OpenTelemetry, Grafana Alloy, og Grafana Cloud i den andre enden.

Det interessante er ikke «vi laget et dashbord». Det interessante er at vi måtte dele telemetrien i to forskjellige strømmer fordi ingen enkelt kilde ga oss hele bildet.

Problemet: Agenttelemetri er fragmentert

Moderne kodeagenter sender allerede noe telemetri. Det hjelper, men det er ikke nok.

Native OTEL er god til å svare på spørsmål som:

  • Hvor mange forespørsler gjorde vi?
  • Hvor mye kostet en økt?
  • Hvor er spanene og sporene (traces)?
  • Var det en latenstopp?

Den er mye dårligere til å svare på spørsmål som:

  • Hvilken MCP-server lente agenten seg på?
  • Skjedde denne feilen i Bash, et innebygd filverktøy, eller et MCP-kall?
  • Hvilken skill ble faktisk aktivert?
  • Hvilken subagent-type ble dispatchet?
  • Gjorde økten nyttig arbeid, eller bare tråkket den på stedet?

Den andre klassen spørsmål hører hjemme nærmere hooks enn spor.

Men det motsatte er også sant: noen av de viktigste ytelsesspørsmålene hører hjemme nærmere spor enn hooks.

Hvis du vil vite hvor latensen faktisk hopet seg opp, hvilke spans som var trege, eller om økten brukte tid på modellkall versus verktøyutførelse, trenger du sporingsdata i tillegg til semantiske hendelser.

Arkitekturen vi endte opp med

Vi kjører to telemetriløyper 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 delingen er bevisst.

Den er også asymmetrisk. Claude Code gir oss en mye rikere hook-overflate for livssyklusen. Codex gir oss native OTEL pluss en notify-overflate, så vi normaliserer tynnere hendelser for turfullføring inn i det samme loggskjemaet i stedet for å late som om begge runtimene eksponerer de samme kontrollene.

Native OTEL gir oss basisstrømmen: logger og spor fra selve runtimen, pluss metrikker der runtimen faktisk sender dem.

Hook- og notify-hendelser gir oss det semantiske laget: ting som PreToolUse, PostToolUse, PostToolUseFailure, UserPromptSubmit, SubagentStop, SkillActivated, og de klassifiserte metadataene vi faktisk bryr oss om når vi feilsøker agentatferd. Claude Code bidrar med den rikere hendelsesstrømmen her. Codex bidrar med en tynnere, men fortsatt nyttig, normalisert strøm.

Hvorfor hooks i det hele tatt finnes

Hook-pipelinen vår beriker hendelser før de treffer Loki.

I stedet for bare å si «et verktøy kjørte», klassifiserer vi hendelsen i felt som:

  • tool_type: builtin, mcp, skill, agent, bash
  • mcp_server: hvilken MCP-backend som håndterte kallet
  • bash_cli: shell-kommandofamilien
  • subagent_type: hva slags spesialist som ble dispatchet
  • agent_tool: om kilden var Claude Code eller Codex

Det betyr at vi kan stille spørsmål som betyr noe operasjonelt:

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

Dette er ikke pyntefelt. De er forskjellen mellom «agenten føltes treg» og «agenten brukte de siste ti minuttene på shell-tunge git-operasjoner med høy feilrate på verktøy».

En implementasjonsdetalj som ser merkeligere ut enn den er: den delte Loki-strømmen bruker fortsatt service_name="claude-code-hooks" som etikett selv når hendelsen kom fra Codex. Den reelle skillelinjen mellom runtimene skjer på agent_tool.

Hvorfor Alloy sitter i midten

Grafana Alloy er ikke bare en videresender i dette oppsettet. Det er policygrensen.

Vi peker den native OTEL-strømmen fra Claude Code og Codex mot en lokal Alloy-proxy på localhost:4318, og lar deretter Alloy rydde opp i nyttelasten før den videresendes til Grafana Cloud.

Det betyr noe fordi rå agenttelemetri er full av felt med høy kardinalitet som er nyttige for analyse, men forferdelige som indekserte etiketter:

  • session_id
  • prompt_id
  • antall tokens
  • varigheter
  • verktøyparameter-blobber

Hvis du indekserer alt, får du en etiketteksplosjon og en dårlig dag.

Så Alloy gjør tre ting for oss:

  1. Holder et svært lite sett med lav-kardinalitets-etiketter indeksert.
  2. Flytter støyende, men nyttige felt inn i strukturert metadata.
  3. Dropper ren støy helt.

Den viktige ideen er enkel: observer mer, indekser mindre.

Hvorfor hook-strømmen går utenom Alloy

Hook-strømmen er allerede formet for Loki.

Når send_event.py pusher en hendelse, har vi allerede bestemt hvilke felt som fortjener etikettbehandling og hvilke som hører hjemme i den strukturerte JSON-kroppen. Den strømmen går rett til Grafana Clouds OTLP-gateway i stedet for å gå gjennom Alloy en gang til.

Så systemet har en klar arbeidsdeling:

  • Alloy temmer den rå native OTEL-strømmen.
  • Hook-berikelse gjør semantiske hendelser søkbare.

Det holder arkitekturen enklere enn å prøve å tvinge alt gjennom én sti.

Hva dashbordet faktisk viser

Skjermbildet under er fra ett av observability-dashbordene bak agentarbeidsflyten vår. Det er ikke en benchmark, og tallene er bare et øyeblikksbilde. Poenget er formen på dataene: aktivitetsstrøm, verktøykall, feil, prompter, og oppdelinger etter agent, innebygde verktøy, MCP-bruk, shell-kommandoer og skills.

Det nyttige er at dette dashbordet lever på samme Grafana-stack som resten av agenttelemetrien. Vi kan filtrere etter kildeagent og verktøyfamilie og se på tvers av runtimer uten å finne opp en egen observability-historie for hvert system.

Grafana-dashbord som viser en aktivitetsstrøm, antall verktøykall, feil, prompter og oppdelinger etter agenter, innebygde verktøy, MCP-bruk, CLI-kommandoer og skills for en agentarbeidsflyt.
Ett av de aktive dashbordene bak agentarbeidsflyten vår. Dette skjermbildet er bare ett øyeblikksbilde av en delt Grafana-stack som også mottar telemetri fra våre andre agent-runtimer og systemer. Klikk på bildet for versjonen i full oppløsning.

Hvorfor spor betyr mer enn de først virker

Logger forteller oss hvilken kategori arbeid som skjedde. Spor forteller oss hvordan arbeidet utfoldet seg over tid.

Det skillet betyr noe i agentsystemer fordi «treg» er for grovt til å være nyttig.

Et spor kan fortelle oss om smerten kom fra:

  • modell-latens
  • verktøyutførelsestid
  • gjentatte forsøk (retries)
  • én spesielt kostbar MCP-interaksjon
  • en lang hale av små operasjoner som virket ufarlige hver for seg

I praksis bruker vi hook-strømmen og Tempo-spor sammen.

  • Hook-logger svarer på: hva slags ting skjedde?
  • Spor svarer på: hvor gikk tiden?

Kombinasjonen er det som gjør observability om fra et dashbord til en forklaring.

Hvor Codex passer inn

Codex er en del av samme stack, men er ikke identisk med Claude Code.

For Codex kobler vi to deler:

  • Native OTEL fra Codex inn i Alloy
  • En notify-webhook inn i codex_notify.py, som mapper turfullføringer til det samme Loki-skjemaet vi bruker for hook-hendelser

Det gir oss et samlet filter som agent_tool="codex-cli" i den samme loggstrømmen.

Det ærlige forbeholdet: Codex sin notify-nyttelast er for øyeblikket tynnere enn Claude Codes hook-nyttelast, fordi det ikke er samme type integrasjonsoverflate. I oppsettet vårt i dag kan Codex-turfullføringer normaliseres inn i det delte skjemaet, men rik uttrekking verktøy for verktøy er fortsatt bedre i den native OTEL-strømmen enn i notify-broen.

Det er ikke en grunn til å unngå innlegget. Det er poenget med innlegget. Ekte observability-systemer er satt sammen av ufullkomne signaler.

Grafana over MCP endrer spillet

Det større skiftet er at Grafana ikke bare er et sted mennesker besøker i en nettleser.

I dette repoet eksponerer vi også Grafana gjennom MCP. Det betyr at en agent kan spørre Loki, Prometheus og Tempo direkte, i stedet for å vente på at et menneske manuelt skal inspisere dashbordene først.

Det gjør observability til et aktivt input til arbeidsflyten.

En agent kan spørre:

  • Hvilke verktøyfamilier feilet mest den siste timen?
  • Hvilken MCP-server dominerte en økt?
  • Reduserte nylige endringer verktøyfeil, eller flyttet de bare arbeidet inn i mer shell-tunge stier med de samme feilene?
  • Hvilke spor viser høyest latens eller gjentatte forsøk?

Når du har det, er du veldig nær en selvforbedringsløkke.

Fra dashbord til tilbakemeldingsløkke

Dette er delen vi synes er mest interessant.

Når observability-stacken er søkbar fra agentlaget, slutter telemetri å være en passiv rapporteringsoverflate og blir et kontrollsignal.

Løkken ser slik ut:

  1. Agentaktivitet sender ut spor, metrikker og berikede hook-logger.
  2. Grafana lagrer bevisene i Loki, Tempo og Prometheus der metrikker finnes.
  3. Agenter spør de bevisene gjennom Grafana MCP.
  4. Systemet identifiserer dårlig verktøymiks, sprø skills, svak ruting, eller shell-tunge arbeidsflyter som fortsetter å produsere unngåelige feil.
  5. Agenter eller operatører justerer prompter, agentkonfigurasjoner, skill-beskrivelser, rutingregler, eller verktøytilgang.
  6. Neste økt produserer en ny telemetriform, og syklusen gjentar seg.

Det er slik du beveger deg fra «interessant dashbord» til «målbart forbedringssystem».

Målet er ikke å maksimere én verktøykategori. Det er å lande på riktig miks av CLI, innebygde verktøy, MCP-kall og skills for arbeidet som faktisk gjøres.

Hva dette lar oss svare på

Når begge runtimene lander i samme Grafana-stack, kan vi svare på operasjonelle spørsmål mye raskere:

  • Er feilene konsentrert i én verktøyfamilie?
  • Skaper shell-tunge arbeidsflyter unngåelige feil der et verktøy på høyere nivå burde finnes?
  • Hvilke MCP-servere bærer arbeidsmengden?
  • Betaler vi for agentaktivitet som ikke gir meningsfull fremgang?
  • Er en økt usunn på grunn av modellen, verktøyene, eller orkestreringslaget?

Dette er spesielt nyttig i multi-agent-arbeidsflyter, der «agenten var opptatt» ikke forteller deg nesten noe.

Hvis én spesialist stadig blir dispatchet og produserer høye feilrater, er det et rutings- eller promptformingsproblem.

Hvis én MCP-server dominerer alle kall, kan det være god arkitektur eller et tegn på at alt annet er dødvekt.

Hvis verktøyfeil spikes mens kostnaden holder seg høy, har du et operasjonelt problem, ikke et kvalitetsproblem.

Hvis shell-arbeid fortsetter å feile på forutsigbare, unngåelige måter der et verktøy på høyere nivå burde finnes, er det et produktsignal.

Hvis én skill aktiveres konstant, men ikke forbedrer resultatene, er det et prompt- eller rutingsignal.

Den egentlige lærdommen

Den dypere lærdommen her er at agentobservability trenger både runtime-telemetri og arbeidsflyt-telemetri.

Runtime-telemetri forteller deg hva systemet gjorde.

Arbeidsflyt-telemetri forteller deg hva agenten trodde den holdt på med.

Vi trenger begge deler.

Hvis du bare beholder spor og tellere, går du glipp av det semantiske laget. Hvis du bare beholder hook-hendelser, går du glipp av latens, spans, og det bredere runtime-bildet.

Og hvis du beholder begge, men aldri mater dem tilbake til agentlaget, har du overvåking, ikke tilpasning.

Kombinasjonen er det som gjør systemet forklarlig nok til å drifte og justerbart nok til å forbedre.

Hva som fortsatt er ufullkomment

Det finnes fortsatt ru kanter.

  • Ikke alle hook-hendelser inneholder varighets- og token-dataene vi skulle ønske.
  • Noen av de beste tidsvisningene kommer fortsatt fra Tempo-spor, ikke hook-loggene.
  • Codex er i dag mindre semantisk rikt enn Claude Code i den berikede hendelsesstrømmen.
  • Dashbord-skjermbildet er en levende driftsoverflate, ikke et polert markedsføringsprodukt.

Det siste punktet er bevisst. Vi vil heller vise det virkelige instrumentpanelet enn å late som om agentsystemer er magisk selvforklarende.

Hvorfor dette betyr noe for Maguyva

Maguyva handler om å gi agenter bedre kodeintelligens. Men når agenter faktisk gjør nyttig arbeid, dukker det umiddelbart opp et nytt krav: du må se hvordan de oppfører seg.

Søkekvalitet, rutingkvalitet, verktøyvalg og konteksteffektivitet blir alle observerbare problemer.

Det er derfor vi mener dette er verdt å skrive om. Den fremtidige agent-stacken er ikke bare prompter og verktøy. Det er prompter, verktøy, og instrumenteringslaget som forteller deg om det hele fungerer.

Hvis du bygger seriøse agentarbeidsflyter, er observability ikke valgfri infrastruktur. Det er en del av produktet.

Relatert lesning

Mer fra Maguyva-byggeloggen