заблокировать hooks: в ~/.hermes/config.yaml, указанный на шелл-скриптах
CLI + шлюз
Подключаемые скрипты для блокировки, автоформатирования, контекста обслуживания
Все три системы неблокирующие — ошибки в любом хуке перехватываются и регистрируются, никогда не приводят к сбою агента.
Хуки шлюза (перехватчики событий шлюза)
Хуки шлюза отключаются автоматически во время работы шлюза (Telegram, Discord, Slack, WhatsApp, Teams), не блокируя основной конвейер агента.
Создание хука
Каждый хук представляет собой каталог в ~/.hermes/hooks/, состоящий из двух файлов:
~/.hermes/hooks/
└── my-hook/
├── HOOK.yaml # Объявляет, какие события прослушивать
└── handler.py # Функция-обработчик на Python
КРЮК.yaml
name:my-hookdescription:Логировать всю активность агента в файлevents:-agent:start-agent:end-agent:step
Список событий определяет, какие события активируют вашего обработчика. Вы можете подписаться на любые события, включая шаблоны вроде command:*.
обработчик.py
importjsonfromdatetimeimportdatetimefrompathlibimportPathLOG_FILE=Path.home()/".hermes"/"hooks"/"my-hook"/"activity.log"asyncdefhandle(event_type:str,context:dict):"""Вызывается для каждого подписанного события. Имя функции должно быть 'handle'."""entry={"timestamp":datetime.now().isoformat(),"event":event_type,**context,}withopen(LOG_FILE,"a")asf:f.write(json.dumps(entry)+"\n")
Правила для обработчиков:
- Должен называться ручка
- Получает event_type (строка) и context (словарь)
- Может быть async def или обычный def — оба работают
- Ошибки перехватываются и регистрируются, никогда не приводя к сбою агента
Обработчики, зарегистрированные для command:*, срабатывают для любых событий command: (command:model, command:reset и т.д.). Мониторинг всех слэш-команд с одной подпиской.
Примеры
Оповещение в Telegram о длительных задачах
Отправьте себе сообщение, когда агент выполнит более 10 шагов:
# ~/.hermes/hooks/long-task-alert/HOOK.yamlname:long-task-alertdescription:Оповещение, когда агент выполняет много шаговevents:-agent:step
# ~/.hermes/hooks/long-task-alert/handler.pyimportosimporthttpxTHRESHOLD=10BOT_TOKEN=os.getenv("TELEGRAM_BOT_TOKEN")CHAT_ID=os.getenv("TELEGRAM_HOME_CHANNEL")asyncdefhandle(event_type:str,context:dict):iteration=context.get("iteration",0)ifiteration==THRESHOLDandBOT_TOKENandCHAT_ID:tools=", ".join(context.get("tool_names",[]))text=f"⚠️ Агент работает уже {iteration} шагов. Последние инструменты: {tools}"asyncwithhttpx.AsyncClient()asclient:awaitclient.post(f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage",json={"chat_id":CHAT_ID,"text":text},)
Логирование использования команды
Отслеживайте, какие слэш-команды использовались:
# ~/.hermes/hooks/command-logger/HOOK.yamlname:command-loggerdescription:Логирование использования слэш-командevents:-command:*
Отправка POST во внешний сервис при создании новой сессии:
# ~/.hermes/hooks/session-webhook/HOOK.yamlname:session-webhookdescription:Уведомлять внешний сервис о новых сессияхevents:-session:start-session:reset
Учебное пособие: BOOT.md — Запуск стартового чек-листа при каждом компоненте шлюза
Популярный шаблон в сообществе: положите markdown-check-list в ~/.hermes/BOOT.md, и агент будет выполнять его один раз при каждом запуске шлюза. Полезно для «при каждом запуске проверять ночные сбои cron и пинговать меня в Discord, если что-то упало» или «суммировать последние 24 деплоя часа.log и публиковать в Slack #ops».
Этот урок покажет, как построить это самостоятельно в виде пользовательского хука. Hermes не требует подключения к хуком BOOT.md — вы настраиваете именно то поведение, которое вам нужно.
Что мы строим
Файл ~/.hermes/BOOT.md, созданный для запуска на естественном языке.
Хук шлюза, который работает на gateway:startup, распределяет блокчейн одноразового агента с разрешённой моделью/учётными данными вашего шлюза и выполняет инструкции из BOOT.md.
Соглашение [SILENT], чтобы агент мог отправлять сообщения, если нечего сообщать.
Шаг 1: Напишите свой чек-лист
создайте ~/.hermes/BOOT.md. Пишите так, как если бы вы дали инструкцию человеку-помощнику:
# Стартовый чек-лист1. Выполни `hermes cron list` и проверь, не упали ли какие-либо запланированные задачи за ночь.
2. Если какие-то упали, отправь сводку в Discord #ops с помощью инструмента `send_message`.
3. Проверь, есть ли в `/opt/app/deploy.log` строки ERROR за последние 24 часа. Если да, суммируй их и включи в то же сообщение в Discord.
4. Если ничего не пошло не так, ответь только `[SILENT]`, чтобы сообщение не отправлялось.
Агент видит это как часть своего промпта, так что работает всё, что можно описать на естественном языке — вызовы инструментов, шелл-команды, отправка сообщений, суммирование файлов.
name:boot-mddescription:Выполнять ~/.hermes/BOOT.md при запуске шлюзаevents:-gateway:startup
~/.hermes/hooks/boot-md/handler.py
"""Выполняет ~/.hermes/BOOT.md при каждом запуске шлюза."""importloggingimportthreadingfrompathlibimportPathlogger=logging.getLogger("hooks.boot-md")BOOT_FILE=Path.home()/".hermes"/"BOOT.md"def_build_prompt(content:str)->str:return("Вы выполняете стартовый чек-лист. Следуйте приведённым ниже инструкциям ""точно.\n\n""---\n"f"{content}\n""---\n\n""Выполните каждую инструкцию. Используйте инструмент send_message для доставки ""любых сообщений на платформы, такие как Discord или Slack.\n""Если ничего не требует внимания и нечего сообщать, ответьте ""ТОЛЬКО: [SILENT]")def_run_boot_agent(content:str)->None:"""Порождает одноразового агента и выполняет чек-лист. Использует разрешённую модель шлюза и учётные данные времени выполнения, так что это работает с пользовательскими конечными точками, агрегаторами и провайдерами на основе OAuth. """try:fromgateway.runimport_resolve_gateway_model,_resolve_runtime_agent_kwargsfromrun_agentimportAIAgentagent=AIAgent(model=_resolve_gateway_model(),**_resolve_runtime_agent_kwargs(),platform="gateway",quiet_mode=True,skip_context_files=True,skip_memory=True,max_iterations=20,)result=agent.run_conversation(_build_prompt(content))response=result.get("final_response","")ifresponseand"[SILENT]"notinresponse:logger.info("boot-md завершён: %s",response[:200])else:logger.info("boot-md завершён (нечего сообщать)")exceptExceptionase:logger.error("Ошибка агента boot-md: %s",e)asyncdefhandle(event_type:str,context:dict)->None:ifnotBOOT_FILE.exists():returncontent=BOOT_FILE.read_text(encoding="utf-8").strip()ifnotcontent:returnlogger.info("Выполнение BOOT.md (%d символов)",len(content))# Фоновый поток, чтобы запуск шлюза не блокировался полным оборотом агента.thread=threading.Thread(target=_run_boot_agent,args=(content,),name="boot-md",daemon=True,)thread.start()
Две ключевые строки:
_resolve_gateway_model() читает текущую настроенную модель шлюза.
_resolve_runtime_agent_kwargs() разрешает провайдеру учётных данных точно так же, как это делает обычный шаг шлюза — включая API-ключи, базовые URL-адреса, OAuth-токены и пулы учётных данных.
Без них простой AIAgent() будет использовать встроенные значения по умолчанию и получать 401 при подключении к любой нестандартной конечной точке.
Шаг 3: Протестируйте
Перезапустите шлюз:
hermesgatewayrestart
Следите за логами:
hermeslogs--follow--levelINFO|grepboot-md
Вы должны увидеть Выполнение BOOT.md (N символов), а затем либо boot-md завершён:... (сводка того, что агент сделал), либо boot-md завершён (нечего сообщать), когда агент ответил [SILENT].
Удалите ~/.hermes/BOOT.md, чтобы отключить чек-лист — хук остается загруженным, но молча пропускает выполнение, когда файла нет.
Расширение узора
Чек-листы с учётом расписания: воспользуйтесь datetime.now().weekday() внутри инструкций BOOT.md ("если понедельник, также проверьте еженедельный журнал развёртывания"). Инструкции — это свободный текст, так что агент может рассуждать о чем угодно.
Несколько чек-листов: укажите хук на другом файле (STARTUP.md, MORNING.md и т.д.) и зарегистрируйте файл каталога хуков для каждого.
Вариант без агента: если вам не нужен полный цикл агента, пропустите AIAgent и пусть обработчик отправляет фиксированное напрямую через httpx. Дешевле, быстрее и не зависит от провайдера.
Почему это не встроенная функция
Ранняя версия Hermes поставлялась с этим протоколом хуком и молчала агентом блокчейна с базовыми настройками при каждом запуске шлюза. Это удивляло пользователей с пользовательскими конечными точками и делало функцию невидимой для тех, кто не знал, что она работает. Сохранение этого как документированного шаблона — который вы строите сами в вашем каталоге хуков — означает, что вы видите, что именно он делает, и соглашаетесь, записывая файлы.
Как это работает
При запуске шлюза HookRegistry.discover_and_load() сканирует ~/.hermes/hooks/
Каждый подкаталог с HOOK.yaml + handler.py загружается.
Обработчики регистрируются для объявленных мероприятий.
В каждой точке рабочего цикла hooks.emit() запускаются все подходящие обработчики.
Ошибки в любом обработчике перехватываются и регистрируются — сломанный хук никогда не возникает у агента:::информация
Хуки шлюза срабатывает только в шлюзе (Telegram, Discord, Slack, WhatsApp, Teams). CLI не загружает хуки шлюза. Для хуков, которые работают везде, воспользуйтесь хуки плагинов.
Хуки плагинов (Хуки плагинов)
Плагины могут регистрировать хуки, которые срабатывают в сессиях как CLI, так и шлюза. Они регистрируются программно через ctx.register_hook() в функции register() вашего плагина.
Обратные вызовы получают именные аргументы. Всегда принимайте **kwargs для обеспечения прямой совместимости — новые параметры могут быть добавлены в последующих версиях без нарушения работы вашего плагина.
Если обратный вызов вызывает ошибку, она регистрируется и разрешается. Другие хуки и агент продолжают нормальную работу. Неисправный штекер никогда не может сломать агента.
Возвращаемые значения двух хуков влияют на поведение: pre_tool_call может блокировать инструмент, а pre_llm_call может внедрять контекст в вызове LLM. Все остальные хуки - наблюдатели типа "забыл и пошел дальше".
Имя инструмента, который будет выполнен (например, "terminal", "web_search", "read_file")
аргументы
дикт
Аргументы, которые модель передала инструменту
task_id
ул
Идентификатор сессии/задачи. Пустая строка, если не задано.
С реализация: В model_tools.py, внутри handle_function_call(), до выполнения обработки инструмента. Выполняется один раз на каждом вызове инструмента — если модель содержит 3 инструмента одновременно, этот хук выполняется 3 раза.
Агент прерывает выполнение инструмента, возвращая «сообщение» в качестве модели ошибки. Побеждает первую подступающую директиву блокировки (сначала регистрируются плагины Python, затем шелл-хуки). Любое другое возвращаемое значение теряется, поэтому обратные вызовы возникают только для наблюдения, работа продолжается без изменений.
Варианты использования: Логирование, аудит, инструменты счетчиков вызовов, блокировка операций с символами, ограничение скорости, применение политики для каждого пользователя.
Возвращаемое значение инструмента (всегда строки JSON)
task_id
ул
Идентификатор сессии/задачи. Пустая строка, если не задано.
длительность_мс
интервал
Сколько времени занял инструмент диспетчеризации, в миллисекундах (измеряется с помощью time.monotonic() вокруг registry.dispatch()).
С реализации: В model_tools.py, внутри handle_function_call(), после возврата обработчика инструмента. Выполняется один раз на каждом вызове инструмента. Не среализован, если инструмент вызвал необработанное выражение (ошибка перехватывается и возвращается как строка JSON с ошибкой, а post_tool_call выполняется с этой строковой ошибкой в качестве result).
Возвращаемое значение: Игнорируется.
Варианты использования: Инструменты логирования результатов, сбор метрик, отслеживание успехов/неудачных инструментов, задержек панели, оповещения о бюджете для каждого инструмента, отправка сообщений при завершении определенных инструментов.
Пример — отслеживание метрики использования инструментов:
Выполняется один раз за шаг, до начала цикла вызова инструментов. Это единственный хук, где используется возвращаемое значение — он может включать контекст в сообщение пользователя обычного шага.
Исходное сообщение пользователя для этого шага (обеспечение из навыков)
история_разговора
список
Копия полного списка сообщений (формат OpenAI: [{"role": "user", "content": "..."}])
is_first_turn
бул
True, если это первый шаг новой сессии, False для измерения шагов
модель
ул
Идентификатор модели (например, "anthropic/claude-sonnet-4.6")
платформа
ул
Где заслуга сессии: "cli", "telegram", "discord" и т.д.
Сбывается: в run_agent.py, внутри run_conversation(), после сжатия контекста, но до базового цикла while. Осуществляется один раз за вызов run_conversation() (т.е. один раз за шаг пользователя), а не один раз за вызов API внутри программных инструментов.
Возвращаемое значение: Если обратный вызов вызывает с ключом "context" или простую непустую фразу, текст добавляется к сообщению словаря пользователя текущего шага. Верните «Нет» для обеспечения ремонта.
# Внедрить контекстreturn{"context":"Вспомненные воспоминания:\n- Пользователь любит Python\n- Работает над hermes-agent"}# Простая строка (эквивалентно)return"Вспомненные воспоминания:\n- Пользователь любит Python"# Без внедренияreturnNone
Куда вводится контекст: Всегда сообщение пользователя, никогда не системный запрос. Это сохраняет кэш-промптов — системное приглашение остается неизменным между шагами, поэтому кэшированные токены используются повторно. Системный запрос — это территория Hermes (инструкции по моделям, необходимое использование инструментов, личность, навыки). Плагины носят контекст вместе с вводом пользователя.
Весь внедрённый контекст является эфемерным — добавляется только во время вызова API. Исходное сообщение пользователя в истории разговора никогда не меняется, и ничего не сохраняется в базе данных сессии.
Когда несколько плагинов возвращают контекст, их выводы объединяются с последовательностями переводов строк в порядке поиска плагинов (по алфавиту имени каталога).
Варианты использования: Извлечение из памяти, внедрение контекста RAG, ограничения по длине, аналитика по шагам.
POLICY="Никогда не выполнять команды, удаляющие файлы, без явного подтверждения пользователя."defguardrails(**kwargs):return{"context":POLICY}defregister(ctx):ctx.register_hook("pre_llm_call",guardrails)
post_llm_call
Осуществляется один раз за шаг, после завершения вызова инструментов и получения агентом окончательного ответа. Среализовать только при успешных шагах — не реализовать, если шаг был прерван.
Копия полного списка сообщений после завершения шага
модель
ул
Идентификатор модели
платформа
ул
Где эффектная сессия
Сводится: В run_agent.py, внутри run_conversation(), после выхода из инструментария с окончательным ответом. Выполнены условия if Final_response and not Breaked — так что не сработает, когда пользователь прерывает выполнение на середине или агент достигает лимита итераций без ответа.
Возвращаемое значение: Игнорируется.
Варианты использования: Синхронизация разговоров данных с внешней памятью системы, вычисление метрик качества ответов, регистрация последовательных шагов, запуск действий.
С реализации один раз, когда создаётся совершенно новая сессия. Не сбывает при продолжении сессии (когда пользователь отправляет второе сообщение в параллельной сессии).
Сбывается: В run_agent.py, внутри run_conversation(), во время первого шага новой сессии — конкретно после построения системного запроса, но до запуска инструментов цикла. Проверка: если не разговор_история (нет предыдущих сообщений = новая сессия).
Возвращаемое значение: Игнорируется.
Варианты использования: Инициализация состояния, ограниченного сеанса, прогрев кэшей, регистрация сеанса во внешнем сервисе, регистрация начала сеанса.
Выполняется в самом конце каждого вызова run_conversation(), независимо от результата. Также с реализацией обработки вывода CLI, если агент был на середине шага, когда пользователь был отключен.
True, если агент получил окончательный ответ, False в противном случае
прерванный
бул
True, если шаг был прерван (пользователь отправил новое сообщение, /stop или отключен)
модель
ул
Идентификатор модели
платформа
ул
Где эффектная сессия
Реализация: В двух точках:
1. run_agent.py — в конце каждого вызова run_conversation(), после всей очистки. Всегда выполняется, даже если этап завершается неудачей.
2. cli.py — в обработчике atexit CLI, но только, если агент находился на среднем этапе (_agent_running=True) в момент вывода. Это перехватывает Ctrl+C и /exit во время обработки. В этом случае completed=False и interrupted=True.
Возвращаемое значение: Игнорируется.
Варианты использования: Сброс буферов, закрытие соединений, сохранение состояния сеанса, регистрация длительности сеанса, очистка ресурсов, инициализированных в on_session_start.
Пример — сброс и очистка:
_session_caches={}defcleanup_session(session_id,completed,interrupted,**kwargs):cache=_session_caches.pop(session_id,None)ifcache:# Сброс накопленных данных на диск или во внешний сервисstatus="завершена"ifcompletedelse("прервана"ifinterruptedelse"сбой")print(f"Сессия {session_id} завершена: {status}, {cache['tool_calls']} вызовов инструментов")defregister(ctx):ctx.register_hook("on_session_end",cleanup_session)
С внедрением, когда CLI или шлюз завершает активную сессию — например, когда пользователь запускает /new, шлюз собирает виртуальную сессию или CLI завершает работу с активным агентом. Это последний шанс сбросить состояние, судебное решение о завершении сессии, до того, как ваш идентификатор исчезнет.
Идентификатор завершаемой сессии. Может быть None, если активная сессия не была.
платформа
ул
"cli" или имя платформы обмена сообщениями ("telegram", "discord" и т.д.).
С реализуется: в cli.py (при /new / выводе из CLI) и gateway/run.py (при сбросе или сборке сессии). Всегда парно с on_session_reset на стороне шлюза.
Возвращаемое значение: Игнорируется.
Варианты использования: сохранить сохраненные метрики сеанса до того, как идентификатор сессии будет отброшен, закрыть ресурс, защитить ресурсы к сессии, отправить открытие события телеметрии, опустошить очередь записей.
on_session_reset
С внедрением, когда шлюз меняет ключ сессии для активного чата — пользователь вызывал /new, /reset, /clear или адаптер выбирал новую сессию после периода бездействия. Это позволяет плагинам реагировать на то, что состояние разговора было очищено, не ожидая следующего on_session_start.
Идентификатор новой сессии (уже переключён на свежее значение).
платформа
ул
Название платформы обмена сообщениями.
С реализация: В gateway/run.py, сразу после выделения нового ключа сессии, но до дальнейших обсуждений в предстоящих сообщениях. На шлюзе порядок: on_session_finalize(old_id) → замена → on_session_reset(new_id) → on_session_start(new_id) на первом этапе в следующем шаге.
Возвращаемое значение: Игнорируется.
Варианты использования: Сбросить кэши, применить ограничения к session_id, отправить событие «сессия переключена» в аналитику, подготовить новый набор событий.
См. Руководство по созданию плагина для полного пошагового руководства, включая схемы инструментов, обработчиков и расширенные шаблоны хуков.
subagent_stop
Осуществление один раз на каждого дочернего агента после завершения delegate_task. Независимо от того, делили ли вы одну задачу или три, этот хук реализуется один раз для каждого дочернего процесса, последовательно в родительском потоке.
Тег роли оркестратора, установленный для дочернего процесса («Нет», если функция не включена)
child_summary
ул \| Нет
Окончательный ответ, который дочерний процесс вернул родителю
child_status
ул
"завершено","не удалось", "прервано" или "ошибка"
длительность_мс
интервал
Время выполнения дочернего процесса в миллисекундах
Сбывается: В tools/delegate_tool.py, после того как ThreadPoolExecutor.as_completed() обрабатывает все фьючерсы дочерних процессов. Вызов маршалируется в родительском потоке, поэтому авторам хуков не нужно было думать о конкурентном выполнении обратных вызовов.
Возвращаемое значение: Игнорируется.
Варианты использования: Логирование активности оркестрации, накопление длительности дочерних процессов для биллинга, запись записей аудита после делегирования.
Нормализованное входящее сообщение (имеет .text, .source, .message_id, .internal и т.д.).
шлюз
GatewayRunner
Активный шлюз-раннер, чтобы подключить различные варианты gateway.adapters[platform].send(...) для ответов по побочным каналам (уведомления владельцу и т.д.).
session_store
SessionStore
Для бесшумного приема стенограммы через session_store.append_to_transcript(...).
Сбывается: В gateway/run.py, внутри GatewayRunner._handle_message(), сразу после вычисления is_internal. Внутренние события полностью пропускают этот хук (они генерируют систему — обеспечивают фоновые процессы и т.д. — и не должны контролироваться пользовательской политикой).
Возвращаемое значение:None или словарь. Первый распознанный словарь действует побеждает; остальные результаты плагинов отключаются. Исключения в обратных вызовах плагинов перехватываются и регистрируются; В случае ошибки шлюз всегда обращается к нормальной диспетчеризации.
Возврат
Эффект
{"действие": "пропустить", "причина": "..."}
Отбросить сообщение — никакого ответа агента, никаких ссылок, никаких аутентификаций. Предполагается, что штекер уже обработал его (например, бесшумно принял в стенограмму).
{"action": "rewrite", "text": "новый текст"}
Замените event.text, затем продолжите нормальную диспетчеризацию с изменённым событием. Полезно для поворота буферизованных фоновых сообщений в одном запросе.
Варианты использования: Групповые чаты только для чтения (отвечать только при упоминании; буферизовать фоновые сообщения в контексте); передача человеку (бесшумно принимается сообщение клиенту, пока владелец обрабатывает чат вручную); ограничение скорости для каждого профиля; маршрутизация на основе политики.
Пример — отклонение несанкционированных личных сообщений без активации кода ссылки:
С выполнением непосредственно перед показом запроса на подтверждение запроса — соответствующие все поверхности: интерактивный CLI, платформа TUI Ink, шлюз (Telegram, Discord, Slack, WhatsApp, Matrix и т.д.) и клиенты ACP (VS Code, Zed, JetBrains).
Это подходящее место для подключения пользовательского нотификатора — например, приложение в строке меню macOS, показывает с кнопками разрешить/запретить, или журнал аудита, записывающего каждый запрос подтверждения с контекстом.
Понятное человеку объяснение причины(ин), по которому команда помечена (объединяется, когда происходит несколько шаблонов)
шаблон_ключ
ул
Первичный ключ-шаблон, вызвавший запрос (например, "rm_rf", "sudo")
pattern_keys
список[стр]
Все ключи с узорами, которые совпали
session_key
ул
Идентификатор сессии, используемый для ограничений доступа к чатам
поверхность
ул
"cli" для интерактивных подсказок CLI/TUI, "gateway" для асинхронных подтверждений на платформах
Возвращаемое значение: теряется. Хуки здесь только для наблюдения; они не могут отклонить или предварительно ответить на подтверждение. Используйте pre_tool_call, чтобы заблокировать инструмент до того, как он достигнет системы подтверждения.
Варианты использования: Уведомления на рабочем столе, push-уведомления, аудит-логи, вебхуки Slack, маршрутизация эскалации, метрики.
Пример —. на рабочем столе macOS:
importsubprocessdefnotify_approval(command,description,session_key,**kwargs):title="Hermes требует подтверждения"body=f"{description}: {command[:80]}"subprocess.Popen(["osascript","-e",f'display notification "{body}" with title "{title}"',])defregister(ctx):ctx.register_hook("pre_approval_request",notify_approval)
post_approval_response
С выполнением после того, как пользователь ответил на запрос подтверждения (или истёк таймаут).
Те же самые kwargs, что и у pre_approval_request, плюс:
Параметр
Тип
Описание
выбор
ул
Одно из «однажды», «сеанса», «всегда», «запретить» или «тайм-аут»
Возвращаемое значение: теряется.
Варианты использования: Закройте описание на рабочем столе, окончательно запишите решение в журнал аудита, обновите метрики, переместите ограничитель скорости.
deflog_decision(command,choice,session_key,**kwargs):logger.info("подтверждение %s: %s для сессии %s",choice,command[:60],session_key)defregister(ctx):ctx.register_hook("post_approval_response",log_decision)
transform_tool_result
Осуществление после возврата инструмента и до добавления результата в разговор. Посмотрите плагин, перепишите результат ЛЮБОГО инструмента — не только выводите терминал — до того, как модель его увидит.
Инструмент, который создал результат (read_file, web_extract, delegate_task,...).
аргументы
дикт
Аргументы, с помощью которых возникла модель данного инструмента.
результат
ул
Сырой результат инструмента, после обрезки и удаления ANSI.
task_id
ул \| Нет
Идентификатор задачи/сессии при выполнении в среде RL/бенчмарков.
Возвращаемое значение:str для замены результата (возвращенная строка будет видна модели), None для оставления без изменений.
Варианты использования: Редактировать PII организации из вывода web_extract, обернуть длинные JSON-ответы инструментов в заголовке сводки, внедрить подсказки включения с дополнениями в результаты read_file, переписать отчёты подчинённых delegate_task в схему, специфичную для проекта.
Применяется к каждому инструменту. Для перезаписи только терминала см. transform_terminal_output ниже — он более узкий и прогрессирует раньше на конвейере (до обрезки, до редактирования).
transform_terminal_output
Сработка внутри конвейера вывода инструмента терминалдо обработка обрезки 50 КБ, удаление ANSI и доработка секретов. Используйте плагины, чтобы переписать сырой stdout/stderr команды оболочки до того, как с ним начнёт работать следующая обработка.
Сырой объединённый stdout/stderr (может быть очень большим — обрезка происходит после хука).
код_выхода
интервал
Код процесса возврата.
cwd
ул
Рабочая директория, в которой выполнялась команда.
Возвращаемое значение:str для замены результатов, None для оставления без изменений.
Варианты использования: Внедрить сводки для команды, которые создают огромные выводы (du -ah, find, tree), пометить вывод маркером, специфичным для проекта, чтобы нижестоящие хуки знали, как с этим обращаться, удалять шум времени выполнения, которое меняется между запусками и кэшированием промптов.
defsummarize_find(command,output,**kwargs):ifcommand.startswith("find ")andlen(output)>50_000:lines=output.count("\n")head="\n".join(output.splitlines()[:40])returnf"{head}\n\n[сводка: всего {lines} путей, показаны первые 40]"returnNonedefregister(ctx):ctx.register_hook("transform_terminal_output",summarize_find)
Хорошо сочетается с «transform_tool_result» (который соответствует всем остальным инструментам).
transform_llm_output
Осуществляется один раз за шаг после выполнения инструментов вызова цикла и получения модели окончательного ответа, до того, как этот ответ будет доставлен пользователю (CLI, шлюз или программный вызывающий). Рассмотрим плагин, переписывающий окончательный текст ассистента с помощью классических программных методов — без дополнительных токенов вывода, потраченных на текст SOUL или преобразование, управляемых навыками.
Заключительный текст ответа ассистента для этого шага.
session_id
ул
Идентификатор сессии для этого разговора (может быть пустым для одноразовых запусков).
модель
ул
Имя модели, создавшей ответ (например, anthropic/claude-sonnet-4.6).
платформа
ул
Платформа доставки (cli, telegram, discord, …; пусто, если не задано).
Возвращаемое значение: Непустая str для замены текста ответа, None или пустая строка для оставления без изменений. Первая непустая строка побеждает, когда зарегистрировано несколько плагинов — обозначается transform_tool_result.
Варианты использования: Применить преобразование личности/словаря (пиратская речь, Губка Боб), администраторы идентификаторов, специальные для пользователя, из конечного текста, снизу добавить колонтитул, специальный для проекта, обеспечить соблюдение правил по стилю без затрат токенов на инструкции SOUL.
importos,redefspongebob(response_text,**kwargs):ifos.environ.get("SPONGEBOB_MODE")!="on":returnNone# пропустить без измененийreturnre.sub(r"!","!! Тартарный соус!",response_text)defregister(ctx):ctx.register_hook("transform_llm_output",spongebob)
Хук защищён от непустого, не прерванного ответа — он не срабатывает при нажатии кнопки остановки или пустых шагах. Исключения регистрируются как обращение и не прерывают выполнение агента.
Шелл-хуки (Крючки-ракушки)
Объявите шелл-скриптовые хуки в файле cli-config.yaml, и Hermes будет запускать их как подпроцессы в любой момент, когда реализация включает плагин события хука — как в CLI, так и в сессиях шлюза. Не требуется создание плагинов на Python.
Используйте шелл-хуки, когда вам нужен подключаемый однофайловый скрипт (Bash, Python, что угодно с shebang), чтобы:
Блокировать инструмент вызова — отклонять опасные команды терминала, применять политику для каждой директории, требовать подтверждения для деструктивных операций write_file/patch.
Запускаться после вызова инструмента — автоматически форматировать файлы Python или TypeScript, которые только что написал агент, зарегистрировать API-вызовы, запустить CI-рабочий процесс.
Внедрять контекст в следующем шаге LLM — вывод git status, текущий день недели или извлечённые документы для сообщения пользователю (см. pre_llm_call).
Наблюдать за событиями жизненного цикла — запишите текст лога, когда подчинённый завершается (subagent_stop) или начинается сессия (on_session_start).
Шелл-хуки регистрирует вызов agent.shell_hooks.register_from_config(cfg) при запуске как CLI (hermes_cli/main.py), так и шлюза (gateway/run.py). Они естественно сочетаются с хуками плагинов Python — оба передаются через одного и того же диспетчера.
Жизненный цикл шлюза (шлюз:запуск, агент:*, команда:*)
Можно заблокировать инструмент вызова
Да (pre_tool_call)
Да (pre_tool_call)
Нет
Может внедрить контекст LLM
Да (pre_llm_call)
Да (pre_llm_call)
Нет
Согласие
Первый запрос на каждую пару (событие, команда)
Неявно (доверие к плагину Python)
Неявно (доверие к каталогу)
Межпроцессная изоляция
Да (подпроцесс)
Нет (внутри процесса)
Нет (внутри процесса)
Схема конфигурации
hooks:<event_name>:# Должно быть в VALID_HOOKS-matcher:"<regex>"# Опционально; используется только для pre/post_tool_callcommand:"<командаshell>"# Обязательно; выполняется через shlex.split, shell=Falsetimeout:<секунды># Опционально; по умолчанию 60, не более 300hooks_auto_accept:false# См. "Модель согласия" ниже
Имена событий должны быть одним из событий хуков плагинов; опечатки вызывают предупреждение «Возможно, вы имели в виду X?» и про результат. Неизвестные ключи внутри одной записи игнорируются; отсутствие команды приводит к пропуску с предупреждением. timeout > 300 обрезается с предупреждением.
Протокол JSON через канал
Каждый раз, когда событие происходит, Hermes генерирует подпроцесс для каждого подходящего хука (с учётом фильтра), передаёт JSON-нагрузку на stdin и считывает stdout обратно как JSON.
tool_name и tool_input равны null для событий, не связанных с инструментами (pre_llm_call, subagent_stop, жизненный циклический сеанс). Словарь extra содержит все специфичные для событий kwargs (user_message, conversation_history, child_role, duration_ms,...). Несериализуемые значения преобразуются в строки, а не в результате.
Представление файла в девятом агенте не пересчитывается автоматически — переформатирование влияет только на файл на диске. Последующие вызовы read_file создают отформатированную версию.
#!/usr/bin/env bash# ~/.hermes/agent-hooks/inject-cwd-context.sh
cat->/dev/null# отбросить нагрузку stdinifstatus=$(gitstatus--porcelain2>/dev/null)&&[[-n"$status"]];thenjq--null-input--args"$status"\'{context: ("Незакоммиченные изменения в cwd:\n" + $s)}'elseprintf'{}\n'fi
Событие UserPromptSubmit от Claude Code намеренно не является событием Hermes — pre_llm_call выполняется в том же месте и уже поддерживает реализацию контекста. Используйте его здесь.
Каждый элемент пара (event, команда) запрашивает у пользователя подтверждение, когда Hermes впервые его видит, а затем сохраняет решение в ~/.hermes/shell-hooks-allowlist.json. При последующем запуске (CLI или шлюз) запрос пропускают.
Три выхода обойти интерактивный запрос — достаточно любого:
Флаг --accept-hooks в CLI (например, hermes --accept-hookschat)
Переменная окружения HERMES_ACCEPT_HOOKS=1
hooks_auto_accept: true в cli-config.yaml
Запуски не в TTY (шлюз, cron, CI) требуют одного из трех вариантов — в противном случае любой недавно добавленный хук остается незарегистрированным и регистрирует предупреждение.
Редактирование скриптов молча доверяется. Список разрешений привязывается к точной строке команды, а не к хэшу скрипта, поэтому редактирование скрипта на диске не отменяет согласия. Гермес Крючки Доктор фиксирует перемещение времени, чтобы вы могли увидеть правки и решить, нужно ли повторно восстановить.
CLI гермесовые хуки
Команда
Что делает
список крючков Гермеса
Вывести настроенные хуки с фильтром, таймаутом и статусом соглашения
тест перехватчиков Гермеса <событие> [--for-tool X] [--payload-file F]
Запустить все подсоединения хуки с синтетической конфигурацией и вывести распарсенный ответ
хуки Гермеса отозвать <команду>
Удалить все записи из списка разрешений, соответствует <command> (вступает в силу при следующем перезапуске)
гермес крючки доктор
Для каждого настроенного хука: проверьте исполняемость, статус в списке разрешений, расхождение mtime, валидность JSON-вывода и примерное битовое время выполнения
Безопасность
Шелл-хуки выполняются с вашими полными учётными данными пользователя — та же граница безопасности, что и запись cron или псевдоним оболочки. Относитесь к блоку hooks: в config.yaml как к привилегированной конфигурации:
Используйте только скрипты, которые вы написали или полностью проверили.
Храните скрипты внутри ~/.hermes/agent-hooks/, чтобы путь легко было аудировать.
Повторно запустите Hermes Hooks Doctor после извлечения рабочей документации, чтобы просмотреть недавно добавленные хуки для их регистрации.
Если ваш config.yaml хранится в системе контроля управления для команды, просматривайте PR, изменяя раздел hooks:, так же, как вы просматриваете конфигурацию CI.
Порядок и приоритет
Как хуки плагинов Python, так и шелл-хуки передаются через один и тот же диспетчер invoke_hook(). Плагины Python регистрируются первыми (discover_and_load()), шелл-хуки первыми (register_from_config()), поэтому в спорных случаях решения по блокировке с помощью Python pre_tool_call имеют приоритет. Первая валидная блокировка побеждает — агрегатор возвращает результат, как только любой обратный вызов вызывает {"action": "block", "message": str} с непустым сообщением.