Внутреннее устройство Gateway

Messaging gateway — это долгоживущий процесс, который соединяет Hermes с 20+ внешними платформами обмена сообщениями через единую архитектуру.

Ключевые файлы

Файл Назначение
gateway/run.py GatewayRunner — главный цикл, слэш-команды, диспетчеризация сообщений (большой файл; проверьте git для текущего LOC)
gateway/session.py SessionStore — сохранение бесед и построение ключей сессий
gateway/delivery.py Доставка исходящих сообщений на целевые платформы/каналы
gateway/pairing.py Поток сопряжения в личных сообщениях для авторизации пользователей
gateway/channel_directory.py Сопоставляет ID чатов с человекочитаемыми именами для доставки cron
gateway/hooks.py Обнаружение, загрузка хуков и диспетчеризация событий жизненного цикла
gateway/mirror.py Межсессионное зеркалирование сообщений для send_message
gateway/status.py Управление блокировками токенов для экземпляров gateway в рамках профиля
gateway/builtin_hooks/ Точка расширения для постоянно зарегистрированных хуков (не поставляется)
gateway/platforms/ Адаптеры платформ (по одному на каждую платформу обмена сообщениями)

Обзор архитектуры

┌─────────────────────────────────────────────────┐
│                  GatewayRunner                  │
│                                                 │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐       │
│  │ Telegram │  │ Discord  │  │  Slack   │       │
│  │ Adapter  │  │ Adapter  │  │ Adapter  │       │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘       │
│       │             │             │             │
│       └─────────────┼─────────────┘             │
│                     ▼                           │
│              _handle_message()                  │
│                     │                           │
│         ┌───────────┼───────────┐               │
│         ▼           ▼           ▼               │
│  Slash command   AIAgent    Queue/BG            │
│    dispatch      creation   sessions            │
│                     │                           │
│                     ▼                           │
│                 SessionStore                    │
│              (SQLite persistence)               │
└───────┴─────────────┴─────────────┴─────────────┘

Поток сообщений

Когда сообщение поступает с любой платформы:

  1. Адаптер платформы получает сырое событие и нормализует его в MessageEvent

  2. Базовый адаптер проверяет защиту активной сессии:

  3. Если агент запущен для этой сессии → поставить сообщение в очередь, установить событие прерывания
  4. Если /approve, /deny, /stop → обойти защиту (диспетчеризация inline)

  5. GatewayRunner._handle_message() получает событие:

  6. Разрешение ключа сессии через _session_key_for_source() (формат: agent:main:{platform}:{chat_type}:{chat_id})
  7. Проверка авторизации (см. раздел «Авторизация» ниже)
  8. Проверка, является ли это слэш-командой → диспетчеризация обработчику команд
  9. Проверка, запущен ли уже агент → перехват команд, таких как /stop, /status
  10. В противном случае → создание экземпляра AIAgent и запуск беседы

  11. Ответ отправляется обратно через адаптер платформы

Формат ключа сессии

Ключи сессий кодируют полный контекст маршрутизации:

agent:main:{platform}:{chat_type}:{chat_id}

Например: agent:main:telegram:private:123456789

Платформы с поддержкой тредов (темы форумов Telegram, треды Discord, треды Slack) могут включать ID тредов в часть chat_id. Никогда не создавайте ключи сессий вручную — всегда используйте build_session_key() из gateway/session.py.

Двухуровневая защита сообщений

Когда агент активно работает, входящие сообщения проходят через две последовательные проверки:

  1. Уровень 1 — Базовый адаптер (gateway/platforms/base.py): Проверяет _active_sessions. Если сессия активна, помещает сообщение в очередь _pending_messages и устанавливает событие прерывания. Это перехватывает сообщения до того, как они достигнут gateway runner.

  2. Уровень 2 — Gateway runner (gateway/run.py): Проверяет _running_agents. Перехватывает определённые команды (/stop, /new, /queue, /status, /approve, /deny) и маршрутизирует их соответствующим образом. Всё остальное вызывает running_agent.interrupt().

Команды, которые должны достичь runner'а, пока агент заблокирован (например, /approve), диспетчеризируются inline через await self._message_handler(event) — они обходят систему фоновых задач, чтобы избежать состояний гонки.

Авторизация

Gateway использует многоуровневую проверку авторизации, выполняемую по порядку:

  1. Флаг разрешения всех на платформе (например, TELEGRAM_ALLOW_ALL_USERS) — если установлен, все пользователи на этой платформе авторизованы

  2. Белый список платформы (например, TELEGRAM_ALLOWED_USERS) — ID пользователей через запятую

  3. Сопряжение в ЛС — авторизованные пользователи могут добавлять новых пользователей через код сопряжения

  4. Глобальное разрешение всех (GATEWAY_ALLOW_ALL_USERS) — если установлен, все пользователи на всех платформах авторизованы

  5. По умолчанию: запрет — неавторизованные пользователи отклоняются

Поток сопряжения в ЛС

Admin: /pair
Gateway: "Pairing code: ABC123. Share with the user."
New user: ABC123
Gateway: "Paired! You're now authorized."

Состояние сопряжения сохраняется в gateway/pairing.py и переживает перезапуски.

Диспетчеризация слэш-команд

Все слэш-команды в gateway проходят через один и тот же конвейер разрешения:

  1. resolve_command() из hermes_cli/commands.py сопоставляет ввод с каноническим именем (обрабатывает псевдонимы, сопоставление по префиксу)

  2. Каноническое имя проверяется на наличие в GATEWAY_KNOWN_COMMANDS

  3. Обработчик в _handle_message() диспетчеризирует на основе канонического имени

  4. Некоторые команды ограничены конфигурацией (gateway_config_gate в CommandDef)

Защита от работающего агента

Команды, которые НЕ ДОЛЖНЫ выполняться, пока агент обрабатывает запрос, отклоняются на раннем этапе:

if _quick_key in self._running_agents:
    if canonical == "model":
        return "⏳ Agent is running — wait for it to finish or /stop first."

Команды обхода (/stop, /new, /approve, /deny, /queue, /status) имеют специальную обработку.

Источники конфигурации

Gateway читает конфигурацию из нескольких источников:

Источник Что предоставляет
~/.hermes/.env Ключи API, токены ботов, учётные данные платформ
~/.hermes/config.yaml Настройки моделей, конфигурация инструментов, параметры отображения
Переменные окружения Переопределяют любые из вышеуказанных

В отличие от CLI (который использует load_cli_config() с жёстко заданными значениями по умолчанию), gateway читает config.yaml напрямую через загрузчик YAML. Это означает, что ключи конфигурации, существующие в словаре значений по умолчанию CLI, но отсутствующие в файле конфигурации пользователя, могут вести себя по-разному в CLI и gateway.

Адаптеры платформ

Каждая платформа обмена сообщениями имеет адаптер в gateway/platforms/:

gateway/platforms/
├── base.py              # BaseAdapter — shared logic for all platforms
├── telegram.py          # Telegram Bot API (long polling or webhook)
├── discord.py           # Discord bot via discord.py
├── slack.py             # Slack Socket Mode
├── whatsapp.py          # WhatsApp Business Cloud API
├── signal.py            # Signal via signal-cli REST API
├── matrix.py            # Matrix via mautrix (optional E2EE)
├── mattermost.py        # Mattermost WebSocket API
├── email.py             # Email via IMAP/SMTP
├── sms.py               # SMS via Twilio
├── dingtalk.py          # DingTalk WebSocket
├── feishu.py            # Feishu/Lark WebSocket or webhook
├── wecom.py             # WeCom (WeChat Work) callback
├── weixin.py            # Weixin (personal WeChat) via iLink Bot API
├── bluebubbles.py       # Apple iMessage via BlueBubbles macOS server
├── qqbot/               # QQ Bot (Tencent QQ) via Official API v2 (sub-package: adapter.py, crypto.py, keyboards.py, …)
├── yuanbao.py           # Yuanbao (Tencent) DM/group adapter
├── feishu_comment.py    # Feishu document/drive comment-reply handler
├── msgraph_webhook.py   # Microsoft Graph change-notification webhook (Teams, Outlook, etc.)
├── webhook.py           # Inbound/outbound webhook adapter
├── api_server.py        # REST API server adapter
└── homeassistant.py     # Home Assistant conversation integration

Адаптеры реализуют общий интерфейс:

Блокировки токенов

Адаптеры, подключающиеся с уникальными учётными данными, вызывают acquire_scoped_lock() в connect() и release_scoped_lock() в disconnect(). Это предотвращает одновременное использование одного и того же токена бота двумя профилями.

Путь доставки

Исходящие доставки (gateway/delivery.py) обрабатывают:

Доставки cron-задач НЕ зеркалируются в историю сессий gateway — они существуют только в собственной cron-сессии. Это осознанный выбор дизайна для предотвращения нарушений чередования сообщений.

Хуки

Хуки gateway — это Python-модули, которые реагируют на события жизненного цикла:

События хуков gateway

Событие Когда срабатывает
gateway:startup Процесс gateway запускается
session:start Начинается новый сеанс беседы
session:end Сессия завершается или истекает по таймауту
session:reset Пользователь сбрасывает сессию командой /new
agent:start Агент начинает обработку сообщения
agent:step Агент завершает одну итерацию вызова инструментов
agent:end Агент завершает работу и возвращает ответ
command:* Любая слэш-команда выполняется

Хуки обнаруживаются из gateway/builtin_hooks/ (точка расширения — в настоящее время пуста в поставляемой сборке; _register_builtin_hooks() — это заглушка, не выполняющая никаких действий) и ~/.hermes/hooks/ (установленные пользователем). Каждый хук — это директория с манифестом HOOK.yaml и файлом handler.py.

Интеграция провайдеров памяти

Когда включён плагин провайдера памяти (например, Honcho):

  1. Gateway создаёт AIAgent для каждого сообщения с ID сессии

  2. MemoryManager инициализирует провайдера с контекстом сессии

  3. Инструменты провайдера (например, honcho_profile, viking_search) маршрутизируются через:

AIAgent._invoke_tool()
  → self._memory_manager.handle_tool_call(name, args)
    → provider.handle_tool_call(name, args)
  1. При завершении/сбросе сессии срабатывает on_session_end() для очистки и финального сброса данных

Жизненный цикл сброса памяти

Когда сессия сбрасывается, возобновляется или истекает:

  1. Встроенные воспоминания сбрасываются на диск

  2. Срабатывает хук on_session_end() провайдера памяти

  3. Временный AIAgent выполняет оборот беседы только для памяти

  4. Затем контекст отбрасывается или архивируется

Фоновое обслуживание

Gateway выполняет периодическое обслуживание параллельно с обработкой сообщений:

Управление процессами

Gateway работает как долгоживущий процесс, управляемый через:

В рамках профиля vs глобально: start_gateway() использует PID-файлы в рамках профиля. hermes gateway stop останавливает только gateway текущего профиля. hermes gateway stop --all использует глобальное сканирование ps aux для завершения всех процессов gateway (используется при обновлениях).