sidebar_position: 7 title: "Gateway Internals" description: "How the messaging gateway boots, authorizes users, routes sessions, and delivers messages" lang: ru


Внутреннее устройство шлюза

Шлюз обмена сообщениями — это длительный процесс, который соединяет Hermes с более чем 14 внешними платформами обмена сообщениями через унифицированную архитектуру.

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

Файл Цель
gateway/run.py GatewayRunner — основной цикл, слэш-команды, отправка сообщений (~12 000 строк)
gateway/session.py SessionStore — сохранение диалога и создание сеансового ключа
gateway/delivery.py Доставка исходящих сообщений на целевые платформы/каналы
gateway/pairing.py Процесс сопряжения DM для авторизации пользователя
gateway/channel_directory.py Сопоставляет идентификаторы чатов с удобочитаемыми именами для доставки cron
gateway/hooks.py Обнаружение, загрузка и отправка событий жизненного цикла хуков
gateway/mirror.py Межсессионное зеркалирование сообщений для send_message
gateway/status.py Управление блокировкой токенов для экземпляров шлюза на уровне профиля
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 → обход защиты (отправляется в линию)
  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) могут включать идентификаторы потоков в часть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), отправляются инлайн через await self._message_handler(event) — они обходят систему фоновых задач, чтобы избежать условий гонки.

Авторизация

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

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

Порядок подключения DM

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

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

Отправка команды Slash

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

  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) имеют специальную обработку.

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

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

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

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

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

Каждая платформа обмена сообщениями имеет адаптер 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.py             # QQ Bot (Tencent QQ) via Official API v2
├── 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 НЕ отражаются в истории сеансов шлюза — они живут только в своем собственном сеансе 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 для каждого сообщения с идентификатором сеанса.
  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. Контекст затем удаляется или архивируется.

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

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

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

Шлюз работает как долговременный процесс, управляемый через:

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

Сопутствующие документы