5.2 KiB
Zulip Router
Последнее обновление: 2026-06-16
Цель
Event-роутер между Zulip и balda-ботами (Валера, Клавдий). Решает проблему конкурентного получения событий — сейчас оба бота через Events API видят все сообщения одновременно.
Проблема (root cause)
- Каждый balda-бот регистрирует собственную event queue в Zulip Events API
- Без роутера оба получают все сообщения, и каждый может ответить на @mention другого
extractOtherMention()патч в коде — костыль, не решает проблему полностью
Как сейчас (до роутера)
Zulip
├── Events API ← Валера (valera-bot, poll на :8091)
└── Events API ← Клавдий (claudio-bot, poll на :8092)
└── Webhook → оба (outgoing webhook bot_type=3 через @mention)
Архитектура (с роутером)
Zulip Event Queue
↓ poll (отдельный бот-роутер или reuse одного из существующих)
[zulip-router] (~/Docker/zulip-router/)
├── @Валера / @Valera → POST http://balda-agent-valera-1:8091/zulip/webhook
├── @Клавдий / @Klavdiy → POST http://claudio-agent-claudio-1:8092/zulip/webhook
└── (default / unknown) → POST http://balda-agent-valera-1:8091/zulip/webhook
↕ ownership: stream+topic → bot (TTL 24h, persisted to /data/ownership.json)
Важно: таргеты — не Hermes инстансы, а balda-боты (Go бинарники). У каждого уже есть webhook endpoint /zulip/webhook.
Правила маршрутизации
@Валераили@Valeraв тексте → Валера (+ ownership обновляется)@Клавдийили@Klavdiyв тексте → Клавдий (+ ownership обновляется)- Топик уже у кого-то → туда же (TTL refreshed)
- Новый топик без @mention → Валера (default)
resetбез @mention → только владельцу топика
Что меняется в balda
После деплоя роутера у обоих ботов нужно отключить Events API polling в config.yaml:
zulip:
events_polling:
enabled: false # ← было true
После этого сообщения приходят только через webhook (от роутера).
Перезапуск: docker-compose up -d (в каждом compose).
Код
Репозиторий: ~/Developer/zulip-router/ — Go, ~260 LOC (план)
Deploy: ~/Docker/zulip-router/docker-compose.yaml
| Файл | Назначение |
|---|---|
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 к balda webhook endpoints |
Deployment
Compose: ~/Docker/zulip-router/docker-compose.yaml
Перед запуском: создать бота-роутера в Zulip
Нужен отдельный бот (не valera-bot, не claudio-bot).
- В Zulip: Settings → Bots → Add a new bot
- Тип: Outgoing webhook
- Имя:
zulip-router(или любое) - Bot email: скопировать (понадобится в .env)
- API key: скопировать (понадобится в .env)
- Endpoint URL: не заполнять (роутер сам поллит, webhook от Zulip ему не нужен)
- Сохранить
Env vars
Создать ~/Docker/zulip-router/.env:
ZULIP_ROUTER_BOT_EMAIL=zulip-router-bot@zulip.qentra.top
ZULIP_ROUTER_API_KEY=<api-key из шага 5>
# Куда форвардить
VALERA_WEBHOOK=http://balda-agent-valera-1:8091/zulip/webhook
CLAUDIO_WEBHOOK=http://claudio-agent-claudio-1:8092/zulip/webhook
Запуск
cd ~/Docker/zulip-router
docker-compose up -d
docker logs zulip-router --tail 20 # проверить что poll loop запущен
## TODO
- [ ] Написать код роутера (~260 LOC)
- [ ] Создать compose `~/Docker/zulip-router/docker-compose.yaml`
- [ ] Создать бота-роутера в Zulip (или реюзнуть valera-bot/claudio-bot), получить API key
- [ ] `docker-compose up -d`
- [ ] Проверить логи: `docker logs zulip-router --tail 50`
- [ ] Отключить `events_polling.enabled` у Валеры → `docker-compose up -d`
- [ ] Отключить `events_polling.enabled` у Клавдия → `docker-compose up -d`
- [ ] Протестировать: @Клавдий и @Валера в разных топиках
- [ ] Проверить ownership: сообщение в топик без @mention → последний бот
- [ ] Удалить `extractOtherMention()` патч из кода (больше не нужен)