Подключить модель к мессенджеру просто: получить токен, читать обновления, отвечать текстом. За вечер работает. Через неделю выясняется, что у вас два разных ассистента: тот, что на сайте, помнит ваши проекты, а тот, что в мессенджере, каждый раз знакомится заново.
Это не косметическая проблема. Ассистент, который на одной поверхности знает контекст, а на другой нет, хуже, чем ассистент, который не знает нигде: вы перестаёте понимать, что ему известно, и начинаете дублировать информацию вручную.
Требование, вокруг которого стоит строить всю работу: одно ядро, одна личность, одна память, разные транспорты. Всё остальное — детали, но детали с зубами.
#Что понадобится
- Работающий ассистент с сохраняемой историей и памятью. Если памяти нет, мессенджер её не создаст.
- Отдельный процесс под мессенджер. Не поток внутри веб-приложения: у него другой жизненный цикл, и он должен переживать перезапуск сайта.
- Токен бота и понимание, что он равносилен паролю: попав в чужие руки, он даёт полный доступ к переписке бота.
- Идентификатор вашего аккаунта в мессенджере — числовой, а не имя пользователя. Имя меняется, идентификатор нет.
#Шаг 1. Развести идентичность и разговор
Две разные вещи, которые постоянно смешивают.
Идентичность — кто пишет. Один человек, у которого есть аккаунт на сайте и аккаунт в мессенджере. Память общая, потому что человек один.
Разговор — где пишет. Личный чат, групповой чат, тема внутри группы, вкладка на сайте. Это разные ленты, и смешивать их нельзя: реплика из рабочей группы не должна оказаться посреди личной переписки.
Отсюда правило: память общая, ленты раздельные. Факт, рассказанный в мессенджере, доступен на сайте. Сообщение из группы не попадает в личную ленту.
#Шаг 2. Построить отображение чата в сессию
Нужна таблица связей: внешний чат и тема — во внутреннюю сессию разговора.
ВНЕШНЯЯ СТОРОНА ВНУТРЕННЯЯ СТОРОНА
личный чат (chat_id) ──▶ сессия «Личное»
│
группа (chat_id) ──▶ сессия «Рабочая группа»
└ тема 1 (thread_id) ──▶ сессия «Тема: релиз»
└ тема 2 (thread_id) ──▶ сессия «Тема: найм»
│
вкладка на сайте ──▶ сессия «Веб»
│
▼
ОБЩАЯ ПАМЯТЬ ВЛАДЕЛЬЦА
(факты, предпочтения,
проекты — одни на всех)
Три правила, которые стоит заложить сразу:
- Уникальность связи. Пара «чат плюс тема» отображается ровно в одну активную сессию. Без ограничения уникальности в базе вы получите две ленты на один чат и не заметите этого месяц.
- Группа никогда не сливается с личным чатом автоматически. Даже если человек один и тот же.
- Фоновая задача пишет в свою сессию. Утренняя сводка, сработавшее напоминание, результат ночной обработки — отдельная лента, а не вклинивание в текущий разговор.
Как устроено хранение истории с временными метками и почему это отдельная задача — в материале про чат с памятью и битемпоральность.
#Шаг 3. Настроить allowlist отправителей
Самый важный шаг, и его нельзя откладывать «на потом»: бот в мессенджере доступен всем, кто узнал его имя.
Чек-лист, который должен быть выполнен целиком до первого запуска на боевом токене:
□ Личные сообщения принимаются только от одного числового
идентификатора владельца. Не от имени пользователя — оно меняется.
□ Групповые чаты работают только по явному списку идентификаторов.
Пустой список означает «ни одна группа», а не «все».
□ Неизвестный отправитель не создаёт сессию и не создаёт запись
в истории. Не «отвечаем отказом» — вообще не заводим ничего.
□ Членство в разрешённой группе не даёт прав владельца.
Любой участник группы может писать, но команды выполняет владелец.
□ Инструменты в группе — отдельный узкий список, независимо
от того, кто прислал сообщение.
□ Токен бота не пишется в логи и не показывается в интерфейсе.
□ При ошибке базы или проверки доступа — отказ, а не пропуск.
Четвёртый пункт — тот, на котором ломаются самодельные реализации. Разрешить группу целиком и считать это авторизацией означает, что личным агентом управляет любой её участник.
В Persona владелец задаётся числовым идентификатором в конфигурации, разрешённые группы — отдельным списком, и это два независимых механизма, а не один. Как ту же задачу решает сторонний фреймворк Hermes Agent, у которого разделение личных сообщений и групп сделано похожим образом, — в разборе Telegram-allowlist в Hermes Agent.
#Шаг 4. Ограничить инструменты по поверхности
Групповой чат — поверхность, где текст пишут посторонние. Любая их реплика попадает в контекст, а значит может повлиять на то, что модель решит вызвать.
Практическое решение: в группе доступен минимальный набор, и он задан явным списком, а не выведен из класса риска. В Persona это ровно один инструмент — поиск в сети. Не «все читающие»: универсальный HTTP-инструмент принимает произвольные заголовки и тело, а прямое открытие страницы обходит проверку адреса, поэтому оба остаются вне группы.
Отдельно про чужие реплики в контексте: их стоит обрамлять явной меткой как недоверенный внешний текст. Это не защита, а снижение частоты — но снижение заметное.
Общая рамка распределения прав по классам и поверхностям — в руководстве по белым спискам и правам по умолчанию.
#Шаг 5. Подтверждение на необратимые действия
Мессенджер добавляет удобную вещь — кнопки. Их стоит использовать по назначению: вызовы, которые выполняют код или удаляют данные, не выполняются из текста, а паркуются и ждут нажатия.
Два требования к реализации:
- Кнопка несёт только непрозрачный идентификатор. Не имя инструмента и не аргументы: полезная нагрузка кнопки приходит от клиента и не должна определять, что именно выполнится. Имя и аргументы перечитываются из строки в базе по этому идентификатору.
- У отложенного действия есть срок жизни. Кнопка, нажатая через сутки, не должна выполнить команду, о которой все забыли.
Так это и сделано у нас: вызовы классов «выполнение» и «разрушение», пришедшие с поверхности мессенджера, не выполняются на месте, а превращаются в карточку с двумя кнопками, и если подтверждать негде — действие не выполняется вовсе.
#Шаг 6. Дедупликация и доставка
Мессенджер доставляет обновления с гарантией «хотя бы один раз». Перезапуск процесса в середине обработки приведёт к тому, что то же сообщение придёт снова.
- Каждое обновление имеет номер. Обработка помечается в базе до начала работы, повторное обновление с тем же номером отбрасывается.
- Ответ и запись в очередь отправки — в одной транзакции. Иначе после сбоя вы получите либо ответ, которого нет в истории, либо запись в истории без отправленного ответа.
- Один читатель обновлений. Два параллельных процесса, читающих одну очередь, дадут двойные ответы.
#Проверка результата
Подключение считается готовым, когда проходят шесть проб:
- Факт, рассказанный в мессенджере, вспоминается на сайте.
- Сообщение от постороннего в личку не создаёт ни сессии, ни записи в истории.
- Участник разрешённой группы не может заставить агента выполнить команду.
- Запрос на удаление из мессенджера приводит к карточке с кнопками, а не к удалению.
- Перезапуск процесса в середине ответа не создаёт второго вопроса в истории и не теряет завершённый ответ.
- Реплики из группы не появляются в личной ленте.
Пятая проба неудобна в исполнении — придётся убить процесс в нужный момент, — и именно она отделяет работающее подключение от демонстрационного.
#Что может пойти не так
Бот не видит сообщений в группе. Это ожидаемое поведение: у ботов в группах ограниченный доступ к сообщениям, и без изменения настроек бот получает в основном упоминания, ответы и команды. Не считайте это поломкой и не пытайтесь обойти — объясните в интерфейсе.
Ожидание, что бот прочитает старую переписку. Не прочитает: обычный бот получает только те обновления, которые пришли после подключения. Ретроспективный импорт истории обещать нельзя, и это стоит писать прямо в описании, а не выяснять по жалобам.
Стриминг превращается в спам. Отправлять каждый фрагмент ответа отдельным сообщением нельзя. Работает редактирование одного сообщения раз в секунду с небольшим.
Токен утёк в лог. Проверьте, что он не пишется при ошибках подключения, — это самое частое место утечки.
Одна сессия на все группы. Симптом — обсуждение из одной группы всплывает в другой. Лечится ограничением уникальности в таблице связей.
Мессенджер живёт внутри веб-процесса. Перезапуск сайта роняет бота, а тяжёлый цикл опроса мешает отдавать страницы. Отдельный процесс — не архитектурная роскошь, а условие работоспособности.
Напоминания приходят не в то время. Обычно дело в часовом поясе, привязанном к сессии, а не к человеку; разбор — в материале про расписание на естественном языке.
#Когда мессенджер не нужен
Если вы работаете с ассистентом только за компьютером — не подключайте. Мессенджер добавляет отдельный процесс, отдельную поверхность атаки и отдельный набор ошибок доставки, а взамен даёт удобство, которое нужно не всем.
Если у вас нет памяти, общей между поверхностями, — сначала сделайте её. Иначе вы получите второго ассистента, не знающего того, что знает первый.
Если вы хотите пустить в бота коллег или семью — учитывайте, что в Persona доступ к личному ассистенту устроен как один владелец плюс список разрешённых групп, а не как многопользовательская система с ролями. Общая картина того, что здесь работает, а что остаётся проектом, — в разборе агентной архитектуры Persona.
#Частые вопросы
#Может ли бот прочитать мою переписку за прошлый год?
Нет. Обычный бот получает только обновления, пришедшие после того, как его подключили, и в группах видит ограниченный набор сообщений. Ретроспективный импорт истории через обычный интерфейс ботов недоступен, и обещать его нельзя.
#Как сделать так, чтобы память была общая с сайтом?
Хранить память отдельно от лент разговоров и привязывать её к человеку, а не к чату. Тогда чат в мессенджере, вкладка на сайте и фоновая задача читают одно хранилище фактов, оставаясь при этом разными лентами.
#Безопасно ли добавлять бота в групповой чат?
Только при выполнении двух условий: группа внесена в явный список разрешённых, и членство в ней не даёт прав владельца. Плюс узкий набор инструментов на групповой поверхности — исходя из того, что текст там пишут посторонние.
#Что будет, если процесс упадёт в середине ответа?
При правильной реализации — ничего страшного: обновление помечается обработанным по своему номеру, поэтому повторная доставка не создаст второй вопрос, а запись ответа и постановка его в очередь отправки происходят одной транзакцией. Без этого вы получите дубли и потерянные ответы.
#Нужен ли отдельный процесс под мессенджер?
Практически — да. Внутри веб-процесса цикл опроса мешает обслуживать запросы, а перезапуск сайта роняет бота. Для небольшой установки процесс мессенджера и фоновый обработчик можно объединить, но отделить их от веб-приложения стоит.
#Как ограничить, кто может писать боту?
Числовым идентификатором владельца для личных сообщений и явным списком идентификаторов для групп. Имя пользователя для этого не годится: оно меняется и может быть занято другим человеком.
#Вывод
Работающее подключение мессенджера держится на четырёх вещах: общая память при раздельных лентах, allowlist по числовым идентификаторам, узкий набор инструментов на групповой поверхности и дедупликация обновлений по номеру.
Если у вас пока нет общей памяти — начните с неё, иначе получите второго ассистента вместо второй двери к первому. И проверьте подключение шестью пробами, включая неудобную: убейте процесс посреди ответа и посмотрите, что окажется в истории.