Наблюдаемость агентов: хуки, Alloy и Grafana
> Мы подключили Claude Code и Codex к единому стеку Grafana через OpenTelemetry и Alloy, а затем с помощью трасс и логов находили и устраняли проблемы в поведении агентов прямо у источника.
Агентные системы ломаются причудливыми способами.
Иногда проблема в модели. Иногда в инструменте. Иногда с MCP-сервером всё в порядке, но агент выбрал не того специалиста, или потратил половину сессии на неожиданную работу в шелле, или незаметно сжёг бюджет в цикле, который со стороны выглядел продуктивным.
Если вы не видите разницы, вы не по-настоящему управляете агентной системой. Вы гадаете.
Поэтому мы построили стек наблюдаемости для собственного рабочего процесса: Claude Code, Codex, события хуков Claude, события notify Codex, нативный OpenTelemetry, Grafana Alloy и Grafana Cloud на другом конце.
Интересная часть здесь не «мы сделали дашборд». Интересно то, что нам пришлось разделить телеметрию на два разных потока, потому что ни один из них по отдельности не давал полной картины.
Проблема: телеметрия агентов фрагментирована
Современные кодирующие агенты уже излучают некоторую телеметрию. Это помогает, но недостаточно.
Нативный OTEL хорошо отвечает на вопросы вроде:
- Сколько запросов мы сделали?
- Сколько стоила сессия?
- Где спаны и трассы?
- Была ли всплеск задержки?
Он куда хуже отвечает на вопросы вроде:
- На какой MCP-сервер опирался агент?
- Этот сбой произошёл в
Bash, во встроенном файловом инструменте или в MCP-вызове? - Какой скилл реально активировался?
- Какой тип субагента был задействован?
- Сессия занималась полезной работой или просто буксовала?
Этот второй класс вопросов ближе к хукам, чем к трассам.
Но верно и обратное: некоторые из самых важных вопросов о производительности ближе к трассам, чем к хукам.
Если вы хотите знать, где реально накопилась задержка, какие спаны были медленными или тратила ли сессия время на вызовы модели, а не на выполнение инструментов, вам нужны данные трасс не меньше, чем семантические события.
Архитектура, к которой мы пришли
Мы ведём два пути телеметрии параллельно.
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
Это разделение сделано намеренно.
Оно ещё и асимметрично. Claude Code даёт нам куда более богатую поверхность хуков жизненного цикла. Codex даёт нам нативный OTEL плюс поверхность notify, поэтому мы нормализуем более тонкие события завершения хода в ту же схему логов, вместо того чтобы делать вид, будто оба рантайма предоставляют одинаковые рычаги управления.
Нативный OTEL даёт нам базовый поток: логи и трассы от самого рантайма, плюс метрики там, где рантайм их реально излучает.
Хуки и notify-события дают нам семантический слой: такие вещи, как PreToolUse, PostToolUse, PostToolUseFailure, UserPromptSubmit, SubagentStop, SkillActivated, и классифицированные метаданные, которые нас реально волнуют при отладке поведения агента. Claude Code вносит сюда более богатый поток событий. Codex вносит более тонкий, но всё же полезный нормализованный поток.
Зачем вообще нужны хуки
Наш конвейер хуков обогащает события, прежде чем они попадают в Loki.
Вместо того чтобы просто сказать «инструмент выполнился», мы классифицируем событие по полям вроде:
tool_type: builtin, mcp, skill, agent, bashmcp_server: какой MCP-бэкенд обработал вызовbash_cli: семейство команд шеллаsubagent_type: какой тип специалиста был задействованagent_tool: был ли источник Claude Code или Codex
Это значит, что мы можем задавать операционно значимые вопросы:
{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"
Это не показные поля ради красоты. Это разница между «агент показался медленным» и «агент провёл последние десять минут в тяжёлых по шеллу git-операциях с высокой долей сбоев инструментов».
Одна деталь реализации выглядит страннее, чем есть на самом деле: общий поток Loki по-прежнему использует service_name="claude-code-hooks" как метку, даже когда событие пришло от Codex. Реальное разделение между рантаймами происходит на уровне agent_tool.
Почему Alloy находится посередине
Grafana Alloy в этой схеме — не просто пересыльщик. Это граница политики.
Мы направляем нативный поток OTEL от Claude Code и Codex на локальный прокси Alloy на localhost:4318, а затем позволяем Alloy очистить полезную нагрузку перед пересылкой в Grafana Cloud.
Это важно, потому что сырая телеметрия агентов полна полей с высокой кардинальностью, которые полезны для анализа, но ужасны в роли индексируемых меток:
session_idprompt_id- количество токенов
- длительности
- блобы параметров инструментов
Если индексировать всё подряд, получите взрыв меток и плохой день.
Поэтому Alloy делает для нас три вещи:
- Держит индексированным очень небольшой набор меток с низкой кардинальностью.
- Переносит шумные, но полезные поля в структурированные метаданные.
- Полностью отбрасывает чистый шум.
Важная идея проста: наблюдайте больше, индексируйте меньше.
Почему поток хуков обходит Alloy
Поток хуков уже сформирован под Loki.
К тому моменту, когда send_event.py отправляет событие, мы уже решили, какие поля заслуживают статуса метки, а какие принадлежат структурированному телу JSON. Этот поток идёт напрямую в OTLP-шлюз Grafana Cloud, минуя ещё один проход через Alloy.
Так что в системе чёткое разделение труда:
- Alloy укрощает сырой нативный поток OTEL.
- Обогащение хуками делает семантические события пригодными для запросов.
Это делает архитектуру проще, чем попытка протолкнуть всё через один путь.
Что реально показывает дашборд
Скриншот ниже — с одного из дашбордов наблюдаемости за нашим агентным рабочим процессом. Это не бенчмарк, а цифры — просто срез на момент времени. Суть в форме данных: лента активности, вызовы инструментов, сбои, промпты и разбивки по агентам, встроенным инструментам, использованию MCP, командам шелла и скиллам.
Полезная часть в том, что этот дашборд живёт на том же стеке Grafana, что и остальная телеметрия агентов. Мы можем фильтровать по исходному агенту и семейству инструментов и смотреть по всем рантаймам сразу, не изобретая отдельную историю наблюдаемости для каждой системы.
Почему трассы важнее, чем кажется на первый взгляд
Логи говорят нам, к какой категории относилась выполненная работа. Трассы говорят нам, как эта работа разворачивалась во времени.
Это различие важно в агентных системах, потому что «медленно» — слишком тупой инструмент, чтобы быть полезным.
Трасса может сказать нам, откуда взялась боль:
- задержка модели
- время выполнения инструмента
- повторные попытки
- одно особенно дорогое взаимодействие с MCP
- длинный хвост мелких операций, каждая из которых по отдельности выглядела безобидной
На практике мы используем поток хуков и трассы Tempo вместе.
- Логи хуков отвечают: что за событие произошло?
- Трассы отвечают: куда ушло время?
Именно сочетание превращает наблюдаемость из дашборда в объяснение.
Где здесь место Codex
Codex — часть того же стека, но он не идентичен Claude Code.
Для Codex мы подключаем два элемента:
- Нативный OTEL из Codex в Alloy
- Notify-вебхук в
codex_notify.py, который отображает завершения ходов в ту же схему Loki, что мы используем для событий хуков
Это даёт нам единый фильтр вроде agent_tool="codex-cli" внутри одного и того же потока логов.
Честная оговорка: полезная нагрузка notify у Codex сейчас тоньше, чем полезная нагрузка хуков у Claude Code, потому что это не тот же тип поверхности интеграции. В нашей текущей настройке завершения ходов Codex можно нормализовать в общую схему, но детальное, инструмент-за-инструментом извлечение по-прежнему лучше получается в нативном потоке OTEL, чем в notify-мосте.
Это не повод избегать темы. Это и есть суть темы. Настоящие системы наблюдаемости собираются из неидеальных сигналов.
Grafana поверх MCP меняет правила игры
Более крупный сдвиг в том, что Grafana — это не только место, куда люди заходят через браузер.
В этом репозитории мы также предоставляем Grafana через MCP. Это значит, что агент может напрямую делать запросы к Loki, Prometheus и Tempo, вместо того чтобы ждать, пока человек сначала вручную изучит дашборды.
Это превращает наблюдаемость в активный вход в рабочий процесс.
Агент может спросить:
- Какие семейства инструментов чаще всего давали сбой за последний час?
- Какой MCP-сервер доминировал в сессии?
- Снизили ли недавние изменения число сбоев инструментов, или просто перенесли работу в более тяжёлые по шеллу пути с теми же ошибками?
- Какие трассы показывают наибольшую задержку или повторные попытки?
Как только у вас это есть, вы очень близки к циклу самосовершенствования.
От дашборда к контуру обратной связи
Это часть, которую мы находим самой интересной.
Как только стек наблюдаемости становится доступен для запросов со стороны слоя агентов, телеметрия перестаёт быть пассивной поверхностью отчётности и становится управляющим сигналом.
Цикл выглядит так:
- Активность агента порождает трассы, метрики и обогащённые логи хуков.
- Grafana хранит эти свидетельства в Loki, Tempo и Prometheus там, где есть метрики.
- Агенты запрашивают эти свидетельства через Grafana MCP.
- Система выявляет неудачный набор инструментов, хрупкие скиллы, слабую маршрутизацию или тяжёлые по шеллу рабочие процессы, которые постоянно приводят к предотвратимым ошибкам.
- Агенты или операторы корректируют промпты, конфигурации агентов, описания скиллов, правила маршрутизации или доступ к инструментам.
- Следующая сессия порождает новую форму телеметрии, и цикл повторяется.
Именно так вы переходите от «интересного дашборда» к «измеримой системе улучшения».
Цель — не максимизировать одну категорию инструментов. Цель — прийти к правильному сочетанию CLI, встроенных инструментов, MCP-вызовов и скиллов для реально выполняемой работы.
Что это позволяет нам понять
Как только оба рантайма оказываются в одном стеке Grafana, мы можем гораздо быстрее отвечать на операционные вопросы:
- Сосредоточены ли сбои в одном семействе инструментов?
- Создают ли тяжёлые по шеллу рабочие процессы предотвратимые ошибки там, где должен быть инструмент более высокого уровня?
- Какие MCP-серверы несут на себе основную нагрузку?
- Платим ли мы за активность агента, которая не даёт значимого прогресса?
- Нездорова ли сессия из-за модели, инструментов или уровня оркестрации?
Это особенно полезно в мультиагентных рабочих процессах, где фраза «агент был занят» почти ничего не говорит.
Если один специалист постоянно получает задачи и выдаёт высокий процент сбоев — это проблема маршрутизации или формулировки промпта.
Если один MCP-сервер доминирует во всех вызовах, это может быть либо хорошей архитектурой, либо признаком того, что всё остальное — мёртвый груз.
Если сбои инструментов растут, а стоимость остаётся высокой, у вас операционная проблема, а не проблема качества.
Если работа в шелле постоянно ломается предсказуемым, предотвратимым образом там, где должен быть инструмент более высокого уровня, — это продуктовый сигнал.
Если один скилл постоянно активируется, но не улучшает результаты, — это сигнал о промпте или маршрутизации.
Настоящий урок
Более глубокий урок здесь в том, что наблюдаемость агентов нуждается и в телеметрии рантайма, и в телеметрии рабочего процесса.
Телеметрия рантайма говорит вам, что сделала система.
Телеметрия рабочего процесса говорит вам, что, по мнению агента, он делал.
Нам нужны обе.
Если вы храните только трассы и счётчики, вы упускаете семантический слой. Если вы храните только события хуков, вы упускаете задержки, спаны и более широкую картину рантайма.
А если вы храните и то, и другое, но никогда не возвращаете это обратно в слой агентов, у вас мониторинг, а не адаптация.
Именно сочетание делает систему достаточно объяснимой, чтобы ей управлять, и достаточно настраиваемой, чтобы её улучшать.
Что всё ещё несовершенно
Шероховатости остаются.
- Не каждое событие хука включает желаемые данные о длительности и токенах.
- Некоторые из лучших представлений по таймингам по-прежнему берутся из трасс Tempo, а не из логов хуков.
- Codex сегодня семантически беднее Claude Code в обогащённом потоке событий.
- Скриншот дашборда — это живая операционная поверхность, а не отполированный маркетинговый артефакт.
Последний пункт — намеренный. Мы предпочитаем показать настоящую приборную панель, а не делать вид, будто агентные системы волшебным образом самоочевидны.
Почему это важно для Maguyva
Maguyva — про то, чтобы дать агентам лучший интеллектуальный анализ кода. Но как только агенты действительно начинают делать полезную работу, немедленно возникает новое требование: нужно видеть, как они себя ведут.
Качество поиска, качество маршрутизации, выбор инструментов и эффективность использования контекста — всё это становится наблюдаемыми проблемами.
Поэтому мы считаем, что об этом стоит писать. Будущий агентный стек — это не только промпты и инструменты. Это промпты, инструменты и слой инструментирования, который говорит вам, работает ли всё это вместе.
Если вы строите серьёзные агентные рабочие процессы, наблюдаемость — не опциональная инфраструктура. Это часть продукта.
Похожие материалы
Ещё из журнала разработки Maguyva
Почему мы обновили поиск по коду до voyage-4-large_
Мы перевели эмбеддинги кода на voyage-4-large — модель, которая сейчас возглавляет публичный рейтинг RTEB по поиску кода. Честная версия: какой компромисс мы принимаем, что мы на самом деле индексируем и почему платим за премиальные эмбеддинги.
Рекурсивное самосовершенствование языков: шлифуем интеллектуальный анализ кода на ~280 языках_
Мы поддерживаем интеллектуальный анализ кода для ~280 языков. Ни один человек не может вручную проверить это. Поэтому мы построили цикл рекурсивного самосовершенствования языков — выборочная проверка, LLM в роли судьи, исправление одной вещи, повторная валидация — и прогоняем его флотом изолированных агентов, пока извлечение не станет по-настоящему верным, а не просто «зелёным».
Мультимодальный фьюжн-поиск: выбор правильного ретривера для каждого запроса_
Запрос вроде «где определён parseConfig» требует другого поиска, чем «как работает аутентификация». Maguyva классифицирует намерение, соответствующим образом взвешивает четыре модальности поиска и сливает результаты с помощью взвешенного Reciprocal Rank Fusion.