Что такое плагин Hermes Agent
Плагин — это Python-пакет, расширяющий ассистента: добавляет инструменты (tools), хуки жизненного цикла, навыки (skills), команды и провайдеры. Плагины позволяют интегрировать внешние сервисы и настраивать поведение агента без изменения ядра.
Источники плагинов
Hermes загружает плагины из четырёх мест (поздние перекрывают ранние при совпадении имён):
- Bundled —
<repo>/plugins/<name>/(идут с Hermes) - User —
~/.hermes/plugins/<name>/ - Project —
./.hermes/plugins/<name>/(включается черезHERMES_ENABLE_PROJECT_PLUGINS) - 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— до/после запроса к LLMtransform_tool_result— трансформация результата инструментаtransform_terminal_output— трансформация вывода терминалаtransform_llm_output— трансформация ответа LLMon_session_start/on_session_end/on_session_reset— жизненный цикл сессииon_session_finalize— финализация сессииsubagent_start/subagent_stop— запуск/остановка субагентаpre_gateway_dispatch— перед диспатчем сообщения в gatewaypre_api_request/post_api_request/api_request_error— lifecycle API-запросовpre_verify— gate перед верификацией кодаpre_approval_request/post_approval_response— lifecycle approvalkanban_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")
Тестирование
- Поместите плагин в
~/.hermes/plugins/my-plugin/ - Запустите
hermes doctor— проверит обнаружение - Запустите
hermes plugins list— убедитесь что плагин виден - Для подробного логирования:
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