Osservabilità degli agenti: hook, Alloy e Grafana
> Abbiamo collegato Claude Code e Codex a un unico stack Grafana con OpenTelemetry e Alloy, poi abbiamo usato trace e log per trovare e correggere i problemi di comportamento degli agenti alla fonte.
I sistemi ad agenti falliscono in modi strani.
A volte il problema è il modello. A volte è lo strumento. A volte il tuo server MCP funziona benissimo, ma l’agente ha scelto lo specialista sbagliato, oppure ha passato metà della sessione a fare lavoro di shell che non ti aspettavi, oppure ha silenziosamente bruciato budget in un loop che dall’esterno sembrava produttivo.
Se non riesci a vedere la differenza, non stai davvero operando un sistema ad agenti. Stai tirando a indovinare.
Così abbiamo costruito uno stack di observability per il nostro stesso workflow: Claude Code, Codex, eventi hook di Claude, eventi notify di Codex, OpenTelemetry nativo, Grafana Alloy e Grafana Cloud dall’altra parte.
La parte interessante non è “abbiamo fatto una dashboard”. La parte interessante è che abbiamo dovuto dividere la telemetria in due flussi diversi perché nessun singolo feed ci dava il quadro completo.
Il problema: la telemetria degli agenti è frammentata
Gli agenti di coding moderni emettono già una certa telemetria. Aiuta, ma non basta.
L’OTEL nativo è bravo a rispondere a domande come:
- Quante richieste abbiamo fatto?
- Quanto è costata una sessione?
- Dove sono gli span e le trace?
- C’è stato un picco di latenza?
È molto meno bravo a rispondere a domande come:
- Su quale server MCP si è appoggiato l’agente?
- Questo fallimento era in
Bash, in uno strumento file integrato, o in una chiamata MCP? - Quale skill si è effettivamente attivata?
- Quale tipo di subagent è stato dispatchato?
- La sessione stava facendo lavoro utile, o stava solo arrancando a vuoto?
Quella seconda classe di domande vive più vicino agli hook che alle trace.
Ma è vero anche l’inverso: alcune delle domande di performance più importanti vivono più vicino alle trace che agli hook.
Se vuoi sapere dove si è effettivamente accumulata la latenza, quali span erano lenti, o se la sessione ha bruciato tempo nelle chiamate al modello piuttosto che nell’esecuzione degli strumenti, ti servono i dati di trace oltre agli eventi semantici.
L’architettura a cui siamo arrivati
Facciamo girare due percorsi di telemetria in parallelo.
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
Quella divisione è deliberata.
Ed è anche asimmetrica. Claude Code ci offre una superficie di hook di ciclo di vita molto più ricca. Codex ci offre OTEL nativo più una superficie notify, quindi normalizziamo eventi di completamento turno più esili nello stesso schema di log, invece di far finta che entrambi i runtime espongano gli stessi controlli.
L’OTEL nativo ci dà lo stream di base: log e trace dal runtime stesso, più metriche dove il runtime le emette effettivamente.
Gli eventi hook e notify ci danno il layer semantico: cose come PreToolUse, PostToolUse, PostToolUseFailure, UserPromptSubmit, SubagentStop, SkillActivated, e i metadati classificati a cui teniamo davvero quando facciamo debug sul comportamento degli agenti. Claude Code contribuisce qui lo stream di eventi più ricco. Codex contribuisce uno stream normalizzato più esile ma comunque utile.
Perché esistono gli hook
La nostra pipeline di hook arricchisce gli eventi prima che arrivino a Loki.
Invece di dire semplicemente “è girato uno strumento”, classifichiamo l’evento in campi come:
tool_type: builtin, mcp, skill, agent, bashmcp_server: quale backend MCP ha gestito la chiamatabash_cli: la famiglia di comandi shellsubagent_type: che tipo di specialista è stato dispatchatoagent_tool: se la sorgente era Claude Code o Codex
Questo significa che possiamo fare domande che contano operativamente:
{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"
Non sono campi di vanità. Sono la differenza tra “l’agente sembrava lento” e “l’agente ha passato gli ultimi dieci minuti in operazioni git pesanti di shell con un alto tasso di fallimento degli strumenti”.
Un dettaglio implementativo che sembra più strano di quanto sia: lo stream Loki condiviso usa ancora service_name="claude-code-hooks" come label anche quando l’evento arriva da Codex. La vera separazione tra runtime avviene su agent_tool.
Perché Alloy sta nel mezzo
Grafana Alloy in questa configurazione non è solo un forwarder. È il confine delle policy.
Puntiamo lo stream OTEL nativo di Claude Code e Codex verso un proxy Alloy locale su localhost:4318, poi lasciamo che Alloy ripulisca il payload prima di inoltrarlo a Grafana Cloud.
Questo conta perché la telemetria grezza degli agenti è piena di campi ad alta cardinalità che sono utili per l’analisi ma pessimi come label indicizzate:
session_idprompt_id- conteggi di token
- durate
- blob di parametri degli strumenti
Se indicizzi tutto, ottieni un’esplosione di label e una brutta giornata.
Quindi Alloy fa tre cose per noi:
- Mantiene indicizzato un set molto piccolo di label a bassa cardinalità.
- Sposta i campi rumorosi ma utili in metadati strutturati.
- Scarta completamente il rumore puro.
L’idea importante è semplice: osserva di più, indicizza di meno.
Perché lo stream degli hook bypassa Alloy
Lo stream degli hook è già modellato per Loki.
Nel momento in cui send_event.py invia un evento, abbiamo già deciso quali campi meritano il trattamento da label e quali appartengono al corpo JSON strutturato. Quello stream va dritto al gateway OTLP di Grafana Cloud invece di passare di nuovo attraverso Alloy.
Quindi il sistema ha una chiara divisione del lavoro:
- Alloy doma lo stream OTEL nativo grezzo.
- L’arricchimento hook rende gli eventi semantici interrogabili.
Questo mantiene l’architettura più semplice rispetto a cercare di far passare tutto attraverso un unico percorso.
Cosa mostra davvero la dashboard
Lo screenshot qui sotto proviene da una delle dashboard di observability dietro il nostro workflow ad agenti. Non è un benchmark, e i numeri sono solo uno spaccato in un punto nel tempo. Il punto è la forma dei dati: feed di attività, chiamate agli strumenti, fallimenti, prompt, e ripartizioni per agente, strumenti integrati, uso di MCP, comandi shell e skill.
La parte utile è che questa dashboard vive sullo stesso stack Grafana del resto della telemetria degli agenti. Possiamo filtrare per agente sorgente e famiglia di strumenti e guardare attraverso i runtime senza inventare una storia di observability diversa per ogni sistema.
Perché le trace contano più di quanto sembri a prima vista
I log ci dicono che categoria di lavoro è successa. Le trace ci dicono come il lavoro si è svolto nel tempo.
Quella distinzione conta nei sistemi ad agenti perché “lento” è troppo generico per essere utile.
Una trace può dirci se il dolore è arrivato da:
- latenza del modello
- tempo di esecuzione degli strumenti
- retry ripetuti
- un’interazione MCP particolarmente costosa
- una lunga coda di piccole operazioni che sembravano innocue prese isolatamente
In pratica usiamo insieme lo stream degli hook e le trace di Tempo.
- I log degli hook rispondono: che tipo di cosa è successa?
- Le trace rispondono: dove è andato il tempo?
La combinazione è ciò che trasforma l’observability da una dashboard in una spiegazione.
Dove si inserisce Codex
Codex fa parte dello stesso stack, ma non è identico a Claude Code.
Per Codex colleghiamo due elementi:
- OTEL nativo da Codex verso Alloy
- Un webhook notify verso
codex_notify.py, che mappa i completamenti dei turni nello stesso schema Loki che usiamo per gli eventi hook
Questo ci dà un filtro unificato come agent_tool="codex-cli" all’interno dello stesso stream di log.
L’avvertenza onesta: il payload notify di Codex oggi è più esile del payload hook di Claude Code perché non è lo stesso tipo di superficie di integrazione. Nella nostra configurazione attuale, i completamenti dei turni di Codex possono essere normalizzati nello schema condiviso, ma l’estrazione ricca strumento per strumento è ancora migliore nello stream OTEL nativo che nel bridge notify.
Questo non è un motivo per evitare l’articolo. È il punto dell’articolo. I sistemi di observability reali sono assemblati a partire da segnali imperfetti.
Grafana su MCP cambia le regole del gioco
Il cambiamento più grande è che Grafana non è solo un posto che gli umani visitano nel browser.
In questo repo esponiamo anche Grafana tramite MCP. Questo significa che un agente può interrogare Loki, Prometheus e Tempo direttamente, invece di aspettare che un umano ispezioni manualmente le dashboard per primo.
Questo trasforma l’observability in un input attivo per il workflow.
Un agente può chiedere:
- Quali famiglie di strumenti hanno fallito di più nell’ultima ora?
- Quale server MCP ha dominato una sessione?
- Le modifiche recenti hanno ridotto i fallimenti degli strumenti o hanno solo spostato il lavoro su percorsi più pesanti di shell con gli stessi errori?
- Quali trace mostrano la latenza più alta o retry ripetuti?
Una volta che hai questo, sei molto vicino a un loop di auto-miglioramento.
Dalla dashboard al loop di feedback
Questa è la parte che troviamo più interessante.
Una volta che lo stack di observability è interrogabile dal layer degli agenti, la telemetria smette di essere una superficie di reportistica passiva e diventa un segnale di controllo.
Il loop funziona così:
- L’attività dell’agente emette trace, metriche e log hook arricchiti.
- Grafana archivia le prove in Loki, Tempo e Prometheus dove esistono le metriche.
- Gli agenti interrogano quelle prove tramite Grafana MCP.
- Il sistema identifica mix di strumenti scadenti, skill fragili, routing debole, o workflow pesanti di shell che continuano a produrre errori evitabili.
- Agenti o operatori aggiustano prompt, configurazioni degli agenti, descrizioni delle skill, regole di routing, o accesso agli strumenti.
- La sessione successiva produce una nuova forma di telemetria, e il ciclo si ripete.
Così si passa da “dashboard interessante” a “sistema di miglioramento misurabile”.
L’obiettivo non è massimizzare una categoria di strumenti. È arrivare al giusto mix di CLI, strumenti integrati, chiamate MCP e skill per il lavoro effettivamente svolto.
A cosa ci permette di rispondere
Una volta che entrambi i runtime finiscono nello stesso stack Grafana, possiamo rispondere a domande operative molto più velocemente:
- I fallimenti sono concentrati in una famiglia di strumenti?
- I workflow pesanti di shell stanno creando errori evitabili dove dovrebbe esistere uno strumento di livello superiore?
- Quali server MCP stanno portando il carico di lavoro?
- Stiamo pagando per attività di agenti che non producono progressi significativi?
- Una sessione è malsana per colpa del modello, degli strumenti, o del layer di orchestrazione?
Questo è particolarmente utile nei workflow multi-agente, dove “l’agente era impegnato” non ti dice quasi niente.
Se uno specialista continua a essere dispatchato e a produrre alti tassi di fallimento, è un problema di routing o di formulazione dei prompt.
Se un server MCP domina tutte le chiamate, potrebbe essere una buona architettura o un segnale che tutto il resto è peso morto.
Se i fallimenti degli strumenti aumentano mentre il costo resta alto, hai un problema operativo, non un problema di qualità.
Se il lavoro di shell continua a fallire in modi prevedibili ed evitabili dove dovrebbe esistere uno strumento di livello superiore, quello è un segnale di prodotto.
Se una skill si attiva di continuo ma non migliora i risultati, quello è un segnale di prompt o di routing.
La vera lezione
La lezione più profonda qui è che l’observability degli agenti richiede sia telemetria di runtime che telemetria di workflow.
La telemetria di runtime ti dice cosa ha fatto il sistema.
La telemetria di workflow ti dice cosa l’agente pensava di stare facendo.
Ci servono entrambe.
Se tieni solo trace e contatori, perdi il layer semantico. Se tieni solo eventi hook, perdi latenza, span, e il quadro più ampio del runtime.
E se tieni entrambi ma non li reintroduci mai nel layer degli agenti, hai monitoraggio, non adattamento.
La combinazione è ciò che rende il sistema abbastanza spiegabile da poter essere operato e abbastanza regolabile da poter essere migliorato.
Cosa resta imperfetto
Ci sono ancora spigoli da smussare.
- Non tutti gli eventi hook includono i dati di durata e token che vorremmo.
- Alcune delle migliori viste temporali arrivano ancora dalle trace di Tempo, non dai log degli hook.
- Codex oggi è meno ricco semanticamente di Claude Code nello stream di eventi arricchiti.
- Lo screenshot della dashboard è una superficie operativa live, non un artefatto di marketing rifinito.
Quest’ultimo punto è intenzionale. Preferiamo mostrare il vero pannello di controllo piuttosto che far finta che i sistemi ad agenti si spieghino magicamente da soli.
Perché questo conta per Maguyva
Maguyva riguarda dare agli agenti una migliore Code Intelligence. Ma una volta che gli agenti stanno effettivamente facendo lavoro utile, emerge subito un nuovo requisito: devi vedere come si stanno comportando.
Qualità della ricerca, qualità del routing, selezione degli strumenti, ed efficienza del contesto diventano tutti problemi osservabili.
Ecco perché pensiamo valga la pena scriverne. Lo stack degli agenti del futuro non è solo prompt e strumenti. È prompt, strumenti, e il layer di strumentazione che ti dice se tutto l’insieme sta funzionando.
Se stai costruendo workflow ad agenti seri, l’observability non è infrastruttura opzionale. È parte del prodotto.
Letture correlate
Altro dal diario di costruzione di Maguyva
Perché abbiamo aggiornato la ricerca sul codice a voyage-4-large_
Abbiamo spostato i nostri embedding del codice su voyage-4-large — attualmente in cima alla classifica pubblica RTEB per il retrieval di codice. La versione onesta: il compromesso che facciamo, cosa indicizziamo davvero, e perché paghiamo per embedding premium.
Auto-miglioramento ricorsivo dei linguaggi: il grind della Code Intelligence su ~280 linguaggi_
Supportiamo la Code Intelligence per ~280 linguaggi. Nessun essere umano può controllarli a mano uno per uno. Così abbiamo costruito un loop di auto-miglioramento ricorsivo dei linguaggi — campionamento, LLM come giudice, correggi una cosa, rivalida — e lo facciamo girare con una flotta di agenti isolati finché l'estrazione non è davvero corretta, non solo verde.
Ricerca a fusione multi-modale: scegliere il retriever giusto per ogni query_
Una query come 'dove è definito parseConfig' vuole una ricerca diversa da 'come funziona l'auth'. Maguyva classifica l'intento, pesa di conseguenza quattro modalità di retrieval, e fonde i risultati con una Reciprocal Rank Fusion pesata.