Self-healing боты: система самовосстановления AI агентов
Три часа ночи. Бали. WhatsApp-транслятор упал уже в четвёртый раз за месяц. Клиенты не получают сообщений, менеджеры паникуют, сделки зависают. Я перезапускаю контейнер вручную, прописываю в заметках «разобраться с этим завтра», ложусь спать. Завтра не разбираюсь — завтра прилетает другой пожар. Так прошло шесть месяцев, пока я не понял: проблема не в боте, а в том, что у меня нет системы самовосстановления. Сегодня расскажу, как мы построили self-healing архитектуру, которая за следующие шесть месяцев выполнила 247 автовосстановлений — и ни разу не разбудила меня среди ночи.
Почему боты падают и почему это неизбежно
Принято думать, что бот — это что-то очень надёжное. Запустил, настроил, забыл. В реальности AI агент — живой программный организм, который работает в постоянно меняющейся среде. Он зависит от десятков внешних сервисов: API мессенджеров, баз данных, сторонних провайдеров, файловых систем. Каждое из этих звеньев может сломаться в любой момент.
За полтора года работы с AI агентами я собрал личную классификацию причин падений:
- Потеря соединения — сетевые прерывания, таймауты, смена IP. Бот пытается что-то сделать, получает Connection Refused и зависает в ожидании.
- Ошибки API — внешний сервис вернул 429 (rate limit), 500 (серверная ошибка) или неожиданный формат ответа. Бот не знает что делать и останавливается.
- Бесконечные циклы — логическая ошибка в коде или неожиданные данные загоняют бота в петлю. CPU упирается в 100%, полезной работы ноль.
- Переполнение памяти — бот накапливает данные в памяти, не освобождает их, через несколько часов система убивает процесс по OOM killer.
- Протухшие токены — OAuth токены, API ключи, сессии истекают. Бот начинает получать 401 Unauthorized и ничего не делает.
- Конкурентные конфликты — два экземпляра одного бота запустились одновременно, пишут в одну базу, создают дубли или блокируют друг друга.
- Дисковое переполнение — логи, временные файлы, кэши разрастаются. Бот пытается записать что-то на диск и получает No space left on device.
Ни одна из этих проблем не является признаком «плохого кода». Это нормальная эксплуатационная реальность. Вопрос только в том, кто будет её разруливать: дежурный DevOps в три часа ночи или автоматическая система самовосстановления.
Традиционный подход и его цена
До внедрения self-healing у нас была стандартная схема: Алиса — бот-мониторинг — следила за состоянием всех сервисов и писала мне в Telegram, когда что-то шло не так. Звучит разумно. На практике получалось вот что.
Алиса пишет: «eZee Scrape упал». Я вижу сообщение через час, когда просыпаюсь. Захожу на сервер, смотрю логи, перезапускаю контейнер. Ещё час данные были недоступны — значит, утренний отчёт по занятости вилл пришёл без обновлений. Менеджер не понял почему, позвонил гостю вручную. Цепочка ошибок из одной поломки.
Алиса — это метафора для сотрудника, который умеет только жаловаться, но не решать. Она диагностирует, информирует, ждёт. Прекрасный мониторинг и бесполезный партнёр. Стоимость такого подхода складывается из:
- Время реакции: от момента падения до восстановления — в среднем 45 минут при ручном вмешательстве
- Накопленный стресс: постоянные ночные и утренние алерты разрушают качество работы
- Цепные потери: каждая минута простоя бота — пропущенные лиды, несинхронизированные данные, недовольные клиенты
- Контекстные переключения: разработчик, которого выдёргивают на «перезапусти контейнер», теряет час глубокой работы
Когда я подсчитал реальные потери за квартал, цифра оказалась неприятной. WhatsApp-транслятор падал раз в три дня. За квартал — порядка 30 инцидентов. Умножаем на 45 минут реакции, добавляем косвенные потери. Это сотни тысяч рублей в год, которые съедала неавтоматизированная инфраструктура.
Архитектура self-healing системы
Self-healing — это не один инструмент, а многоуровневая система, где каждый уровень обрабатывает свой класс проблем. Представьте это как луковицу: внешние слои ловят простые транзиентные ошибки, внутренние — более серьёзные структурные проблемы.
Уровень 1: Health check endpoints
Каждый бот в нашей системе обязательно реализует HTTP endpoint на /health. Это простой веб-сервер, который возвращает JSON с текущим состоянием агента:
Пример ответа /health:
status: "ok" или "degraded" или "critical"
uptime: время работы в секундах
last_heartbeat: временная метка последнего успешного действия
queue_size: размер очереди задач
memory_mb: текущее потребление памяти
errors_last_hour: количество ошибок за последний час
Ключевая деталь — это не просто «жив или нет». Бот может технически работать, но находиться в деградированном состоянии: обрабатывать задачи в 10 раз медленнее, накапливать очередь, получать повторяющиеся ошибки. Health check позволяет детектировать такие состояния до того, как они превратятся в полный отказ.
Уровень 2: Watchdog процесс
Watchdog — это независимый Python-процесс, который каждые 30 секунд опрашивает все зарегистрированные боты через их /health endpoints. Он живёт в отдельном контейнере и намеренно изолирован от остальной системы: если упадёт любой из ботов, watchdog продолжит работать.
Логика watchdog простая, но точная:
- Нет ответа на /health в течение 30 секунд — пометить как «не отвечает»
- Не отвечает 2 минуты подряд — инициировать автоперезапуск
- Статус «critical» — немедленно инициировать диагностику
- Статус «degraded» три проверки подряд — создать задачу в очереди на мягкое восстановление
- Ошибок за час больше порога — активировать circuit breaker для проблемного API
Watchdog ведёт историю состояний каждого бота. Это позволяет отличить транзиентную ошибку от системного сбоя. Один таймаут — не повод для паники. Пять подряд — уже паттерн.
Уровень 3: Circuit breaker
Circuit breaker — паттерн из мира распределённых систем, который предотвращает каскадные отказы. Принцип прост: если внешний API возвращает ошибки слишком часто, мы временно прекращаем к нему обращаться.
У каждого circuit breaker три состояния:
- Closed (нормальная работа) — запросы проходят свободно, ошибки считаются
- Open (отключено) — запросы блокируются, возвращается заранее заготовленный fallback ответ. Открывается автоматически после 5 ошибок подряд или если процент ошибок превысил 50% за последние 60 секунд
- Half-open (тестирование) — через 30 секунд после открытия пропускается один тестовый запрос. Если успешный — breaker закрывается. Если нет — снова открывается
Это особенно важно для работы с внешними API вроде Telegram Bot API, Google Sheets API, eZee PMS. Когда у них происходит деградация сервиса, боты без circuit breaker начинают накапливать очереди неудавшихся запросов, загружают CPU, могут вызвать собственный OOM. С circuit breaker они просто «замирают» до восстановления внешнего сервиса.
Уровень 4: Exponential backoff
Когда бот получает временную ошибку (сеть мигнула, API вернул 503), он не должен немедленно повторять запрос. Немедленный повтор только нагружает уже перегруженный сервис. Exponential backoff — это стратегия нарастающих пауз между попытками.
Первая попытка неудачна — ждём 1 секунду. Вторая неудача — 2 секунды. Третья — 4. Четвёртая — 8. И так до максимума в 5 минут. Добавляем случайный jitter (±20% от паузы), чтобы множество ботов не синхронизировались в «шторм» одновременных повторов.
После шести неудачных попыток задача считается не восстановимой через backoff — передаётся на следующий уровень: полный перезапуск контейнера.
Инфраструктура: Docker, supervisord и systemd
Self-healing на уровне кода — это хорошо, но недостаточно. Нужна поддержка на уровне инфраструктуры. Мы используем трёхуровневый стек автоматического восстановления.
Docker restart policies
Первый и самый простой уровень — политики перезапуска Docker контейнеров. Каждый бот запускается с политикой --restart=unless-stopped. Это значит: если процесс в контейнере завершился с ненулевым кодом, Docker автоматически перезапускает контейнер через небольшую паузу (до 10 секунд).
Docker делает это быстро и без участия watchdog. Большинство «упал и встал» инцидентов решаются именно здесь — за 5-10 секунд, полностью незаметно.
Supervisord внутри контейнеров
Для ботов с несколькими внутренними процессами используем supervisord. Например, бот-транслятор WhatsApp запускает одновременно: основной процесс обработки сообщений, внутренний веб-сервер для /health, воркер очереди задач. Supervisord следит за всеми тремя, перезапускает упавший компонент независимо от остальных.
Это позволяет избежать «перегрузки с пушки»: если упал только веб-сервер /health, не нужно перезапускать весь контейнер и терять состояние основного процесса.
systemd на хост-машине
Третий уровень — systemd сервисы на хост-машине. Сам Docker daemon и watchdog-процесс управляются через systemd с автозапуском. Если хост-машина перезагрузилась (обновление ядра, аварийная остановка питания), systemd автоматически поднимет Docker и все контейнеры в правильном порядке, соблюдая зависимости между сервисами.
Кастомный Python watchdog
Поверх всего этого работает наш Python watchdog — он делает то, что не умеют стандартные инструменты: принимает решения на основе бизнес-логики. Стандартный Docker restart просто перезапустит упавший контейнер. Watchdog понимает: если eZee Scraper упал одновременно с PostgreSQL, возможно, проблема в базе данных — нужно сначала восстановить БД, а потом Scraper.
Watchdog хранит граф зависимостей между сервисами и применяет его при восстановлении. Это предотвращает ситуацию, когда сервис перезапускается успешно, но сразу падает снова из-за недоступной зависимости.
Как работает автоматическое восстановление: сценарии
Разберём конкретные сценарии, которые система обрабатывает автоматически.
Сценарий 1: бот завис, не отвечает на health check
Watchdog фиксирует отсутствие ответа на /health. Через 30 секунд — повторная проверка. Ещё раз через 30. После 4 неудачных проверок (2 минуты молчания) watchdog:
- Снимает метрику: фиксирует в базе факт зависания с временной меткой и последним известным состоянием
- Посылает SIGTERM процессу — корректное завершение. Ждёт 10 секунд
- Если не завершился — SIGKILL. Жёсткое убийство
- Docker restart policy поднимает контейнер заново
- Watchdog ждёт 30 секунд и проверяет /health нового экземпляра
- Если всё ок — инцидент закрыт, в Telegram приходит тихое уведомление: «eZee Scraper: автовосстановлен в 03:47»
Сценарий 2: протухший OAuth токен
Google Sheets API начинает возвращать 401 Unauthorized. Circuit breaker фиксирует серию ошибок. Watchdog определяет паттерн: ошибки аутентификации, не сетевые проблемы. Запускает специализированный recovery action:
- Обращается к vault за свежими credentials
- Запускает процедуру обновления токена через refresh token
- Записывает новый токен в защищённое хранилище
- Посылает боту сигнал перечитать конфигурацию (без полного перезапуска)
- Проверяет что следующий запрос к Sheets прошёл успешно
Весь процесс занимает около 20 секунд. Пользователь не видит ничего — синхронизация просто пропускает один цикл и продолжает работать.
Сценарий 3: переполнение диска
Бот начинает писать ошибки OSError: [Errno 28] No space left on device. Health check возвращает статус «critical» с кодом причины «disk_full». Watchdog:
- Запускает скрипт очистки: удаляет логи старше 7 дней, временные файлы, Docker images без тегов
- Сжимает архивные логи (gzip)
- Проверяет свободное место. Если стало достаточно — бот продолжает работу
- Если после очистки всё равно мало места — эскалирует: отправляет алерт с полным отчётом по использованию диска
Сценарий 4: бесконечный цикл, CPU 100%
Watchdog мониторит не только HTTP endpoints, но и системные метрики контейнеров через Docker stats API. Если CPU контейнера держится выше 90% более 5 минут при нулевом прогрессе задач (очередь не уменьшается, нет новых heartbeat в логах) — это признак зависания в цикле.
В этом случае watchdog собирает stacktrace (py-spy для Python процессов), сохраняет его в базу для последующего анализа, затем перезапускает контейнер. Stacktrace позволяет разработчику потом разобраться, на каком месте в коде бот завис — без необходимости воспроизводить проблему вручную.
Система оповещений: умные алерты вместо спама
Классическая проблема мониторинга — alert fatigue. Когда алертов слишком много, люди перестают на них реагировать. Мы прошли через это: в один период система слала по 30-40 сообщений в день, и я начал их игнорировать.
Теперь система оповещений работает по принципу «молчи, пока не нужна помощь»:
Тихие восстановления
Если система сама справилась с проблемой менее чем за 3 минуты — никакого уведомления. Только запись в лог. Я могу посмотреть дайджест восстановлений раз в день, если интересно.
Мягкие уведомления
Если восстановление заняло от 3 до 15 минут, или потребовалось нестандартное действие (обновление токена, очистка диска) — приходит краткое сообщение в Telegram. Одна строка: что случилось, что сделано, сколько заняло.
Критические алерты с полным контекстом
Если система не смогла восстановиться за 15 минут, или проблема повторяется более трёх раз за час, или произошло что-то, чего watchdog не умеет чинить — приходит развёрнутый алерт:
[CRITICAL] WhatsApp Транслятор — требуется вмешательство
Инцидент начался: 03:47 UTC+8
Попыток восстановления: 3
Последняя ошибка: ConnectionResetError([Errno 104] Connection reset by peer)
Последний успешный heartbeat: 01:23 UTC+8
Stacktrace: [ссылка на файл]
Последние 50 строк лога: [вставлено прямо в сообщение]
Предполагаемая причина: смена IP Bali ISP (исторический паттерн)
Ключевая деталь последнего пункта: watchdog анализирует исторические данные и пытается угадать причину. Не всегда правильно, но часто помогает сократить время диагностики с 20 минут до 2.
Кейс: WhatsApp транслятор и путь от трёх дней до нуля падений
WhatsApp транслятор — бот, который принимает сообщения от клиентов на разных языках (русский, английский, индонезийский), переводит и маршрутизирует менеджерам. Критически важный сервис: если он падает, коммуникация с клиентами разрывается.
До внедрения self-healing бот падал примерно раз в три дня. Иногда через два дня, иногда через четыре — но паттерн был стабильный. Средний даунтайм до восстановления — 45 минут (от падения до момента, когда я замечал алерт, захожу на сервер, разбираюсь, перезапускаю).
Диагностика корневой причины
Первым делом с помощью watchdog мы собрали статистику за месяц. Оказалось:
- 80% падений происходили между 01:00 и 04:00 местного времени
- Ошибка всегда одна и та же: ConnectionResetError
- После перезапуска бот работал нормально ровно до следующей ночи
Версия: балийские ISP проводят ночное обслуживание сети, из-за чего меняется внешний IP сервера. Сессия WhatsApp Web привязана к IP, и при его смене сбрасывается. Бот не умел переподключаться — просто падал с Connection Reset.
Два уровня защиты
Решение оказалось двухуровневым. Первое: добавили в бот логику автоматического переподключения с exponential backoff — при потере соединения пробуем снова через 1, 2, 4, 8, 16 секунд. Второе: добавили watchdog правило специально для этого бота — если видим ConnectionResetError три раза за час, принудительно пересоздаём WhatsApp сессию полностью.
Результат: за следующие шесть месяцев бот не требовал ни одного ручного вмешательства. Watchdog автоматически обрабатывает ночные reconnect в среднем два раза в неделю. Пользователи не замечают ничего — задержка обработки сообщений в момент reconnect составляет 8-12 секунд.
Что система НЕ умеет делать: границы автоматизации
Честный разговор о self-healing невозможен без обсуждения ограничений. Есть класс проблем, которые система намеренно эскалирует человеку, не пытаясь починить самостоятельно.
- Ошибки бизнес-логики. Если бот неправильно считает доходы или неверно классифицирует лиды — это не техническая поломка, а содержательная ошибка. Автоматическая «починка» может усугубить ситуацию.
- Глобальные отказы внешних сервисов. Если Telegram API лежит для всего мира — watchdog это видит (circuit breaker открывается для всех ботов одновременно) и просто ждёт, периодически проверяя доступность. Ничего починить здесь нельзя.
- Аппаратные проблемы сервера. Если умер диск или сгорела сетевая карта — нужен физический доступ. Watchdog присылает критический алерт и останавливается.
- Проблемы безопасности. Если в логах появляются признаки компрометации (неожиданные запросы, изменения файлов) — система не пытается «починить» это самостоятельно. Блокирует подозрительные процессы и ждёт человека.
- Изменения в API провайдеров. Если Booking.com поменял структуру ответа и парсер сломался — нужна правка кода, а не перезапуск контейнера.
Это осознанные границы. Попытка автоматизировать всё без исключений рано или поздно приведёт к ситуации, когда система «чинит» то, что не сломано, или маскирует реальную проблему. Self-healing должен быть умным, а не агрессивным.
Результаты: шесть месяцев в цифрах
Система работает с октября 2025 года. По состоянию на апрель 2026:
- 247 автоматических восстановлений — это все инциденты, которые система обработала без участия человека
- 0 ручных вмешательств ночью — ни одного раза я не вставал ночью чинить упавшего бота за последние 6 месяцев
- Среднее время восстановления: 47 секунд — против 45 минут при ручном подходе. Ускорение в 57 раз
- 14 эскалаций к человеку — это проблемы, которые система правильно определила как «требует человека». Ни одной ложной тревоги
- Экономия: ~120 часов разработчика — оценка времени, которое раньше тратилось на ручную диагностику и перезапуск сервисов
- Uptime ботов: 99.6% — с учётом времени плановых обновлений
Самая неожиданная польза оказалась не в числах, а в психологии. Я перестал постоянно думать «а вдруг что-то упало прямо сейчас». Это освобождает ментальную нагрузку, которая раньше была фоновым шумом каждого дня.
Как начать внедрять self-healing: практические шаги
Если вы хотите внедрить что-то подобное, не нужно строить всё сразу. Вот последовательность, которую я бы рекомендовал:
Шаг 1: Health check endpoints (1-2 дня)
Начните с простого: добавьте /health endpoint в каждый бот. Минимальный вариант — просто возвращать HTTP 200, если процесс жив. Это уже позволяет внешнему инструменту понять, что бот работает. Постепенно обогащайте: добавляйте метрики памяти, времени последнего успешного действия, счётчик ошибок.
Шаг 2: Docker restart policies (1 час)
Убедитесь, что все ваши боты запущены с restart: unless-stopped в docker-compose.yml. Это самый дешёвый и эффективный первый уровень защиты. Большинство «упал и забыл» случаев решается именно этим.
Шаг 3: Простой watchdog (3-5 дней)
Напишите скрипт, который каждую минуту опрашивает /health всех ботов и при отсутствии ответа отправляет алерт в Telegram. Это даст вам видимость без сложной автоматики. Постепенно добавляйте автодействия: сначала только уведомления, потом — автоперезапуск для самых понятных случаев.
Шаг 4: Exponential backoff в коде ботов (2-3 дня)
Пройдитесь по коду каждого бота и добавьте retry логику с backoff для всех внешних вызовов: HTTP запросы, обращения к БД, файловые операции. В Python для этого есть библиотека tenacity, которая делает это элегантно через декораторы.
Шаг 5: Circuit breaker (1 неделя)
Добавьте circuit breaker для критически важных внешних API. Для Python есть библиотека pybreaker. Начните с самых нестабильных интеграций — там, где ошибки API происходят чаще всего.
Каждый шаг даёт ощутимый результат даже без следующего. Не нужно внедрять всё сразу — начните с малого и итерируйте.
Также важно учитывать, что self-healing система — это живой проект, а не разовая настройка. Когда у вас появляются новые боты или интеграции, нужно добавлять их в watchdog, настраивать для них специфические recovery actions, собирать исторические паттерны отказов. Мы поддерживаем «книгу восстановлений» — документ, где описан каждый тип инцидента и как система его обрабатывает. Это помогает при онбординге новых разработчиков и при добавлении новых сервисов.
Если вам интересно посмотреть, как система мониторинга помогает выявлять проблемы до того, как они становятся критическими — читайте о нашем опыте построения системы мониторинга AI агентов. А о том, как бороться со спамом уведомлений от множества ботов — в статье про debounce для AI ассистента.