--- tags: [project, hermes, zulip, go, infra] status: deployed created: 2026-06-16 --- # Zulip Router ## Цель Event-роутер между Zulip и несколькими Hermes-инстансами. Решает проблему конкурентного получения событий (Валера, Клавдий и другие боты видели все сообщения одновременно, `reset` срабатывал у всех). ## Проблема (root cause) - Каждый Hermes-инстанс в polling-режиме регистрирует собственную event queue в Zulip - `reset` — системная команда Hermes, обрабатывается до хука `whale-thread-guard` - Нет механизма ownership топика между инстансами ## Архитектура ``` Zulip Event Queue ↓ poll (valera-bot credentials) [zulip-router] ├── @Валера → http://hermes-valera:8765/zulip-webhook └── @Клавдий → http://hermes-klavdiy:8765/zulip-webhook ↕ ownership: stream+topic → bot (TTL 24h, persisted to /data/ownership.json) ``` Hermes/Орёл — не трогаем, остаётся в polling-режиме. ## Правила маршрутизации 1. `@Валера` или `@Valera` в тексте → Валера (+ ownership обновляется) 2. `@Клавдий` или `@Klavdiy` в тексте → Клавдий (+ ownership обновляется) 3. Топик уже у кого-то → туда же (TTL refreshed) 4. Новый топик без @mention → Валера (default) 5. `reset` без @mention → уходит только к владельцу топика ✓ ## Код `~/docker/zulip-router/` — Go, ~260 LOC | Файл | Назначение | |------|------------| | `main.go` | Poll loop, routing, dispatch | | `config.go` | Config struct + YAML loading + env var expansion | | `zulip.go` | Zulip API client (register_queue, get_events, long-poll) | | `ownership.go` | Topic ownership store (RW mutex + JSON persistence) | | `forwarder.go` | HTTP POST к Hermes webhook endpoints | ## Deployment **Compose:** `~/docker/hermes/docker-compose.yml` — сервис `zulip-router` **Env vars** (`~/docker/hermes/.env`): ``` ZULIP_ROUTER_API_KEY= ZULIP_WEBHOOK_TOKEN=zr-secret-2026 ``` **Запуск после заполнения .env:** ```bash cd ~/docker/hermes docker compose up -d --force-recreate ``` **Перезапуск после изменений в профилях:** ```bash docker compose restart hermes-valera hermes-klavdiy ``` ## Hermes профили Валера и Клавдий переведены в webhook-режим: ```yaml platforms: zulip: webhook_port: 8765 webhook_path: /zulip-webhook webhook_token: 'zr-secret-2026' ``` Токен должен совпадать с `ZULIP_WEBHOOK_TOKEN` в `.env`. ## TODO - [ ] Заполнить ZULIP_ROUTER_API_KEY в ~/docker/hermes/.env - [ ] `docker compose up -d --force-recreate` - [ ] Проверить логи: `docker compose logs -f zulip-router` - [ ] Протестировать: написать @Клавдий и @Валера в отдельных топиках - [ ] Проверить ownership: обратиться в топик без @mention — должен отвечать последний бот - [ ] При необходимости: добавить Hermes/Орёл в роутер (отдельная задача)