Создание плагина провайдера памяти

Плагины провайдеров памяти предоставляют агенту Hermes постоянное межсессионное хранилище знаний, выходное за рамки встроенных MEMORY.md и USER.md. В этом руководстве описано, как создать такой плагин.:::совет Провайдеры памяти — один из двух типов провайдерских плагинов. Второй — Плагины контекстного движка, которые заменяют встроенный компрессор контекста. Оба следующих шаблона: одиночный выбор, управление через конфигурацию, администрирование через hermes плагины.

Структура директории

Память каждого провайдера находится в plugins/memory/<имя>/:

plugins/memory/my-provider/
├── __init__.py      # Реализация MemoryProvider + точка входа register()
├── plugin.yaml      # Метаданные (имя, описание, хуки)
└── README.md        # Инструкции по настройке, справочник конфигурации, инструменты

начало базового класса MemoryProvider

Ваш плагин реализует абстрактный базовый класс MemoryProvider из agent/memory_provider.py:

from agent.memory_provider import MemoryProvider

class MyMemoryProvider(MemoryProvider):
    @property
    def name(self) -> str:
        return "my-provider"

    def is_available(self) -> bool:
        """Проверяет, может ли этот провайдер активироваться. Без сетевых вызовов."""
        return bool(os.environ.get("MY_API_KEY"))

    def initialize(self, session_id: str, **kwargs) -> None:
        """Вызывается один раз при запуске агента.

        kwargs всегда включает:
          hermes_home (str): Активный путь HERMES_HOME. Используйте для хранения.
        """
        self._api_key = os.environ.get("MY_API_KEY", "")
        self._session_id = session_id

    #... реализация остальных методов

Обязательные методы

Основной жизненный цикл

Метод Когда возникает Обязателен?
имя (свойство) Всегда Да
is_available() Инициализация агента, перед активацией Да — без сетевых вызовов
инициализировать(session_id, **kwargs) Запуск агента Да
get_tool_schemas() После изобретения инструменты для ремонта Да
handle_tool_call(имя, аргументы) Когда агент использует ваши инструменты Да (если есть инструменты)

Конфигурация

Метод Назначение Обязателен?
get_config_schema() Объявляет поля конфигурации для настройки памяти Гермеса Да
save_config(значения, hermes_home) Записывает несекретную конфигурацию в данном положении Да (если не только через переменные окружения)

Опциональные хуки

Метод Когда возникает Сценарий использования
system_prompt_block() Сборка системного промпта Статическая информация о провайдере
предварительная выборка(запрос) Перед каждым API-вызовом Возврат извлечённого контекста
queue_prefetch(запрос) После каждого шага Предварительная загрузка для следующего шага
sync_turn(пользователь, помощник) После каждого завершения шага Сохранение диалога
on_session_end(сообщения) Завершение диалога Финальное извлечение/сброс
on_pre_compress(сообщения) Перед сжатием контекста Сохранение инсайтов перед удалением
on_memory_write(действие, цель, содержимое) Встроенные записи в память Зеркалирование в вашем бэкенде
выключение() Процесс завершения Очистка соединений

Схема схемы

get_config_schema() возвращает список описаний полей, включая hermes Memory setup:

def get_config_schema(self):
    return [
        {
            "key": "api_key",
            "description": "API-ключ My Provider",
            "secret": True,           # → записывается в.env
            "required": True,
            "env_var": "MY_API_KEY",   # явное имя переменной окружения
            "url": "https://my-provider.com/keys",  # где получить
        },
        {
            "key": "region",
            "description": "Регион сервера",
            "default": "us-east",
            "choices": ["us-east", "eu-west", "ap-south"],
        },
        {
            "key": "project",
            "description": "Идентификатор проекта",
            "default": "hermes",
        },
    ]

Поля с secret: True и env_var в .env. Несекретные поля передаются в save_config().

💡 Tip

Минимальная и Полная схема Каждое поле из get_config_schema() запрашивается во время настройки памяти Hermes. Провайдерам с большим количеством опций следует делать минимальный протокол — включая только поля, которые пользователь обязан настроить (API-ключ, обязательные учётные данные). Дополнительные настройки документируйте в файле конфигурации (например, $HERMES_HOME/myprovider.json), и не запрашивайте их каждый раз во время установки. Это сохраняет скорость установки мастера, но поддерживает расширенную конфигурацию. Смотрите пример провайдера Supermemory — он запрашивает только API-ключ; все остальные параметры находятся в supermemory.json.

Сохранение конфигурации

def save_config(self, values: dict, hermes_home: str) -> None:
    """Записывает несекретную конфигурацию в собственное расположение."""
    import json
    from pathlib import Path
    config_path = Path(hermes_home) / "my-provider.json"
    config_path.write_text(json.dumps(values, indent=2))

Для провайдеров, использующих только переменные окружения, оставьте сообщение по умолчанию (пустую).

Точка входа плагина

def register(ctx) -> None:
    """Вызывается системой обнаружения плагинов памяти."""
    ctx.register_memory_provider(MyMemoryProvider())

плагин.yaml

name: my-provider
version: 1.0.0
description: "Краткое описание того, что делает этот провайдер."
hooks:
  - on_session_end    # перечислите реализованные хуки

Контракт потоков

sync_turn() НЕ ДОЛЖЕН блокировать выполнение. Если ваш бэкенд имеет задержку (API-вызовы, обработка LLM), выполните работу в фоновом потоке-демоне:

def sync_turn(self, user_content, assistant_content):
    def _sync():
        try:
            self._api.ingest(user_content, assistant_content)
        except Exception as e:
            logger.warning("Синхронизация не удалась: %s", e)

    if self._sync_thread and self._sync_thread.is_alive():
        self._sync_thread.join(timeout=5.0)
    self._sync_thread = threading.Thread(target=_sync, daemon=True)
    self._sync_thread.start()

Изоляция профилей

Все пути хранения обязаны использовать аргумент hermes_home из initialize(), а не жёстко заданный ~/.hermes:

# ПРАВИЛЬНО — в рамках профиля
from hermes_constants import get_hermes_home
data_dir = get_hermes_home() / "my-provider"

# НЕПРАВИЛЬНО — общий для всех профилей
data_dir = Path("~/.hermes/my-provider").expanduser()

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

Смотрите tests/agent/test_memory_plugin_e2e.py для полного шаблона E2E-тестирования с использованием реального SQLite-провайдера.

from agent.memory_manager import MemoryManager

mgr = MemoryManager()
mgr.add_provider(my_provider)
mgr.initialize_all(session_id="test-1", platform="cli")

# Тестирование маршрутизации инструментов
result = mgr.handle_tool_call("my_tool", {"action": "add", "content": "test"})

# Тестирование жизненного цикла
mgr.sync_all("сообщение пользователя", "сообщение ассистента")
mgr.on_session_end([])
mgr.shutdown_all()

Добавление CLI-команды

Плагины провайдеров в памяти могут регистрировать собственное дерево подкомандой CLI (например, «статус моего провайдера Гермеса», «конфигурация моего провайдера Гермеса»). Это использует обнаружение систем на основе соглашений — изменений в основных файлах не требуется.

Как это работает

  1. Добавьте файл cli.py в каталог вашего плагина.
  2. Определите функцию register_cli(subparser), которая строит дерево argparse.
  3. Система плагинов памяти обнаруживает его при запуске через discover_plugin_cli_commands()
  4. Ваша команда строительной под hermes <имя-провайдера> <подкоманда>

Ограничение активным провайдером: Ваши CLI-команды тогда только тогда, когда ваш провайдер является активным memory.provider в конфигурации. Если пользователь не настроил вашего провайдера, ваши команды не будут указаны в hermes --help.

Пример

# plugins/memory/my-provider/cli.py

def my_command(args):
    """Обработчик, вызываемый argparse."""
    sub = getattr(args, "my_command", None)
    if sub == "status":
        print("Провайдер активен и подключён.")
    elif sub == "config":
        print("Показываю конфигурацию...")
    else:
        print("Использование: hermes my-provider <status|config>")

def register_cli(subparser) -> None:
    """Строит дерево argparse для hermes my-provider.

    Вызывается discover_plugin_cli_commands() во время настройки argparse.
    """
    subs = subparser.add_subparsers(dest="my_command")
    subs.add_parser("status", help="Показать статус провайдера")
    subs.add_parser("config", help="Показать конфигурацию провайдера")
    subparser.set_defaults(func=my_command)

Эталонная продажа

Смотрите plugins/memory/honcho/cli.py для всего примера с 13 подкомандами, кросс-профильным управлением (--target-profile) и чтением/записью конфигурации.

Структура директории с CLI

plugins/memory/my-provider/
├── __init__.py      # Реализация MemoryProvider + register()
├── plugin.yaml      # Метаданные
├── cli.py           # register_cli(subparser)  CLI-команды
└── README.md        # Инструкции по настройке

Правило одного провайдера

Только один внешний провайдер памяти может быть активирован одновременно. Если пользователь попытается зарегистрировать вторую, MemoryManager отклонит его предупреждение. Это собственное раздувание схемных инструментов и конфликтов бэкендов.