Это руководство шаг за шагом проведёт вас через создание полноценного плагина Hermes с нуля. В результате вы получите работающий плагин с несколькими инструментами, хуками жизненного цикла, встроенными файлами данных и встроенным навыком — всё, что поддерживает система плагинов.
info Не уверены, какой гайд вам нужен?
У Hermes есть несколько различных подключаемых интерфейсов — одни используют Python API register_*, другие являются конфигурационными или основаны на директориях. Используйте эту карту в первую очередь:
Если вы хотите добавить…
Читайте
Пользовательские инструменты, хуки, слеш-команды, навыки или подкоманды CLI
Полная таблица подключаемых интерфейсов содержит сводку всех поверхностей расширения, включая конфигурационные (TTS, STT, MCP, оболочечные хуки) и на основе директорий (хуки шлюза).
Что вы создадите
Плагин-калькулятор с двумя инструментами:
calculate — вычисление математических выражений (2**16, sqrt(144), pi * 5**2)
unit_convert — конвертация между единицами измерения (100 F → 37.78 C, 5 km → 3.11 mi)
Плюс хук, который логирует каждый вызов инструмента, и встроенный файл навыка.
name:calculatorversion:1.0.0description:Math calculator — evaluate expressions and convert unitsprovides_tools:-calculate-unit_convertprovides_hooks:-post_tool_call
Это сообщает Hermes: «Я плагин с именем calculator, я предоставляю инструменты и хуки.» Поля provides_tools и provides_hooks — это списки того, что регистрирует плагин.
Необязательные поля, которые вы можете добавить:
author:Your Namerequires_env:# блокирует загрузку при отсутствии переменных; запрашивается во время установки-SOME_API_KEY# простой формат — плагин отключается, если отсутствует-name:OTHER_KEY# расширенный формат — показывает описание/url во время установкиdescription:"Key for the Other service"url:"https://other.com/keys"secret:true
Шаг 3: Напишите схемы инструментов
Создайте schemas.py — именно это читает LLM, чтобы решить, когда вызывать ваши инструменты:
"""Tool schemas — what the LLM sees."""CALCULATE={"name":"calculate","description":("Evaluate a mathematical expression and return the result. ""Supports arithmetic (+, -, *, /, **), functions (sqrt, sin, cos, ""log, abs, round, floor, ceil), and constants (pi, e). ""Use this for any math the user asks about."),"parameters":{"type":"object","properties":{"expression":{"type":"string","description":"Math expression to evaluate (e.g., '2**10', 'sqrt(144)')",},},"required":["expression"],},}UNIT_CONVERT={"name":"unit_convert","description":("Convert a value between units. Supports length (m, km, mi, ft, in), ""weight (kg, lb, oz, g), temperature (C, F, K), data (B, KB, MB, GB, TB), ""and time (s, min, hr, day)."),"parameters":{"type":"object","properties":{"value":{"type":"number","description":"The numeric value to convert",},"from_unit":{"type":"string","description":"Source unit (e.g., 'km', 'lb', 'F', 'GB')",},"to_unit":{"type":"string","description":"Target unit (e.g., 'mi', 'kg', 'C', 'MB')",},},"required":["value","from_unit","to_unit"],},}
Почему схемы важны: Поле description — это то, как LLM решает, когда использовать ваш инструмент. Будьте конкретны в описании того, что он делает и когда его применять. Параметры parameters определяют, какие аргументы передаёт LLM.
Шаг 4: Напишите обработчики инструментов
Создайте tools.py — это код, который фактически выполняется, когда LLM вызывает ваши инструменты:
"""Tool handlers — the code that runs when the LLM calls each tool."""importjsonimportmath# Safe globals for expression evaluation — no file/network access_SAFE_MATH={"abs":abs,"round":round,"min":min,"max":max,"pow":pow,"sqrt":math.sqrt,"sin":math.sin,"cos":math.cos,"tan":math.tan,"log":math.log,"log2":math.log2,"log10":math.log10,"floor":math.floor,"ceil":math.ceil,"pi":math.pi,"e":math.e,"factorial":math.factorial,}defcalculate(args:dict,**kwargs)->str:"""Evaluate a math expression safely. Rules for handlers: 1. Receive args (dict) — the parameters the LLM passed 2. Do the work 3. Return a JSON string — ALWAYS, even on error 4. Accept **kwargs for forward compatibility """expression=args.get("expression","").strip()ifnotexpression:returnjson.dumps({"error":"No expression provided"})try:result=eval(expression,{"__builtins__":{}},_SAFE_MATH)returnjson.dumps({"expression":expression,"result":result})exceptZeroDivisionError:returnjson.dumps({"expression":expression,"error":"Division by zero"})exceptExceptionase:returnjson.dumps({"expression":expression,"error":f"Invalid: {e}"})# Conversion tables — values are in base units_LENGTH={"m":1,"km":1000,"mi":1609.34,"ft":0.3048,"in":0.0254,"cm":0.01}_WEIGHT={"kg":1,"g":0.001,"lb":0.453592,"oz":0.0283495}_DATA={"B":1,"KB":1024,"MB":1024**2,"GB":1024**3,"TB":1024**4}_TIME={"s":1,"ms":0.001,"min":60,"hr":3600,"day":86400}def_convert_temp(value,from_u,to_u):# Normalize to Celsiusc={"F":(value-32)*5/9,"K":value-273.15}.get(from_u,value)# Convert to targetreturn{"F":c*9/5+32,"K":c+273.15}.get(to_u,c)defunit_convert(args:dict,**kwargs)->str:"""Convert between units."""value=args.get("value")from_unit=args.get("from_unit","").strip()to_unit=args.get("to_unit","").strip()ifvalueisNoneornotfrom_unitornotto_unit:returnjson.dumps({"error":"Need value, from_unit, and to_unit"})try:# Temperatureiffrom_unit.upper()in{"C","F","K"}andto_unit.upper()in{"C","F","K"}:result=_convert_temp(float(value),from_unit.upper(),to_unit.upper())returnjson.dumps({"input":f"{value}{from_unit}","result":round(result,4),"output":f"{round(result,4)}{to_unit}"})# Ratio-based conversionsfortablein(_LENGTH,_WEIGHT,_DATA,_TIME):lc={k.lower():vfork,vintable.items()}iffrom_unit.lower()inlcandto_unit.lower()inlc:result=float(value)*lc[from_unit.lower()]/lc[to_unit.lower()]returnjson.dumps({"input":f"{value}{from_unit}","result":round(result,6),"output":f"{round(result,6)}{to_unit}"})returnjson.dumps({"error":f"Cannot convert {from_unit} → {to_unit}"})exceptExceptionase:returnjson.dumps({"error":f"Conversion failed: {e}"})
Возврат: Всегда строка JSON. Как для успеха, так и для ошибок.
Никогда не вызывайте исключения: Перехватывайте все исключения, вместо этого возвращайте JSON с ошибкой.
Принимайте **kwargs: Hermes может передавать дополнительный контекст в будущем.
Шаг 5: Напишите регистрацию
Создайте __init__.py — он связывает схемы с обработчиками:
"""Calculator plugin — registration."""importloggingfrom.importschemas,toolslogger=logging.getLogger(__name__)# Track tool usage via hooks_call_log=[]def_on_post_tool_call(tool_name,args,result,task_id,**kwargs):"""Hook: runs after every tool call (not just ours)."""_call_log.append({"tool":tool_name,"session":task_id})iflen(_call_log)>100:_call_log.pop(0)logger.debug("Tool called: %s (session %s)",tool_name,task_id)defregister(ctx):"""Wire schemas to handlers and register hooks."""ctx.register_tool(name="calculate",toolset="calculator",schema=schemas.CALCULATE,handler=tools.calculate)ctx.register_tool(name="unit_convert",toolset="calculator",schema=schemas.UNIT_CONVERT,handler=tools.unit_convert)# This hook fires for ALL tool calls, not just oursctx.register_hook("post_tool_call",_on_post_tool_call)
Что делает register():
Вызывается ровно один раз при запуске
ctx.register_tool() помещает ваш инструмент в реестр — модель видит его немедленно
ctx.register_hook() подписывается на события жизненного цикла
ctx.register_cli_command() регистрирует подкоманду CLI (например, hermes my-plugin <подкоманда>)
ctx.register_command() регистрирует слеш-команду внутри сессии (например, /myplugin <аргументы> в CLI / чате шлюза) — см. Регистрация слеш-команд ниже
ctx.dispatch_tool(name, arguments) — вызывает любой другой инструмент (встроенный или из другого плагина) с автоматически подключённым контекстом родительского агента (одобрения, учётные данные, task_id). Полезно из обработчиков слеш-команд, которым нужно вызвать terminal, read_file или любой другой инструмент так, как если бы модель вызвала его напрямую.
Если эта функция падает, плагин отключается, но Hermes продолжает работу
Пример dispatch_tool — слеш-команда, запускающая инструмент:
defhandle_scan(ctx,argstr):"""Implement /scan by invoking the terminal tool through the registry."""result=ctx.dispatch_tool("terminal",{"command":f"find . -name '{argstr}'"})returnresult# returned to the caller's chat UIdefregister(ctx):ctx.register_command("scan",handle_scan,help="Find files matching a glob")
Вызванный инструмент проходит через обычные конвейеры одобрения, редактирования и бюджета — это реальный вызов инструмента, а не обходной путь.
Шаг 6: Протестируйте
Запустите Hermes:
hermes
Вы должны увидеть calculator: calculate, unit_convert в списке инструментов баннера.
Попробуйте следующие запросы:
What's 2 to the power of 16?
Convert 100 fahrenheit to celsius
What's the square root of 2 times pi?
How many gigabytes is 1.5 terabytes?
Проверьте статус плагина:
/plugins
Вывод:
Plugins(1):✓calculatorv1.0.0(2tools,1hooks)
Отладка обнаружения плагинов
Если ваш плагин не отображается — или отображается, но не загружается — установите HERMES_PLUGINS_DEBUG=1, чтобы получить подробные логи обнаружения в stderr:
HERMES_PLUGINS_DEBUG=1hermespluginslist
Вы увидите для каждого источника плагинов (встроенные, пользовательские, проектные, entry-points):
какие директории были просканированы и сколько манифестов каждая дала
для каждого манифеста: разрешённый ключ, имя, kind, источник, путь на диске
причины пропуска: disabled via config, not enabled in config, exclusive plugin, no plugin.yaml, depth cap reached
при загрузке: импортируемый плагин плюс однострочная сводка того, что зарегистрировал register(ctx) (инструменты, хуки, слеш-команды, CLI команды)
при ошибке парсинга: полный traceback исключения (ошибки YAML сканера и т.д.)
при ошибке register(): полный traceback, указывающий на строку в вашем __init__.py, где произошла ошибка
Те же логи всегда записываются в ~/.hermes/logs/agent.log на уровне WARNING (только ошибки) и DEBUG (всё) при установленной переменной окружения. Если вы не можете запустить с переменной окружения (например, изнутри шлюза), просматривайте файл лога вместо этого:
hermeslogs--levelWARNING|grep-iplugin
Распространённые причины, почему плагин не появляется:
Не включён в конфиге — плагины подключаются по желанию. Выполните hermes plugins enable <имя> (имя берётся из вывода plugins list, которое может быть <категория>/<плагин> для вложенных макетов).
Неправильная структура директории — должно быть ~/.hermes/plugins/<имя-плагина>/plugin.yaml (плоская) или ~/.hermes/plugins/<категория>/<имя-плагина>/plugin.yaml (один уровень вложенности категории, макс.). Всё более глубокое игнорируется.
Отсутствует __init__.py — директория плагина должна содержать и plugin.yaml, и __init__.py с функцией register(ctx).
Неправильный kind — адаптеры шлюза должны иметь kind: platform в манифесте. Провайдеры памяти определяются автоматически как kind: exclusive и маршрутизируются через конфиг memory.provider, а не plugins.enabled.
Финальная структура вашего плагина
~/.hermes/plugins/calculator/├──plugin.yaml# "Я calculator, я предоставляю инструменты и хуки"├──__init__.py# Связывание: схемы → обработчики, регистрация хуков├──schemas.py# Что читает LLM (описания + спецификации параметров)└──tools.py# Что выполняется (функции calculate, unit_convert)
Четыре файла, чёткое разделение:
Манифест объявляет, что такое плагин
Схемы описывают инструменты для LLM
Обработчики реализуют фактическую логику
Регистрация соединяет всё вместе
Что ещё могут делать плагины?
Встраивание файлов данных
Поместите любые файлы в директорию вашего плагина и читайте их во время импорта:
# In tools.py or __init__.pyfrompathlibimportPath_PLUGIN_DIR=Path(__file__).parent_DATA_FILE=_PLUGIN_DIR/"data"/"languages.yaml"withopen(_DATA_FILE)asf:_DATA=yaml.safe_load(f)
Встраивание навыков
Плагины могут содержать файлы навыков, которые агент загружает через skill_view(\"plugin:skill\"). Зарегистрируйте их в вашем __init__.py:
Теперь агент может загружать ваши навыки по их именованному имени:
skill_view("my-plugin:my-workflow")# → версия плагинаskill_view("my-workflow")# → встроенная версия (без изменений)
Ключевые свойства:
Навыки плагина только для чтения — они не попадают в ~/.hermes/skills/ и не могут быть отредактированы через skill_manage.
Навыки плагина не перечислены в индексе <available_skills> системного промпта — они загружаются явно по желанию.
Простые имена навыков не затрагиваются — пространство имён предотвращает коллизии со встроенными навыками.
Когда агент загружает навык плагина, перед ним добавляется баннер контекста пакета со списком соседних навыков из того же плагина.
tip Устаревший паттерн
Старый паттерн shutil.copy2 (копирование навыка в ~/.hermes/skills/) всё ещё работает, но создаёт риск коллизии имён со встроенными навыками. Для новых плагинов предпочитайте ctx.register_skill().
Блокировка по переменным окружения
Если вашему плагину нужен API ключ:
# plugin.yaml — простой формат (обратная совместимость)requires_env:-WEATHER_API_KEY
Если WEATHER_API_KEY не установлен, плагин отключается с понятным сообщением. Никакого краха, никакой ошибки в агенте — просто «Plugin weather disabled (missing: WEATHER_API_KEY)».
Когда пользователи запускают hermes plugins install, они интерактивно запрашиваются для отсутствующих переменных requires_env. Значения автоматически сохраняются в .env.
Для лучшего опыта установки используйте расширенный формат с описаниями и URL для регистрации:
# plugin.yaml — расширенный форматrequires_env:-name:WEATHER_API_KEYdescription:"API key for OpenWeather"url:"https://openweathermap.org/api"secret:true
Поле
Обязательно
Описание
name
Да
Имя переменной окружения
description
Нет
Показывается пользователю во время установки
url
Нет
Где получить учётные данные
secret
Нет
Если true, ввод скрыт (как поле пароля)
Оба формата можно смешивать в одном списке. Уже установленные переменные молча пропускаются.
Условная доступность инструментов
Для инструментов, зависящих от опциональных библиотек:
ctx.register_tool(name="my_tool",schema={...},handler=my_handler,check_fn=lambda:_has_optional_lib(),# False = инструмент скрыт от модели)
Каждый хук полностью документирован в Справочнике хуков событий — сигнатуры колбэков, таблицы параметров, когда именно срабатывает, и примеры. Вот краткая сводка:
Большинство хуков — это наблюдатели типа «запустил и забыл» — их возвращаемые значения игнорируются. Исключение — pre_llm_call, который может внедрять контекст в разговор.
Все колбэки должны принимать **kwargs для обратной совместимости. Если колбэк хука падает, это логируется и пропускается. Другие хуки и агент продолжают нормально работать.
Внедрение контекста pre_llm_call
Это единственный хук, чьё возвращаемое значение имеет значение. Когда колбэк pre_llm_call возвращает словарь с ключом "context" (или обычную строку), Hermes внедряет этот текст в сообщение пользователя текущего хода. Это механизм для плагинов памяти, RAG интеграций, защитных шлюзов и любого плагина, которому нужно предоставить модели дополнительный контекст.
Формат возврата
# Словарь с ключом contextreturn{"context":"Recalled memories:\\n- User prefers dark mode\\n- Last project: hermes-agent"}# Обычная строка (эквивалент формы словаря выше)return"Recalled memories:\\n- User prefers dark mode"# Вернуть None или не возвращать → без внедрения (только наблюдение)returnNone
Любой не-None, непустой возврат с ключом "context" (или обычная непустая строка) собирается и добавляется к сообщению пользователя для текущего хода.
Как работает внедрение
Внедряемый контекст добавляется к сообщению пользователя, а не к системному промпту. Это осознанный дизайнерский выбор:
Сохранение кеша промптов — системный промпт остаётся идентичным между ходами. Anthropic и OpenRouter кешируют префикс системного промпта, поэтому его стабильность экономит 75%+ входных токенов в многопозиционных разговорах. Если бы плагины изменяли системный промпт, каждый ход приводил бы к промаху кеша.
Эфемерность — внедрение происходит только во время вызова API. Исходное сообщение пользователя в истории разговора никогда не изменяется, и ничего не сохраняется в базу данных сессии.
Системный промпт — территория Hermes — он содержит инструкции, специфичные для модели, правила принудительного использования инструментов, инструкции по личности и кешированное содержимое навыков. Плагины добавляют контекст рядом с вводом пользователя, а не изменяя основные инструкции агента.
Пример: Плагин памяти
"""Memory plugin — recalls relevant context from a vector store."""importhttpxMEMORY_API="https://your-memory-api.example.com"defrecall_context(session_id,user_message,is_first_turn,**kwargs):"""Called before each LLM turn. Returns recalled memories."""try:resp=httpx.post(f"{MEMORY_API}/recall",json={"session_id":session_id,"query":user_message,},timeout=3)memories=resp.json().get("results",[])ifnotmemories:returnNone# нечего внедрятьtext="Recalled context from previous sessions:\\n"text+="\\n".join(f"- {m['text']}"forminmemories)return{"context":text}exceptException:returnNone# молча падать, не ломать агентаdefregister(ctx):ctx.register_hook("pre_llm_call",recall_context)
Пример: Плагин защитных шлюзов
"""Guardrails plugin — enforces content policies."""POLICY="""You MUST follow these content policies for this session:- Never generate code that accesses the filesystem outside the working directory- Always warn before executing destructive operations- Refuse requests involving personal data extraction"""definject_guardrails(**kwargs):"""Injects policy text into every turn."""return{"context":POLICY}defregister(ctx):ctx.register_hook("pre_llm_call",inject_guardrails)
Пример: Хук только для наблюдения (без внедрения)
"""Analytics plugin — tracks turn metadata without injecting context."""importlogginglogger=logging.getLogger(__name__)deflog_turn(session_id,user_message,model,is_first_turn,**kwargs):"""Fires before each LLM call. Returns None — no context injected."""logger.info("Turn: session=%s model=%s first=%s msg_len=%d",session_id,model,is_first_turn,len(user_messageor""))# Нет возврата → без внедренияdefregister(ctx):ctx.register_hook("pre_llm_call",log_turn)
Несколько плагинов, возвращающих контекст
Когда несколько плагинов возвращают контекст из pre_llm_call, их выводы объединяются с двойными переводами строк и добавляются к сообщению пользователя вместе. Порядок соответствует порядку обнаружения плагинов (алфавитный по имени директории плагина).
Регистрация CLI команд
Плагины могут добавлять собственное дерево подкоманд hermes <плагин>:
def_my_command(args):"""Handler for hermes my-plugin <subcommand>."""sub=getattr(args,"my_command",None)ifsub=="status":print("All good!")elifsub=="config":print("Current config: ...")else:print("Usage: hermes my-plugin <status|config>")def_setup_argparse(subparser):"""Build the argparse tree for hermes my-plugin."""subs=subparser.add_subparsers(dest="my_command")subs.add_parser("status",help="Show plugin status")subs.add_parser("config",help="Show plugin config")subparser.set_defaults(func=_my_command)defregister(ctx):ctx.register_tool(...)ctx.register_cli_command(name="my-plugin",help="Manage my plugin",setup_fn=_setup_argparse,handler_fn=_my_command,)
После регистрации пользователи могут запускать hermes my-plugin status, hermes my-plugin config и т.д.
Плагины провайдеров памяти используют подход на основе соглашений вместо этого: добавьте функцию register_cli(subparser) в файл cli.py вашего плагина. Система обнаружения плагинов памяти находит её автоматически — вызов ctx.register_cli_command() не требуется. См. Руководство по плагинам провайдеров памяти для подробностей.
Блокировка активного провайдера: CLI команды плагинов памяти появляются только когда их провайдер является активным memory.provider в конфиге. Если пользователь не настроил ваш провайдер, ваши CLI команды не будут засорять вывод справки.
Регистрация слеш-команд
Плагины могут регистрировать слеш-команды внутри сессии — команды, которые пользователи вводят во время разговора (например, /lcm status или /ping). Они работают как в CLI, так и в шлюзе (Telegram, Discord и т.д.).
def_handle_status(raw_args:str)->str:"""Handler for /mystatus — called with everything after the command name."""ifraw_args.strip()=="help":return"Usage: /mystatus [help|check]"return"Plugin status: all systems nominal"defregister(ctx):ctx.register_command("mystatus",handler=_handle_status,description="Show plugin status",)
После регистрации пользователи могут вводить /mystatus в любой сессии. Команда появляется в автодополнении, выводе /help и меню Telegram бота.
Имя команды без ведущего слеша (например, "lcm", "mystatus")
handler
Callable[[str], str \| None]
Вызывается с сырой строкой аргументов. Может быть async.
description
str
Показывается в /help, автодополнении и меню Telegram бота
Ключевые отличия от register_cli_command():
register_command()
register_cli_command()
Вызывается как
/name в сессии
hermes name в терминале
Где работает
CLI сессии, Telegram, Discord и т.д.
Только терминал
Обработчик получает
Сырую строку аргументов
argparse Namespace
Сценарий использования
Диагностика, статус, быстрые действия
Сложные деревья подкоманд, мастера настройки
Защита от конфликтов: Если плагин пытается зарегистрировать имя, конфликтующее со встроенной командой (help, model, new и т.д.), регистрация молча отклоняется с предупреждением в логе. Встроенные команды всегда имеют приоритет.
Асинхронные обработчики: Диспетчер шлюза автоматически определяет и ожидает async обработчики, поэтому вы можете использовать как синхронные, так и асинхронные функции:
Обработчикам слеш-команд, которым нужно оркестрировать инструменты (запустить субагента через delegate_task, вызвать file_edit и т.д.), следует использовать ctx.dispatch_tool() вместо обращения к внутренностям фреймворка. Контекст родительского агента (подсказки рабочей области, спиннер, наследование модели) подключается автоматически.
defregister(ctx):def_handle_deliver(raw_args:str):result=ctx.dispatch_tool("delegate_task",{"goal":raw_args,"toolsets":["terminal","file","web"],},)returnresultctx.register_command("deliver",handler=_handle_deliver,description="Delegate a goal to a subagent",)
Имя инструмента, зарегистрированного в реестре (например, "delegate_task", "file_edit")
args
dict
Аргументы инструмента, той же формы, что отправила бы модель
parent_agent
Agent \| None
Опциональное переопределение. Если опущено, разрешается из текущего CLI агента (или корректно деградирует в режиме шлюза)
Поведение во время выполнения:
Режим CLI:parent_agent разрешается из активного CLI агента, поэтому подсказки рабочей области, спиннер и выбор модели наследуются как ожидается.
Режим шлюза: CLI агента нет, поэтому инструменты корректно деградируют — рабочая область читается из TERMINAL_CWD, и спиннер не показывается.
Явное переопределение: Если вызывающий явно передаёт parent_agent=, оно уважается и не перезаписывается.
Это публичный, стабильный интерфейс для диспетчеризации инструментов из команд плагинов. Плагины не должны обращаться к ctx._cli_ref.agent или подобным приватным состояниям.
Этот гайд охватывает **общие плагины** (инструменты, хуки, слеш-команды, CLI команды). Разделы ниже описывают паттерн создания для каждого специализированного типа плагинов; каждый ссылается на свой полный гайд для справочника полей и примеров.
Специализированные типы плагинов
У Hermes есть пять специализированных типов плагинов помимо общей поверхности. Каждый поставляется как директория в plugins/<категория>/<имя>/ (встроенные) или ~/.hermes/plugins/<категория>/<имя>/ (пользовательские). Контракт различается в зависимости от категории — выберите нужный, затем прочитайте его полный гайд.
Плагины провайдеров моделей — добавьте LLM бэкенд
Поместите профиль в plugins/model-providers/<имя>/:
# plugins/model-providers/acme/plugin.yamlname:acme-providerkind:model-providerversion:1.0.0description:Acme Inference — OpenAI-compatible direct API
Обнаруживается лениво при первом вызове get_provider_profile() или list_providers() — auth.py, config.py, doctor.py, models.py, runtime_provider.py и транспорт chat_completions автоматически подключаются к нему. Пользовательские плагины переопределяют встроенные по имени.
Полный гайд:Плагины провайдеров моделей — справочник полей, переопределяемые хуки (prepare_messages, build_extra_body, build_api_kwargs_extras, fetch_models), выбор api_mode, типы аутентификации, тестирование.
Плагины платформ — добавьте канал шлюза
Поместите адаптер в plugins/platforms/<имя>/:
# plugins/platforms/myplatform/adapter.pyfromgateway.platforms.baseimportBasePlatformAdapterclassMyPlatformAdapter(BasePlatformAdapter):asyncdefconnect(self):...asyncdefsend(self,chat_id,text):...asyncdefdisconnect(self):...defcheck_requirements():importosreturnbool(os.environ.get("MYPLATFORM_TOKEN"))def_env_enablement():importostok=os.getenv("MYPLATFORM_TOKEN","").strip()ifnottok:returnNonereturn{"token":tok}defregister(ctx):ctx.register_platform(name="myplatform",label="MyPlatform",adapter_factory=lambdacfg:MyPlatformAdapter(cfg),check_fn=check_requirements,required_env=["MYPLATFORM_TOKEN"],# Автозаполнение PlatformConfig.extra из окружения, чтобы настройки только с env# отображались в `hermes gateway status` без инстанцирования SDK.env_enablement_fn=_env_enablement,# Согласие на доставку cron: `deliver=myplatform` маршрутизируется к этой переменной.cron_deliver_env_var="MYPLATFORM_HOME_CHANNEL",emoji="💬",platform_hint="You are chatting via MyPlatform. Keep responses concise.",)
# plugins/platforms/myplatform/plugin.yamlname:myplatform-platformlabel:MyPlatformkind:platformversion:1.0.0description:MyPlatform gateway adapterrequires_env:-name:MYPLATFORM_TOKENdescription:"Bot token from the MyPlatform console"password:trueoptional_env:-name:MYPLATFORM_HOME_CHANNELdescription:"Default channel for cron delivery"password:false
Полный гайд:Добавление адаптеров платформ — полный контракт BasePlatformAdapter, маршрутизация сообщений, блокировка аутентификации, интеграция мастера настройки. Посмотрите plugins/platforms/irc/ для работающего примера только со stdlib.
Плагины провайдеров памяти — добавьте бэкенд знаний между сессиями
Поместите реализацию MemoryProvider в plugins/memory/<имя>/:
# plugins/image_gen/my-imggen/__init__.pyfromagent.image_gen_providerimportImageGenProviderclassMyImageGenProvider(ImageGenProvider):@propertydefname(self)->str:return"my-imggen"defis_available(self)->bool:...defgenerate(self,prompt:str,**kwargs)->str:...# возвращает путь к изображениюdefregister(ctx):ctx.register_image_gen_provider(MyImageGenProvider())
Полный гайд:Плагины провайдеров генерации изображений — полный ABC ImageGenProvider, метаданные list_models() / get_setup_schema(), хелперы success_response()/error_response(), вывод base64 vs URL, пользовательские переопределения, pip дистрибуция.
Примеры для справки:plugins/image_gen/openai/ (DALL-E / GPT-Image через OpenAI SDK), plugins/image_gen/openai-codex/, plugins/image_gen/xai/ (Grok image gen).
Не-Python поверхности расширения
Hermes также принимает расширения, которые вообще не являются Python плагинами. Они показаны в таблице подключаемых интерфейсов; разделы ниже кратко описывают каждый стиль создания.
MCP серверы — регистрация внешних инструментов
Серверы Model Context Protocol (MCP) регистрируют свои собственные инструменты в Hermes без какого-либо Python плагина. Объявите их в ~/.hermes/config.yaml:
Hermes подключается к каждому серверу при запуске, перечисляет его инструменты и регистрирует их вместе со встроенными. LLM видит их точно так же, как любой другой инструмент. Полный гайд:MCP.
Хуки событий шлюза — срабатывают по событиям жизненного цикла
Поместите манифест + обработчик в ~/.hermes/hooks/<имя>/:
# ~/.hermes/hooks/long-task-alert/HOOK.yamlname:long-task-alertdescription:Send a push notification when a long task finishesevents:-agent:end
События включают gateway:startup, session:start, session:end, session:reset, agent:start, agent:step, agent:end и wildcard command:*. Ошибки в хуках перехватываются и логируются — они никогда не блокируют основной конвейер.
Оболочечные хуки — выполнение команды оболочки при вызовах инструментов
Если вы просто хотите запустить скрипт при срабатывании инструмента (уведомления, аудиторские логи, оповещения на рабочем столе, автоформаттеры), используйте оболочечные хуки в config.yaml — Python не требуется:
Поддерживаются все те же события, что и для Python плагинов-хуков (pre_tool_call, post_tool_call, pre_llm_call, post_llm_call, on_session_start, on_session_end, pre_gateway_dispatch) плюс структурированный JSON вывод для решений блокировки pre_tool_call.
Публикация собственного tap — это просто GitHub репозиторий с директориями skills/<имя-навыка>/SKILL.md — никакого сервера или регистрации не требуется.
Для STT укажите HERMES_LOCAL_STT_COMMAND на шаблон оболочки. Поддерживаемые плейсхолдеры: {input_path}, {output_path}, {format}, {voice}, {model}, {speed} (TTS); {input_path}, {output_dir}, {language}, {model} (STT). Любой CLI, взаимодействующий с путями, автоматически становится плагином.
# Неправильно — сломается, если Hermes передаст дополнительный контекстdefhandler(args):...# Правильноdefhandler(args,**kwargs):...
Обработчик вызывает исключения:
# Неправильно — исключение распространяется, вызов инструмента падаетdefhandler(args,**kwargs):result=1/int(args["value"])# ZeroDivisionError!returnjson.dumps({"result":result})# Правильно — перехватываем и возвращаем JSON с ошибкойdefhandler(args,**kwargs):try:result=1/int(args.get("value",0))returnjson.dumps({"result":result})exceptExceptionase:returnjson.dumps({"error":str(e)})
Слишком размытое описание схемы:
# Плохо — модель не знает, когда использовать"description":"Does stuff"# Хорошо — модель знает точно когда и как"description":"Evaluate a mathematical expression. Use for arithmetic, trig, logarithms. Supports: +, -, *, /, **, sqrt, sin, cos, log, pi, e."