Внутреннее устройство 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) │
└───────┴─────────────┴─────────────┴─────────────┘
Поток сообщений
Когда сообщение поступает с любой платформы:
-
Адаптер платформы получает сырое событие и нормализует его в
MessageEvent -
Базовый адаптер проверяет защиту активной сессии:
- Если агент запущен для этой сессии → поставить сообщение в очередь, установить событие прерывания
-
Если
/approve,/deny,/stop→ обойти защиту (диспетчеризация inline) -
GatewayRunner._handle_message() получает событие:
- Разрешение ключа сессии через
_session_key_for_source()(формат:agent:main:{platform}:{chat_type}:{chat_id}) - Проверка авторизации (см. раздел «Авторизация» ниже)
- Проверка, является ли это слэш-командой → диспетчеризация обработчику команд
- Проверка, запущен ли уже агент → перехват команд, таких как
/stop,/status -
В противном случае → создание экземпляра
AIAgentи запуск беседы -
Ответ отправляется обратно через адаптер платформы
Формат ключа сессии
Ключи сессий кодируют полный контекст маршрутизации:
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 — Базовый адаптер (
gateway/platforms/base.py): Проверяет_active_sessions. Если сессия активна, помещает сообщение в очередь_pending_messagesи устанавливает событие прерывания. Это перехватывает сообщения до того, как они достигнут gateway runner. -
Уровень 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 использует многоуровневую проверку авторизации, выполняемую по порядку:
-
Флаг разрешения всех на платформе (например,
TELEGRAM_ALLOW_ALL_USERS) — если установлен, все пользователи на этой платформе авторизованы -
Белый список платформы (например,
TELEGRAM_ALLOWED_USERS) — ID пользователей через запятую -
Сопряжение в ЛС — авторизованные пользователи могут добавлять новых пользователей через код сопряжения
-
Глобальное разрешение всех (
GATEWAY_ALLOW_ALL_USERS) — если установлен, все пользователи на всех платформах авторизованы -
По умолчанию: запрет — неавторизованные пользователи отклоняются
Поток сопряжения в ЛС
Admin: /pair
Gateway: "Pairing code: ABC123. Share with the user."
New user: ABC123
Gateway: "Paired! You're now authorized."
Состояние сопряжения сохраняется в gateway/pairing.py и переживает перезапуски.
Диспетчеризация слэш-команд
Все слэш-команды в gateway проходят через один и тот же конвейер разрешения:
-
resolve_command()изhermes_cli/commands.pyсопоставляет ввод с каноническим именем (обрабатывает псевдонимы, сопоставление по префиксу) -
Каноническое имя проверяется на наличие в
GATEWAY_KNOWN_COMMANDS -
Обработчик в
_handle_message()диспетчеризирует на основе канонического имени -
Некоторые команды ограничены конфигурацией (
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
Адаптеры реализуют общий интерфейс:
-
connect()/disconnect()— управление жизненным циклом -
send_message()— доставка исходящих сообщений -
on_message()— нормализация входящего сообщения →MessageEvent
Блокировки токенов
Адаптеры, подключающиеся с уникальными учётными данными, вызывают acquire_scoped_lock() в connect() и release_scoped_lock() в disconnect(). Это предотвращает одновременное использование одного и того же токена бота двумя профилями.
Путь доставки
Исходящие доставки (gateway/delivery.py) обрабатывают:
-
Прямой ответ — отправка ответа обратно в исходный чат
-
Доставка в домашний канал — маршрутизация результатов cron-задач и фоновых результатов в настроенный домашний канал
-
Явная доставка цели — инструмент
send_message, указывающийtelegram:-1001234567890 -
Кроссплатформенная доставка — доставка на другую платформу, отличную от исходного сообщения
Доставки 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):
-
Gateway создаёт
AIAgentдля каждого сообщения с ID сессии -
MemoryManagerинициализирует провайдера с контекстом сессии -
Инструменты провайдера (например,
honcho_profile,viking_search) маршрутизируются через:
AIAgent._invoke_tool()
→ self._memory_manager.handle_tool_call(name, args)
→ provider.handle_tool_call(name, args)
- При завершении/сбросе сессии срабатывает
on_session_end()для очистки и финального сброса данных
Жизненный цикл сброса памяти
Когда сессия сбрасывается, возобновляется или истекает:
-
Встроенные воспоминания сбрасываются на диск
-
Срабатывает хук
on_session_end()провайдера памяти -
Временный
AIAgentвыполняет оборот беседы только для памяти -
Затем контекст отбрасывается или архивируется
Фоновое обслуживание
Gateway выполняет периодическое обслуживание параллельно с обработкой сообщений:
-
Тиканье cron — проверяет расписания задач и запускает подлежащие выполнению задачи
-
Истечение сессий — очищает заброшенные сессии после таймаута
-
Сброс памяти — упреждающе сбрасывает память до истечения сессии
-
Обновление кэша — обновляет списки моделей и статус провайдеров
Управление процессами
Gateway работает как долгоживущий процесс, управляемый через:
-
hermes gateway start/hermes gateway stop— ручное управление -
systemctl(Linux) илиlaunchctl(macOS) — управление службами -
PID-файл в
~/.hermes/gateway.pid— отслеживание процессов в рамках профиля
В рамках профиля vs глобально: start_gateway() использует PID-файлы в рамках профиля. hermes gateway stop останавливает только gateway текущего профиля. hermes gateway stop --all использует глобальное сканирование ps aux для завершения всех процессов gateway (используется при обновлениях).