Один Telegram gateway для десятков Hermes

Один Telegram gateway для десятков Hermes

Навигация: Hermes как sales funnel bot · Nanobot / Hermes / Agent Garden · Agent Garden

Вопрос

Можно ли сделать так, чтобы был один Telegram bot / один Telegram gateway, а за ним сидели десятки отдельных Hermes-агентов: по клиенту, по чату, по пользователю или через ручной выбор агента ботом?

Цель: не создавать 10 Telegram-ботов для 10 клиентов, а иметь один публичный/операторский бот, который маршрутизирует запросы в нужный независимый Hermes.

Короткий вывод

Да, архитектурно можно и это правильнее, чем десяток Telegram-ботов. Но в текущем Hermes это не является готовой “галочкой” для многих backend-профилей за одним Telegram bot token.

Из коробки Hermes уже умеет:

  • один Telegram bot обслуживает много чатов/групп/топиков;
  • Telegram gateway строит разные session key по platform/chat_type/chat_id/thread_id/user_id;
  • один профиль Hermes может иметь много параллельных Telegram-сессий;
  • Telegram DM topics и group forum topics дают отдельные сессии внутри одного бота;
  • profiles дают отдельные Hermes с отдельной памятью, конфигом, skills, cron, state;
  • API Server даёт каждому профилю HTTP endpoint;
  • gateway proxy mode умеет пересылать сообщения в Hermes API Server.

Но ограничение сейчас такое:

  • один Hermes gateway profile рассчитан на один backend-agent контур;
  • GATEWAY_PROXY_URL / gateway.proxy_url — один URL для всего gateway, без встроенной per-chat/per-user маршрутизации;
  • один Telegram bot token нельзя одновременно использовать несколькими gateway-процессами: Hermes ставит lock по token, а Telegram long polling/webhook тоже предполагает одного активного consumer’а.

Значит, оптимальный путь: один центральный Telegram ingress/router владеет bot token, а дальше по таблице маршрутизации отправляет запрос в нужный Hermes profile API Server.

Что уже есть в Hermes

1. Telegram gateway поддерживает много чатов одним bot token

Документация Telegram integration описывает один BotFather token и allowlists:

TELEGRAM_BOT_TOKEN=...
TELEGRAM_ALLOWED_USERS=...

Один бот может работать в DMs, группах, супергруппах и forum topics.

Для групп есть отдельные gates:

  • TELEGRAM_ALLOWED_USERS / allow_from — глобальный доступ;
  • TELEGRAM_GROUP_ALLOWED_USERS / group_allow_from — кто может вызывать бота в группах;
  • TELEGRAM_GROUP_ALLOWED_CHATS / group_allowed_chats — какие группы разрешены целиком.

Это уже позволяет использовать одного Telegram бота для многих клиентских чатов, если все они должны попадать в один Hermes profile.

2. Session routing уже различает чат, пользователя и thread/topic

В gateway/session.py есть SessionSource:

platform
chat_id
chat_type
user_id
thread_id

И build_session_key() строит ключи вида:

agent:main:{platform}:dm:{chat_id}
agent:main:{platform}:dm:{chat_id}:{thread_id}
agent:main:{platform}:{chat_type}:{chat_id}:{thread_id}:{user_id?}

Для Telegram это означает:

  • DM с разными пользователями — разные сессии;
  • DM topic/thread — отдельная сессия;
  • group/forum topic — отдельная сессия;
  • обычная группа может быть shared или per-user в зависимости от правил.

3. Telegram topics уже решают “много сессий в одном боте”

Hermes docs описывают:

  • Private Chat Topics — фиксированные топики в DM, заданные оператором через config;
  • Multi-session DM mode /topic — пользователь сам включает topics и создаёт параллельные Hermes-сессии через Telegram UI;
  • Group Forum Topic Skill Binding — topic в супергруппе может auto-load конкретный skill.

Это полезно, если нужно много рабочих пространств внутри одного Hermes profile.

Но это не равно “десятки независимых Hermes профилей/клиентов”, потому что память, конфиг, credentials, toolsets и state всё ещё принадлежат одному profile.

4. Profiles дают настоящую изоляцию агентов

Hermes profiles — это отдельные home directories:

  • config.yaml;
  • .env;
  • SOUL.md;
  • memory;
  • sessions;
  • skills;
  • cron jobs;
  • gateway state;
  • logs.

Команды:

hermes profile create client-a
hermes profile create client-b
hermes -p client-a gateway
hermes -p client-b gateway

Для независимых клиентских агентов profiles — правильная единица изоляции.

5. API Server позволяет каждому profile быть backend-agent’ом

API Server включается env vars:

API_SERVER_ENABLED=true
API_SERVER_PORT=8643
API_SERVER_KEY=client-a-secret
hermes -p client-a gateway

Для многих профилей:

hermes profile create client-a
hermes profile create client-b

# client-a/.env
API_SERVER_ENABLED=true
API_SERVER_PORT=8643
API_SERVER_KEY=client-a-secret
API_SERVER_MODEL_NAME=client-a

# client-b/.env
API_SERVER_ENABLED=true
API_SERVER_PORT=8644
API_SERVER_KEY=client-b-secret
API_SERVER_MODEL_NAME=client-b

hermes -p client-a gateway &
hermes -p client-b gateway &

API Server поддерживает:

  • /v1/chat/completions;
  • /v1/responses;
  • /v1/runs;
  • SSE streaming;
  • X-Hermes-Session-Id для session continuity;
  • X-Hermes-Session-Key для долгоживущего per-channel memory scope.

6. Gateway proxy mode уже есть, но сейчас он один-к-одному

В gateway/run.py есть proxy mode:

GATEWAY_PROXY_URL
# или gateway.proxy_url

Когда он задан, gateway становится thin relay:

Telegram/Matrix/etc adapter
  → GatewayRunner
  → POST remote /v1/chat/completions

Но текущая реализация _get_proxy_url() возвращает один URL. Значит, это подходит для split deployment одного backend-agent’а, но не для routing map “чат A → profile A, чат B → profile B”.

Почему нельзя просто запустить 10 Hermes gateway с одним Telegram token

Есть два слоя ограничения:

  1. Telegram Bot API: long polling getUpdates конфликтует, если два процесса читают один bot token. Webhook у бота тоже один активный endpoint.
  2. Hermes: adapters с уникальными credentials используют acquire_scoped_lock(scope, identity) — в gateway/status.py прямо указано, что это предотвращает одновременное использование одного Telegram bot token разными HERMES_HOME/profiles.

Следовательно, должен быть один владелец Telegram token: либо Hermes gateway-router, либо внешний bot-router.

Рекомендуемая архитектура MVP

Telegram users/groups/topics
  → one Telegram bot token
  → central Telegram Router / Ingress
  → routing DB: chat_id/user_id/thread_id/customer → Hermes profile endpoint
  → Hermes profile API Server per client/agent
  → answer back to same Telegram chat/topic

Backend agents

Каждый клиент/агент — отдельный Hermes profile:

client-a: http://127.0.0.1:8643/v1  API_SERVER_KEY=a
client-b: http://127.0.0.1:8644/v1  API_SERVER_KEY=b
client-c: http://127.0.0.1:8645/v1  API_SERVER_KEY=c

Router DB

Минимальная таблица:

CREATE TABLE telegram_agent_routes (
  id INTEGER PRIMARY KEY,
  telegram_chat_id TEXT NOT NULL,
  telegram_user_id TEXT,
  telegram_thread_id TEXT,
  route_scope TEXT NOT NULL,        -- chat | user | thread | manual
  agent_id TEXT NOT NULL,
  api_base_url TEXT NOT NULL,
  api_key_ref TEXT NOT NULL,
  session_key TEXT NOT NULL,
  status TEXT NOT NULL DEFAULT 'active',
  created_at TEXT NOT NULL,
  updated_at TEXT NOT NULL,
  UNIQUE(telegram_chat_id, telegram_user_id, telegram_thread_id)
);

Практический route priority:

  1. exact chat_id + thread_id + user_id;
  2. chat_id + thread_id;
  3. chat_id + user_id;
  4. chat_id;
  5. user_id;
  6. default / ask user.

Что отправлять в Hermes API Server

Для каждого сообщения router вызывает нужный backend:

POST http://127.0.0.1:8643/v1/chat/completions
Authorization: Bearer client-a-secret
X-Hermes-Session-Id: tg:{chat_id}:{thread_id_or_root}:{user_id}:transcript
X-Hermes-Session-Key: tg:{chat_id}:{thread_id_or_root}:{user_id}
Content-Type: application/json

Body:

{
  "model": "hermes-agent",
  "stream": true,
  "messages": [
    {"role": "user", "content": "..."}
  ]
}

Для обычного multi-turn можно использовать X-Hermes-Session-Id, чтобы backend сам грузил историю из своего state.db. X-Hermes-Session-Key полезен, чтобы long-term memory понимала, что это тот же канал даже после /new или смены transcript.

UX routing: как бот узнаёт агента

Вариант A — по чату

Лучший для клиентских групп:

  • каждый клиент добавляет одного общего бота в свою группу;
  • admin один раз делает /bind client-a;
  • дальше весь chat_id маршрутизируется в client-a profile.

Плюсы:

  • просто;
  • меньше ошибок;
  • клиент не выбирает агента каждый раз;
  • подходит для B2B “один клиент = один чат”.

Минусы:

  • если в одном чате нужно несколько агентов, нужны topics или команды переключения.

Вариант B — по Telegram topic/thread

Лучший для одного общего супергруппового “портала”:

  • topic “Client A” → Hermes profile A;
  • topic “Client B” → Hermes profile B;
  • topic “Support” → support-agent.

Плюсы:

  • один Telegram group/forum как консоль управления;
  • thread_id является естественным route key.

Минусы:

  • нужно аккуратно настроить Telegram topics и права;
  • в topic shared context между участниками, если не включать per-user logic.

Вариант C — по пользователю

Подходит для личного DM:

  • user_id 123 всегда маршрутизируется в его личный Hermes;
  • /switch client-b меняет default route пользователя.

Плюсы:

  • хороший UX для “у каждого клиента/сотрудника свой агент”.

Минусы:

  • если один пользователь управляет многими агентами, нужна команда выбора.

Вариант D — бот спрашивает

Если route не найден:

К какому агенту подключить этот чат?
[client-a] [client-b] [создать нового] [одноразовый запрос]

После выбора router сохраняет binding.

Плюсы:

  • удобно при onboarding;
  • меньше ручной настройки.

Минусы:

  • надо реализовать pending state;
  • нужна защита от того, что обычный пользователь привяжет чат к чужому агенту.

Можно ли сделать это внутри Hermes без внешнего router?

Да, но это уже изменение Hermes gateway.

Нужна доработка proxy mode:

gateway:
  proxy_routes:
    - match:
        platform: telegram
        chat_id: "-1001111111111"
      proxy_url: "http://127.0.0.1:8643"
      proxy_key_env: "CLIENT_A_API_KEY"

    - match:
        platform: telegram
        user_id: "123456789"
      proxy_url: "http://127.0.0.1:8644"
      proxy_key_env: "CLIENT_B_API_KEY"

Кодово это означает:

  • заменить _get_proxy_url() на _resolve_proxy_route(source);
  • прокинуть в _run_agent_via_proxy() source и выбранный proxy_key;
  • добавить route table/config loader;
  • добавить fallback behavior “ask/bind”;
  • добавить tests для chat_id/user_id/thread_id priority;
  • не ломать существующий GATEWAY_PROXY_URL как default route.

Плюс желательно добавить slash-команды:

/bind <agent-id>
/unbind
/routes
/switch <agent-id>
/whoami

Почему внешний router быстрее для MVP

Внешний router проще и безопаснее, потому что:

  • не нужно патчить core Hermes;
  • можно сделать на python-telegram-bot или Node.js;
  • router отвечает только за Telegram, ACL, binding и HTTP proxy;
  • каждый Hermes profile остаётся vanilla;
  • rollback простой: остановить router, не трогая backend profiles.

Минимальная реализация:

router.py
  - receives Telegram update
  - extracts chat_id/user_id/thread_id/text/media
  - resolves agent route from SQLite/Postgres
  - if missing route: ask authorized user to bind
  - calls selected Hermes API Server
  - streams/collects response
  - sends response to same chat/thread

Безопасность / multi-tenant правила

Если это для десятков клиентов, нельзя делать только “модель сама поймёт клиента”. Нужны hard boundaries:

  1. Изоляция через profiles: отдельные state/memory/skills/env на клиента.
  2. Отдельные API keys: router хранит key refs, не отдаёт пользователям.
  3. ACL на bind/switch: только owner/admin может привязать chat_id к agent_id.
  4. Route visibility: /whoami показывает текущего agent_id и route scope.
  5. Audit log: кто привязал чат, когда, к какому агенту.
  6. No cross-client tools: credentials клиента лежат только в его profile .env.
  7. Rate/usage limits: quota лучше считать в router до вызова Hermes.
  8. Media handling: если прокидывать файлы, router должен либо передавать URL/base64 image, либо скачивать файл в безопасное shared storage; API Server сейчас лучше поддерживает inline images, а не произвольные file uploads.

Decision matrix

Если нужна быстро работающая схема

Выбрать внешний Telegram router + Hermes API Server per profile.

Если хочется сделать upstream feature в Hermes

Делать gateway.proxy_routes / route resolver внутри Hermes gateway.

Если клиенты не требуют строгой изоляции

Можно начать с одного Hermes profile, одного Telegram bot и route-by-chat/session topics. Это самый быстрый путь, но слабее по безопасности и кастомизации.

Если один оператор управляет многими агентами в личке

Использовать Telegram DM topics или команды /switch, где каждый topic/user binding указывает на backend profile.

MVP-план на 1–2 дня

  1. Создать 2–3 Hermes profiles: client-a, client-b, client-c.
  2. В каждом включить API Server на своём port и secret.
  3. Написать маленький Telegram router:
    • SQLite routes;
    • /bind agent_id;
    • /whoami;
    • обычное сообщение → selected API Server;
    • ответ обратно в тот же chat/thread.
  4. Проверить:
    • один DM → client-a;
    • одна группа → client-b;
    • два topic в одной группе → разные clients;
    • unauthorized user не может bind/switch.
  5. Если UX подтверждается — оформить как Hermes plugin/patch или оставить внешним сервисом.

Финальный ответ

Твоя идея правильная: не плодить Telegram bots. Один bot должен быть входной дверью. За ним — routing layer и десятки Hermes profiles/API Servers.

Самый практичный MVP: внешний Telegram router. Самый красивый upstream-вариант: расширить Hermes gateway.proxy_url до gateway.proxy_routes, чтобы Hermes gateway сам выбирал backend по chat_id, user_id или thread_id и мог спрашивать привязку при первом сообщении.

Источники и проверенные места

  • Hermes Telegram docs: website/docs/user-guide/messaging/telegram.md
  • Hermes Gateway Internals: website/docs/developer-guide/gateway-internals.md
  • Hermes API Server docs: website/docs/user-guide/features/api-server.md
  • Hermes profiles docs: https://hermes-agent.nousresearch.com/docs/user-guide/profiles
  • gateway/session.py: SessionSource, build_session_key()
  • gateway/run.py: _get_proxy_url(), _run_agent_via_proxy()
  • gateway/status.py: acquire_scoped_lock() prevents same Telegram token across profiles
  • gateway/platforms/api_server.py: /v1/chat/completions, X-Hermes-Session-Id, X-Hermes-Session-Key