Passer au contenu
cd /blog

Observabilité des agents : hooks, Alloy et Grafana

[Observabilité][Grafana][OpenTelemetry][Architecture]

> Nous avons relié Claude Code et Codex à une seule pile Grafana avec OpenTelemetry et Alloy, puis utilisé traces et logs pour repérer et corriger les problèmes de comportement des agents à la source.

Les systèmes d’agents échouent de façons étranges.

Parfois, c’est le modèle qui pose problème. Parfois, c’est l’outil. Parfois votre serveur MCP fonctionne très bien, mais l’agent a choisi le mauvais spécialiste, ou a passé la moitié de la session à faire du travail shell que vous n’attendiez pas, ou a silencieusement brûlé du budget dans une boucle qui paraissait productive de l’extérieur.

Si vous ne pouvez pas voir la différence, vous n’opérez pas vraiment un système d’agents. Vous devinez.

Nous avons donc construit une pile d’observabilité pour notre propre flux de travail : Claude Code, Codex, les événements de hooks Claude, les événements de notification Codex, OpenTelemetry natif, Grafana Alloy, et Grafana Cloud de l’autre côté.

La partie intéressante n’est pas « nous avons créé un tableau de bord ». La partie intéressante est que nous avons dû scinder la télémétrie en deux flux distincts, parce qu’aucune source unique ne nous donnait la vue d’ensemble.

Le problème : la télémétrie des agents est fragmentée

Les agents de codage modernes émettent déjà une certaine télémétrie. C’est utile, mais insuffisant.

OTEL natif répond bien à des questions comme :

  • Combien de requêtes avons-nous effectuées ?
  • Combien a coûté une session ?
  • Où se trouvent les spans et les traces ?
  • La latence a-t-elle connu un pic ?

Il répond beaucoup moins bien à des questions comme :

  • Sur quel serveur MCP l’agent s’est-il appuyé ?
  • Cet échec s’est-il produit dans Bash, un outil de fichiers intégré, ou un appel MCP ?
  • Quelle compétence s’est réellement activée ?
  • Quel type de sous-agent a été dépêché ?
  • La session accomplissait-elle un travail utile, ou se débattait-elle simplement ?

Cette seconde catégorie de questions relève davantage des hooks que des traces.

Mais l’inverse est vrai aussi : certaines des questions de performance les plus importantes relèvent davantage des traces que des hooks.

Si vous voulez savoir où la latence s’est réellement accumulée, quels spans étaient lents, ou si la session a passé du temps dans des appels de modèle plutôt que dans l’exécution d’outils, vous avez besoin de données de trace autant que d’événements sémantiques.

L’architecture à laquelle nous avons abouti

Nous faisons tourner deux chemins de télémétrie en parallèle.

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

Cette scission est délibérée.

Elle est aussi asymétrique. Claude Code nous offre une surface de hooks de cycle de vie beaucoup plus riche. Codex nous offre de l’OTEL natif plus une surface de notification, donc nous normalisons des événements de fin de tour plus légers vers le même schéma de log, plutôt que de prétendre que les deux runtimes exposent les mêmes contrôles.

L’OTEL natif nous donne le flux de référence : logs et traces issus du runtime lui-même, plus des métriques là où le runtime les émet réellement.

Les événements de hooks et de notification nous donnent la couche sémantique : des éléments comme PreToolUse, PostToolUse, PostToolUseFailure, UserPromptSubmit, SubagentStop, SkillActivated, et les métadonnées classifiées qui nous intéressent réellement lors du débogage du comportement des agents. Claude Code apporte ici le flux d’événements le plus riche. Codex apporte un flux normalisé plus léger, mais tout de même utile.

Pourquoi les hooks existent tout court

Notre pipeline de hooks enrichit les événements avant qu’ils n’atteignent Loki.

Au lieu de simplement dire « un outil s’est exécuté », nous classons l’événement dans des champs comme :

  • tool_type : builtin, mcp, skill, agent, bash
  • mcp_server : quel backend MCP a traité l’appel
  • bash_cli : la famille de commande shell
  • subagent_type : quel type de spécialiste a été dépêché
  • agent_tool : si la source était Claude Code ou Codex

Cela signifie que nous pouvons poser des questions qui comptent sur le plan opérationnel :

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

Ce ne sont pas des champs de façade. Ils font toute la différence entre « l’agent semblait lent » et « l’agent a passé les dix dernières minutes dans des opérations git riches en commandes shell, avec un taux d’échec d’outils élevé ».

Un détail d’implémentation qui paraît plus étrange qu’il ne l’est vraiment : le flux Loki partagé utilise toujours service_name="claude-code-hooks" comme label, même quand l’événement provient de Codex. La véritable scission entre les runtimes se fait sur agent_tool.

Pourquoi Alloy se trouve au milieu

Dans ce dispositif, Grafana Alloy n’est pas un simple relais. C’est la frontière de politique.

Nous dirigeons le flux OTEL natif de Claude Code et Codex vers un proxy Alloy local sur localhost:4318, puis laissons Alloy nettoyer la charge utile avant de la transmettre à Grafana Cloud.

Cela compte, car la télémétrie brute des agents regorge de champs à forte cardinalité, utiles pour l’analyse mais désastreux comme labels indexés :

  • session_id
  • prompt_id
  • les comptages de tokens
  • les durées
  • les blobs de paramètres d’outils

Si vous indexez tout, vous obtenez une explosion de labels et une mauvaise journée.

Alloy fait donc trois choses pour nous :

  1. Il maintient indexé un tout petit ensemble de labels à faible cardinalité.
  2. Il déplace les champs bruyants mais utiles vers des métadonnées structurées.
  3. Il élimine purement et simplement le bruit pur.

L’idée importante est simple : observer davantage, indexer moins.

Pourquoi le flux de hooks contourne Alloy

Le flux de hooks est déjà façonné pour Loki.

Au moment où send_event.py pousse un événement, nous avons déjà décidé quels champs méritent un traitement en label et lesquels appartiennent au corps JSON structuré. Ce flux part directement vers la passerelle OTLP de Grafana Cloud, sans repasser par Alloy.

Le système présente donc une répartition claire des tâches :

  • Alloy dompte le flux OTEL natif brut.
  • L’enrichissement par hooks rend les événements sémantiques interrogeables.

Cela garde l’architecture plus simple que si l’on tentait de tout faire passer par un seul chemin.

Ce que montre réellement le tableau de bord

La capture d’écran ci-dessous provient de l’un des tableaux de bord d’observabilité qui sous-tendent notre flux de travail d’agents. Ce n’est pas un benchmark, et les chiffres ne sont qu’un instantané à un moment donné. Ce qui compte, c’est la forme des données : flux d’activité, appels d’outils, échecs, prompts, et répartitions par agent, outils intégrés, usage MCP, commandes shell et compétences.

Ce qui est utile, c’est que ce tableau de bord vit sur la même pile Grafana que le reste de la télémétrie des agents. Nous pouvons filtrer par agent source et famille d’outils, et regarder à travers les runtimes sans inventer une histoire d’observabilité différente pour chaque système.

Tableau de bord Grafana montrant un flux d'activité, le nombre d'appels d'outils, les échecs, les prompts, et des répartitions par agents, outils intégrés, usage MCP, commandes CLI et compétences pour un flux de travail d'agent.
L'un des tableaux de bord en direct qui sous-tendent notre flux de travail d'agents. Cette capture d'écran n'est qu'une tranche d'une pile Grafana partagée qui reçoit aussi la télémétrie de nos autres runtimes et systèmes d'agents. Cliquez sur l'image pour la version en pleine résolution.

Pourquoi les traces comptent plus qu’il n’y paraît au premier abord

Les logs nous disent quelle catégorie de travail a eu lieu. Les traces nous disent comment ce travail s’est déroulé dans le temps.

Cette distinction compte dans les systèmes d’agents, parce que « lent » est un terme trop grossier pour être utile.

Une trace peut nous dire si la douleur venait :

  • de la latence du modèle
  • du temps d’exécution des outils
  • de tentatives répétées
  • d’une interaction MCP particulièrement coûteuse
  • d’une longue traîne de petites opérations qui paraissaient inoffensives isolément

En pratique, nous utilisons ensemble le flux de hooks et les traces Tempo.

  • Les logs de hooks répondent : quel genre de chose s’est produit ?
  • Les traces répondent : où le temps est-il passé ?

Cette combinaison est ce qui transforme l’observabilité d’un tableau de bord en une explication.

Où se situe Codex

Codex fait partie de la même pile, mais il n’est pas identique à Claude Code.

Pour Codex, nous relions deux éléments :

  • L’OTEL natif de Codex vers Alloy
  • Un webhook de notification vers codex_notify.py, qui fait correspondre les fins de tour au même schéma Loki que celui utilisé pour les événements de hooks

Cela nous donne un filtre unifié comme agent_tool="codex-cli" à l’intérieur du même flux de logs.

La réserve honnête : la charge utile de notification de Codex est aujourd’hui plus légère que la charge utile de hooks de Claude Code, car ce n’est pas le même type de surface d’intégration. Dans notre configuration actuelle, les fins de tour de Codex peuvent être normalisées dans le schéma partagé, mais une extraction riche outil par outil reste meilleure dans le flux OTEL natif que dans le pont de notification.

Ce n’est pas une raison d’éviter le sujet. C’est justement le propos de cet article. Les vrais systèmes d’observabilité s’assemblent à partir de signaux imparfaits.

Grafana au-dessus de MCP change la donne

Le changement le plus important est que Grafana n’est plus seulement un lieu que les humains visitent dans un navigateur.

Dans ce dépôt, nous exposons aussi Grafana via MCP. Cela signifie qu’un agent peut interroger Loki, Prometheus et Tempo directement, au lieu d’attendre qu’un humain inspecte d’abord manuellement les tableaux de bord.

Cela transforme l’observabilité en une entrée active du flux de travail.

Un agent peut demander :

  • Quelles familles d’outils ont le plus échoué au cours de la dernière heure ?
  • Quel serveur MCP a dominé une session ?
  • Les changements récents ont-ils réduit les échecs d’outils, ou simplement déplacé le travail vers des chemins plus riches en shell avec les mêmes erreurs ?
  • Quelles traces montrent la latence la plus élevée ou des tentatives répétées ?

Une fois que vous avez cela, vous êtes très proche d’une boucle d’auto-amélioration.

Du tableau de bord à la boucle de rétroaction

C’est la partie que nous trouvons la plus intéressante.

Une fois que la pile d’observabilité devient interrogeable depuis la couche agent, la télémétrie cesse d’être une surface de reporting passive et devient un signal de contrôle.

La boucle ressemble à ceci :

  1. L’activité des agents émet des traces, des métriques et des logs de hooks enrichis.
  2. Grafana stocke les preuves dans Loki, Tempo et Prometheus, là où les métriques existent.
  3. Les agents interrogent ces preuves via Grafana MCP.
  4. Le système identifie un mauvais mélange d’outils, des compétences fragiles, un routage faible, ou des flux de travail riches en shell qui continuent de produire des erreurs évitables.
  5. Les agents ou les opérateurs ajustent les prompts, les configurations d’agents, les descriptions de compétences, les règles de routage ou les accès aux outils.
  6. La session suivante produit une nouvelle forme de télémétrie, et le cycle se répète.

C’est ainsi que l’on passe d’un « tableau de bord intéressant » à un « système d’amélioration mesurable ».

L’objectif n’est pas de maximiser une seule catégorie d’outils. C’est de trouver le bon mélange de CLI, d’outils intégrés, d’appels MCP et de compétences pour le travail réellement effectué.

Ce que cela nous permet de répondre

Une fois que les deux runtimes atterrissent dans la même pile Grafana, nous pouvons répondre bien plus vite à des questions opérationnelles :

  • Les échecs sont-ils concentrés dans une seule famille d’outils ?
  • Les flux de travail riches en shell créent-ils des erreurs évitables là où un outil de plus haut niveau devrait exister ?
  • Quels serveurs MCP portent la charge de travail ?
  • Payons-nous pour une activité d’agent qui ne produit pas de progrès significatif ?
  • Une session est-elle malsaine à cause du modèle, des outils, ou de la couche d’orchestration ?

C’est particulièrement utile dans les flux de travail multi-agents, où « l’agent était occupé » ne vous apprend presque rien.

Si un spécialiste continue d’être dépêché et produit des taux d’échec élevés, c’est un problème de routage ou de façonnage des prompts.

Si un seul serveur MCP domine tous les appels, cela peut être une bonne architecture, ou un signe que tout le reste est du poids mort.

Si les échecs d’outils grimpent en flèche pendant que le coût reste élevé, vous avez un problème opérationnel, pas un problème de qualité.

Si le travail shell continue d’échouer de façon prévisible et évitable là où un outil de plus haut niveau devrait exister, c’est un signal produit.

Si une compétence s’active constamment sans améliorer les résultats, c’est un signal de prompt ou de routage.

La véritable leçon

La leçon la plus profonde ici est que l’observabilité des agents a besoin à la fois de télémétrie runtime et de télémétrie de flux de travail.

La télémétrie runtime vous dit ce que le système a fait.

La télémétrie de flux de travail vous dit ce que l’agent pensait être en train de faire.

Nous avons besoin des deux.

Si vous ne gardez que les traces et les compteurs, vous manquez la couche sémantique. Si vous ne gardez que les événements de hooks, vous manquez la latence, les spans et la vue d’ensemble du runtime.

Et si vous gardez les deux sans jamais les reboucler vers la couche agent, vous avez de la surveillance, pas de l’adaptation.

C’est cette combinaison qui rend le système suffisamment explicable pour être opéré, et suffisamment réglable pour être amélioré.

Ce qui reste imparfait

Il subsiste des aspérités.

  • Tous les événements de hooks n’incluent pas les données de durée et de tokens que nous souhaiterions.
  • Certaines des meilleures vues de chronométrage viennent encore des traces Tempo, pas des logs de hooks.
  • Codex est aujourd’hui moins riche sémantiquement que Claude Code dans le flux d’événements enrichi.
  • La capture d’écran du tableau de bord est une surface d’exploitation en direct, pas un artefact marketing poli.

Ce dernier point est volontaire. Nous préférons montrer le véritable tableau de bord de pilotage plutôt que de prétendre que les systèmes d’agents s’expliquent magiquement d’eux-mêmes.

Pourquoi cela compte pour Maguyva

Maguyva a pour vocation de donner aux agents une meilleure intelligence du code. Mais une fois que les agents accomplissent réellement un travail utile, une nouvelle exigence apparaît immédiatement : il faut voir comment ils se comportent.

La qualité de la recherche, la qualité du routage, la sélection des outils et l’efficacité du contexte deviennent tous des problèmes observables.

C’est pourquoi nous pensons que cela vaut la peine d’en parler. La pile d’agents de demain n’est pas seulement faite de prompts et d’outils. C’est faite de prompts, d’outils, et de la couche d’instrumentation qui vous dit si l’ensemble fonctionne réellement.

Si vous construisez des flux de travail d’agents sérieux, l’observabilité n’est pas une infrastructure optionnelle. Elle fait partie du produit.

Lectures associées

Encore plus du journal de bord Maguyva