Перейти к содержанию

Internals

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

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

Файл Назначение
gateway/run.py GatewayRunner — главный цикл, слэш-команды, диспетчеризация сообщений (~12 000 строк)
gateway/session.py SessionStore — сохранение контекста беседы и формирование ключа сессии
gateway/delivery.py Доставка исходящих сообщений на целевые платформы/каналы
gateway/pairing.py Процедура привязки в личных сообщениях (DM) для авторизации пользователей
gateway/channel_directory.py Сопоставление ID чатов с человекочитаемыми именами для доставки cron-задач
gateway/hooks.py Обнаружение хуков, их загрузка и диспетчеризация событий жизненного цикла
gateway/mirror.py Зеркалирование сообщений между сессиями для send_message
gateway/status.py Управление блокировками токенов для экземпляров шлюза в рамках профиля
gateway/builtin_hooks/ Точка расширения для всегда зарегистрированных хуков (ничего не входит в поставку)
gateway/platforms/ Адаптеры платформ (по одному на каждую платформу обмена сообщениями)
## Обзор архитектуры
[code]
┌─────────────────────────────────────────────────┐
│ GatewayRunner │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Telegram │ │ Discord │ │ Slack │ │
│ │ Adapter │ │ Adapter │ │ Adapter │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ └─────────────┼─────────────┘ │
│ ▼ │
│ _handle_message() │
│ │ │
│ ┌───────────┼───────────┐ │
│ ▼ ▼ ▼ │
│ Slash command AIAgent Queue/BG │
│ dispatch creation sessions │
│ │ │
│ ▼ │
│ SessionStore │
│ (SQLite persistence) │
└───────┴─────────────┴─────────────┴─────────────┘

[/code]

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

Когда сообщение поступает с любой платформы: 1. Адаптер платформы получает сырое событие, нормализует его в MessageEvent 2. Базовый адаптер проверяет защиту активной сессии: * Если агент уже выполняется для этой сессии → поставить сообщение в очередь, установить событие прерывания * Если /approve, /deny, /stop → пропустить защиту (обрабатываются inline) 3. GatewayRunner._handle_message() получает событие: * Разрешение ключа сессии через _session_key_for_source() (формат: agent:main:{platform}:{chat_type}:{chat_id}) * Проверка авторизации (см. Авторизацию ниже) * Проверка, является ли сообщение слэш-командой → отправка в обработчик команд * Проверка, выполняется ли уже агент → перехват команд типа /stop, /status * В противном случае → создание экземпляра AIAgent и запуск беседы 4. Ответ отправляется обратно через адаптер платформы

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

Ключи сессии кодируют полный контекст маршрутизации: [code] agent:main:{platform}:{chat_type}:{chat_id}

[/code] Например: 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 и устанавливает событие прерывания. Это перехватывает сообщения до того, как они достигнут шлюза. 2. Уровень 2 — Шлюз (gateway/run.py): Проверяет _running_agents. Перехватывает определённые команды (/stop, /new, /queue, /status, /approve, /deny) и направляет их соответствующим образом. Всё остальное вызывает running_agent.interrupt().

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

Авторизация

Шлюз использует многоуровневую проверку авторизации, выполняемую по порядку: 1. Флаг разрешения всех на платформе (например, TELEGRAM_ALLOW_ALL_USERS) — если установлен, все пользователи на этой платформе авторизованы 2. Белый список платформы (например, TELEGRAM_ALLOWED_USERS) — ID пользователей через запятую 3. Привязка в личных сообщениях (DM) — авторизованные пользователи могут привязывать новых пользователей через код привязки 4. Глобальное разрешение всех (GATEWAY_ALLOW_ALL_USERS) — если установлен, все пользователи на всех платформах авторизованы 5. По умолчанию: запрет — неавторизованные пользователи отклоняются

Процедура привязки в DM

[code] Админ: /pair Шлюз: "Код привязки: ABC123. Передайте пользователю." Новый пользователь: ABC123 Шлюз: "Привязка выполнена! Теперь вы авторизованы."

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

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

Все слэш-команды в шлюзе проходят через один и тот же конвейер разрешения: 1. resolve_command() из hermes_cli/commands.py сопоставляет ввод с каноническим именем (обрабатывает псевдонимы, сопоставление по префиксу) 2. Каноническое имя проверяется на наличие в GATEWAY_KNOWN_COMMANDS 3. Обработчик в _handle_message() диспетчеризует на основе канонического имени 4. Некоторые команды ограничены конфигурацией (gateway_config_gate в CommandDef)

Защита запущенного агента

Команды, которые НЕ ДОЛЖНЫ выполняться, пока агент обрабатывает запрос, отклоняются на раннем этапе: [code] if _quick_key in self._running_agents: if canonical == "model": return "⏳ Агент выполняется — дождитесь завершения или используйте /stop."

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

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

Шлюз читает конфигурацию из нескольких источников: Источник| Что предоставляет ---|--- ~/.hermes/.env| API-ключи, токены ботов, учётные данные платформ ~/.hermes/config.yaml| Настройки модели, конфигурация инструментов, параметры отображения Переменные окружения| Переопределяют любой из вышеперечисленных В отличие от CLI (который использует load_cli_config() с жёстко заданными значениями по умолчанию), шлюз читает config.yaml напрямую через загрузчик YAML. Это означает, что ключи конфигурации, существующие в словаре значений по умолчанию CLI, но отсутствующие в файле конфигурации пользователя, могут вести себя по-разному между CLI и шлюзом.

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

Каждая платформа обмена сообщениями имеет адаптер в gateway/platforms/: [code] gateway/platforms/ ├── base.py # BaseAdapter — общая логика для всех платформ ├── telegram.py # Telegram Bot API (long polling или webhook) ├── discord.py # Discord bot через discord.py ├── slack.py # Slack Socket Mode ├── whatsapp.py # WhatsApp Business Cloud API ├── signal.py # Signal через signal-cli REST API ├── matrix.py # Matrix через mautrix (опционально E2EE) ├── mattermost.py # Mattermost WebSocket API ├── email.py # Email через IMAP/SMTP ├── sms.py # SMS через Twilio ├── dingtalk.py # DingTalk WebSocket ├── feishu.py # Feishu/Lark WebSocket или webhook ├── wecom.py # WeCom (WeChat Work) callback ├── weixin.py # Weixin (личный WeChat) через iLink Bot API ├── bluebubbles.py # Apple iMessage через BlueBubbles macOS сервер ├── qqbot.py # QQ Bot (Tencent QQ) через Official API v2 ├── webhook.py # Адаптер входящего/исходящего webhook ├── api_server.py # Адаптер REST API сервера └── homeassistant.py # Интеграция с Home Assistant conversation

[/code] Адаптеры реализуют общий интерфейс: * connect() / disconnect() — управление жизненным циклом * send_message() — отправка исходящих сообщений * on_message() — нормализация входящих сообщений → MessageEvent

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

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

Доставка сообщений

Исходящие доставки (gateway/delivery.py) обрабатывают: * Прямой ответ — отправка ответа обратно в исходный чат * Доставка в домашний канал — маршрутизация результатов cron-задач и фоновых результатов в настроенный домашний канал * Доставка в явно указанный канал — инструмент send_message с параметром telegram:-1001234567890 * Кроссплатформенная доставка — доставка на другую платформу, отличную от исходного сообщения

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

Хуки

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

События хуков шлюза

Событие Когда срабатывает
gateway:startup Запуск процесса шлюза
session:start Начало новой сессии беседы
session:end Завершение сессии или истечение тайм-аута
session:reset Пользователь сбрасывает сессию с помощью /new
agent:start Агент начинает обработку сообщения
agent:step Агент завершает одну итерацию вызова инструмента
agent:end Агент завершает работу и возвращает ответ
command:* Выполняется любая слэш-команда
Хуки обнаруживаются в gateway/builtin_hooks/ (всегда активны) и ~/.hermes/hooks/ (установленные пользователем). Каждый хук — это директория с манифестом HOOK.yaml и handler.py.
## Интеграция с провайдером памяти
Когда включён плагин провайдера памяти (например, Honcho):
1. Шлюз создаёт AIAgent для каждого сообщения с ID сессии
2. MemoryManager инициализирует провайдера с контекстом сессии
3. Инструменты провайдера (например, honcho_profile, viking_search) маршрутизируются через:

[code] AIAgent._invoke_tool() → self._memory_manager.handle_tool_call(name, args) → provider.handle_tool_call(name, args)

[/code] 4. При завершении/сбросе сессии срабатывает on_session_end() для очистки и финальной записи данных

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

Когда сессия сбрасывается, возобновляется или истекает: 1. Встроенные данные памяти сбрасываются на диск 2. Срабатывает хук on_session_end() провайдера памяти 3. Временный AIAgent выполняет один оборот беседы, посвящённый только памяти 4. Контекст затем отбрасывается или архивируется

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

Шлюз выполняет периодическое обслуживание параллельно с обработкой сообщений: * Тиканье cron — проверка расписания задач и запуск подлежащих выполнению задач * Истечение сессий — очистка заброшенных сессий после тайм-аута * Сброс памяти — упреждающий сброс памяти до истечения сессии * Обновление кэша — обновление списков моделей и статуса провайдеров

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

Шлюз работает как долгоживущий процесс, управляемый через: * hermes gateway start / hermes gateway stop — ручное управление * systemctl (Linux) или launchctl (macOS) — управление через системные службы * PID-файл в ~/.hermes/gateway.pid — отслеживание процесса в рамках профиля

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

Связанные документы