Плагины: как создать

Что такое плагин Hermes Agent

Плагин — это Python-пакет, расширяющий ассистента: добавляет инструменты (tools), хуки жизненного цикла, навыки (skills), команды и провайдеры. Плагины позволяют интегрировать внешние сервисы и настраивать поведение агента без изменения ядра.

Источники плагинов

Hermes загружает плагины из четырёх мест (поздние перекрывают ранние при совпадении имён):

  1. Bundled<repo>/plugins/<name>/ (идут с Hermes)
  2. User~/.hermes/plugins/<name>/
  3. Project./.hermes/plugins/<name>/ (включается через HERMES_ENABLE_PROJECT_PLUGINS)
  4. Pip — пакеты с entry-point группой hermes_agent.plugins

Структура плагина

Каждый плагин — это директория с обязательными файлами:

my-plugin/
├── plugin.yaml        # Манифест (обязательно)
├── __init__.py        # register(ctx) — точка входа (обязательно)
├── adapter.py         # Обработчики инструментов, хуков и т.д.
└── skills/            # (опционально) навыки плагина
    └── my-skill/
        └── SKILL.md

Манифест: plugin.yaml

Описывает имя, версию, зависимости и предоставляемые компоненты:

name: my-plugin
version: 1.0.0
description: "Краткое описание возможностей плагина"
author: "@author_name"
kind: standalone              # standalone | backend | exclusive | platform | model-provider
requires_env:
  - MY_API_KEY                # Проверяется при загрузке
provides_tools:
  - my_tool                   # Имена регистрируемых инструментов
provides_hooks:
  - post_tool_call            # Хуки, которые плагин использует
pip_dependencies:             # (опционально) автоустанавливаемые пакеты
  - some-package>=1.0.0
  - another-lib>=2.0,<3
hooks:                        # (опционально) хуки для краткой записи
  - pre_llm_call
  - on_session_end

Поля манифеста:

Поле Тип Описание
name string Имя плагина (по умолчанию — имя директории)
version string Семантическая версия
description string Описание для hermes plugins list
author string Автор
kind string Тип: standalone (по умолч.), backend, exclusive, platform, model-provider
requires_env list Обязательные переменные окружения
provides_tools list Имена регистрируемых инструментов
provides_hooks list Используемые хуки
pip_dependencies list Python-зависимости (формат pip: pkg>=1.0)

Точка входа: register(ctx)

Файл __init__.py должен содержать функцию register(ctx), которая вызывается при загрузке плагина. ctx — экземпляр PluginContext:

# my-plugin/__init__.py
from pathlib import Path
from . import adapter

def register(ctx):
    # 1. Инструмент
    ctx.register_tool(
        name="my_tool",
        toolset="my-plugin",
        schema={
            "name": "my_tool",
            "description": "Выполняет действие X. Используй когда нужно Y.",
            "parameters": {
                "type": "object",
                "properties": {
                    "query": {"type": "string", "description": "Входные данные"}
                },
                "required": ["query"]
            }
        },
        handler=adapter.handle_my_tool,
        requires_env=["MY_API_KEY"],
    )

    # 2. Хук
    ctx.register_hook("post_tool_call", adapter.on_tool_call)

    # 3. Slash-команда (/mycommand)
    ctx.register_command(
        name="mycommand",
        handler=adapter.handle_command,
        description="Моя команда",
        args_hint="[аргумент]",
    )

    # 4. CLI-подкоманда (hermes mycommand ...)
    ctx.register_cli_command(
        name="mycommand",
        help="Описание для hermes --help",
        setup_fn=adapter.setup_parser,
        handler_fn=adapter.run_command,
    )

    # 5. Навык плагина
    ctx.register_skill(
        name="my-skill",
        path=Path(__file__).parent / "skills" / "my-skill" / "SKILL.md",
        description="Навык, предоставляемый плагином",
    )

PluginContext API

PluginContext — фасад, передаваемый в register(ctx). Все методы регистрации:

Метод Назначение
ctx.register_tool() Регистрация инструмента в глобальном реестре
ctx.register_hook() Регистрация callback на событие жизненного цикла
ctx.register_command() Slash-команда (/name) в CLI и gateway
ctx.register_cli_command() CLI-подкоманда (hermes name ...)
ctx.register_skill() Навык плагина (загружается как plugin:name)
ctx.inject_message() Внедрение сообщения в активную беседу
ctx.dispatch_tool() Вызов другого инструмента из плагина
ctx.register_platform() Адаптер платформы messaging (gateway)
ctx.register_middleware() Middleware для изменения поведения
ctx.llm Доступ к LLM хоста (ctx.llm.complete())
ctx.profile_name Имя активного профиля Hermes

register_tool() — подробно

ctx.register_tool(
    name="calculate",              # Имя инструмента
    toolset="my-plugin",           # Группа инструментов
    schema={...},                  # OpenAI function calling schema
    handler=my_handler,            # fn(args: dict, **kwargs) -> str
    check_fn=lambda: True,         # Проверка зависимостей
    requires_env=["API_KEY"],      # Env-переменные
    is_async=False,                # Асинхронный обработчик
    description="...",             # Описание
    emoji="🔧",             # Эмодзи для UI
    override=False,                # Замена встроенного инструмента
)

register_hook() — подробно

ctx.register_hook("post_tool_call", callback)
# callback(tool_name, args, result, **kwargs)

Доступные хуки:

  • pre_tool_call / post_tool_call — до/после вызова инструмента
  • pre_llm_call / post_llm_call — до/после запроса к LLM
  • transform_tool_result — трансформация результата инструмента
  • transform_terminal_output — трансформация вывода терминала
  • transform_llm_output — трансформация ответа LLM
  • on_session_start / on_session_end / on_session_reset — жизненный цикл сессии
  • on_session_finalize — финализация сессии
  • subagent_start / subagent_stop — запуск/остановка субагента
  • pre_gateway_dispatch — перед диспатчем сообщения в gateway
  • pre_api_request / post_api_request / api_request_error — lifecycle API-запросов
  • pre_verify — gate перед верификацией кода
  • pre_approval_request / post_approval_response — lifecycle approval
  • kanban_task_claimed / kanban_task_completed / kanban_task_blocked — lifecycle kanban

register_command() — slash-команды

ctx.register_command(
    name="analyze",                          # /analyze
    handler=lambda args: f"Result: {args}",  # fn(raw_args: str) -> str | None
    description="Анализ данных",
    args_hint="[файл]",                      # Подсказка для Discord и др.
)

register_cli_command() — CLI-подкоманды

def setup_parser(subparser):
    subparser.add_argument("--output", help="Файл вывода")

def run_command(args):
    print(f"Running with output={args.output}")

ctx.register_cli_command(
    name="analyze",
    help="Краткое описание",
    setup_fn=setup_parser,
    handler_fn=run_command,
)

register_skill() — навыки плагина

from pathlib import Path

ctx.register_skill(
    name="my-workflow",
    path=Path(__file__).parent / "skills" / "my-workflow" / "SKILL.md",
    description="Рабочий процесс плагина",
)
# Загрузка: skill_view(name='my-plugin:my-workflow')
# Плагин-навыки — это opt-in explicit loads, НЕ в списке <available_skills>

inject_message() — внедрение сообщений

# Внедрить сообщение от пользователя в активную беседу
ctx.inject_message("Новое сообщение из внешнего источника", role="user")

# Внедрить системное сообщение
ctx.inject_message("Обновление статуса", role="system")

Плагин-навыки (Plugin Skills)

Плагины могут предоставлять навыки. Они загружается через skill_view() с квалифицированным именем <plugin_name>:<skill_name>:

# В коде плагина:
ctx.register_skill(
    name="weather-workflow",
    path=Path(__file__).parent / "skills" / "weather-workflow" / "SKILL.md",
    description="Рабочий процесс получения погоды",
)

# В агенте:
skill_view(name='my-plugin:weather-workflow')

Важно: плагин-навыки не попадают в <available_skills> системного промпта — они загружается только по явному запросу.

Обработчики

Каждый инструмент связан с Python-функцией. Обработчик принимает args (dict) и обязательно **kwargs. Возвращает JSON-строку:

# my-plugin/adapter.py
import json

def handle_my_tool(args, **kwargs):
    try:
        query = args.get("query", "")
        result = do_something(query)
        return json.dumps({"success": True, "result": result})
    except Exception as e:
        return json.dumps({"error": str(e)})

def on_tool_call(tool_name, args, result, **kwargs):
    print(f"Tool {tool_name} was called")

Тестирование

  1. Поместите плагин в ~/.hermes/plugins/my-plugin/
  2. Запустите hermes doctor — проверит обнаружение
  3. Запустите hermes plugins list — убедитесь что плагин виден
  4. Для подробного логирования: HERMES_PLUGINS_DEBUG=1 hermes

Включение плагина

Плагины opt-in — нужно явно включить в config.yaml:

plugins:
  enabled:
    - my-plugin
  disabled: []         # Явный deny-list (перекрывает enabled)
  entries:
    my-plugin:
      allow_tool_override: true   # Разрешить замену встроенных инструментов
      llm:
        allow_provider_override: true
        allowed_providers: [openrouter, anthropic]

Частые ошибки

  • Возврат dict вместо JSON-строки — используйте json.dumps()
  • Нет **kwargs в сигнатуре обработчика
  • Необработанные исключения — всегда оборачивайте в try/except
  • Слишком общее описание инструмента — LLM не поймёт, когда его вызывать
  • Забыли plugin.yaml — плагин не будет обнаружен
  • Забыли register(ctx) в __init__.py — плагин загрузится, но ничего не зарегистрирует
  • Конфликт имён команд — register_command() отклонит имя, совпадающее со встроенной командой
  • Плагин-навык с : в имени — символ зарезервирован для разделителя namespace