П/ВИН

Как AI-агенты спасли зависшую сессию и построили Twitter-бот

·8 мин чтения

Бывало у тебя такое: открываешь сессию с AI-ассистентом, ждёшь минут двадцать — а там тишина? Курсор мигает, прогресс-бар застыл, и ты не понимаешь — то ли что-то делается, то ли всё давно умерло. Именно это случилось у меня в проекте twitter-manager во время brainstorm-сессии по улучшению сервиса. Но вместо того чтобы впасть в панику и потерять всё наработанное, история закончилась интересно: 107 проходящих тестов, несколько новых модулей и куча инсайтов о том, как правильно организовать разработку с AI-агентами.

В этой статье я подробно разберу что произошло, почему сессия «зависла», как мы восстановили контекст и запустили параллельную реализацию через subagents. Плюс — технические детали самого проекта, которые могут быть полезны если ты строишь что-то похожее.

Контекст проекта twitter-manager

twittermanager — это автономный Twitter-бот, заточенный под англоязычную аудиторию. Задача проекта: не просто постить контент по расписанию, а реально продвигаться в Twitter — цитировать интересные твиты с добавленной ценностью, лайкать релевантный контент, отвечать на упоминания, следить за качеством вовлечённости.

Стек проекта:

  • Python — основной язык бота
  • Playwright (через browser_client.py) — браузерная автоматизация для Twitter, потому что официальный API давно стал золотым
  • SQLite — локальная БД для хранения состояния, истории действий, rate limit-ов
  • Claude API — для генерации контента и скоринга твитов
  • Telegram — уведомления о работе бота
  • APScheduler — планировщик задач

Архитектура разбита на модули: browser_client.py, engagement.py, mention_monitor.py, health.py, notifier.py, content_pipeline.py, main.py. Каждый модуль — отдельная зона ответственности. TDD как основной подход: сначала тесты, потом реализация.

Что значит «сессия зависла»

Вот что реально произошло. У меня была открытая сессия с Claude, где шёл brainstorm по улучшениям сервиса. Прошло 20 минут — никакой активности. UI показывал что сессия активна, но никаких сообщений.

Диагноз оказался простым: сессия зависла на AskUserQuestion — это когда AI-агент задаёт вопрос пользователю через специальный инструмент, но промпт либо не отрисовался в UI, либо потерялся где-то в очереди. Агент честно ждал ответа, которого физически не мог получить.

Что важно понять: ничего не потеряно. Это ключевой момент при работе с AI-агентами в связке с git. К тому моменту оба артефакта предыдущей работы уже были закоммичены:

5d7a12e docs: add design doc — engagement-and-reliability
027d3a1 docs: add implementation plan — engagement-and-reliability

Файлы:

  • docs/plans/2026-03-02-engagement-and-reliability-design.md — дизайн-документ со статусом Approved
  • docs/plans/2026-03-02-engagement-and-reliability-plan.md — план реализации с 11 задачами

Правило номер один при работе с AI-агентами: документируй и коммить промежуточные результаты. Дизайн-сессия — отдельный коммит. План — отдельный коммит. Реализация каждой задачи — отдельный коммит. Тогда любой «зависон» — это просто пауза, а не потеря работы.

Восстановление и запуск реализации

Решение было простым: закрыть зависшую сессию (Ctrl+C в терминале), открыть новую и загрузить контекст из git. Именно так и сделали.

Дальше я дал команду реализовывать план через агентов. Здесь включился интересный паттерн — executing-plans skill с параллельным запуском через git worktree.

Параллельное выполнение через worktree

Вместо последовательного выполнения 11 задач, агент разбил их на 4 группы, которые можно выполнять независимо, и запустил параллельно в изолированных git worktree:

# Каждый агент работает в своём worktree
git worktree add /tmp/agent-1-branch feature/browser-client
git worktree add /tmp/agent-2-branch feature/engagement
git worktree add /tmp/agent-3-branch feature/mention-monitor
git worktree add /tmp/agent-4-branch feature/health-monitoring

После завершения — merge в master через cherry-pick. Это серьёзно ускоряет разработку: задачи без зависимостей выполняются одновременно.

Git worktree документация

Что было реализовано

Вот краткая сводка по файлам:

browser_client.py — добавили persistent context (сессия сохраняется между запусками), retry-декоратор для нестабильных сетевых операций, новые методы:

async def get_user_timeline(self, username: str, count: int = 20) -> list[dict]:
    """Получить последние твиты пользователя"""
    ...
 
async def like_tweet(self, tweet_id: str) -> bool:
    """Лайкнуть твит"""
    ...
 
async def get_mentions(self, since_id: str = None) -> list[dict]:
    """Получить упоминания"""
    ...

health.py — новый модуль для мониторинга здоровья бота:

class HealthMonitor:
    def record_job_success(self, job_name: str) -> None:
        """Записать успешный запуск джоба"""
        self.db.execute(
            "INSERT OR REPLACE INTO job_health VALUES (?, ?, 0, 0)",
            (job_name, datetime.now(timezone.utc).isoformat())
        )
 
    def check_stale(self, job_name: str, max_age_hours: int = 2) -> bool:
        """Проверить, не завис ли джоб"""
        row = self.db.execute(
            "SELECT last_success FROM job_health WHERE job_name = ?",
            (job_name,)
        ).fetchone()
        if not row:
            return True
        last = datetime.fromisoformat(row[0])
        age = datetime.now(timezone.utc) - last
        return age.total_seconds() > max_age_hours * 3600

Один нюанс на который потратили время: datetime.utcnow() deprecated в Python 3.12+. Правильный способ:

# Плохо (deprecated)
datetime.utcnow()
 
# Хорошо
datetime.now(timezone.utc)

При этом важно следить за консистентностью: если храним timezone-aware datetime в БД, то и при чтении должны получать aware объект. datetime.fromisoformat() правильно парсит строки с +00:00.

Python datetime документация

Subagent-Driven Development: как это работает

Одна из самых интересных частей этого проекта — паттерн разработки через subagents. Идея проста но эффективна:

  1. Есть план реализации (файл в docs/plans/)
  2. На каждую задачу запускается свежий subagent без контекста предыдущих задач
  3. После выполнения — двухэтапное ревью: сначала spec compliance (соответствие ТЗ), потом code quality (качество кода)
  4. Фиксы по замечаниям ревью — и коммит

Почему свежий subagent на каждую задачу? Потому что накопленный контекст длинной сессии начинает влиять на качество — агент «помнит» ранние решения и может неосознанно воспроизводить их ошибки. Свежий контекст = чистый взгляд.

Пример из реализации TelegramNotifier:

class TelegramNotifier:
    def __init__(self, token: str, chat_id: str, topic_id: int | None = None):
        self.token = token
        self.chat_id = chat_id
        self.topic_id = topic_id
        self.logger = logging.getLogger(__name__)  # правильно: __name__, не хардкод
 
    def send_message(self, text: str) -> bool:
        url = f"https://api.telegram.org/bot{self.token}/sendMessage"
        payload = {"chat_id": self.chat_id, "text": text, "parse_mode": "HTML"}
        if self.topic_id:
            payload["message_thread_id"] = self.topic_id
        try:
            resp = requests.post(url, json=payload, timeout=10)
            resp.raise_for_status()  # важно: проверяем HTTP статус
            return True
        except Exception as e:
            self.logger.error("Telegram send failed: %s", e)
            return False

Код ревью поймал два важных момента:

  • Изначально send_message игнорировал HTTP response status — молча «успешно» отправлял даже при 4xx/5xx
  • Logger использовал хардкод строку вместо __name__

Telegram Bot API документация

EngagementManager: скоринг твитов через Claude

Самая интересная часть с точки зрения продвижения в Twitter — EngagementManager. Логика такая:

  1. Берём timeline из 8 мониторимых аккаунтов (sama, paulg, naval, elonmusk, levelsio, dhh и другие)
  2. Каждый твит скорим через Claude по шкале 1-10
  3. Твиты с оценкой ≥7 цитируем с добавленным мнением
  4. Твиты с оценкой ≥6 лайкаем (с учётом дневного лимита)
async def run_quote_tweets(self) -> int:
    quoted = 0
    for account in MONITORED_ACCOUNTS:
        if quoted >= MAX_QUOTES_PER_RUN:  # макс 3 за запуск
            return quoted
        tweets = await self.client.get_user_timeline(account, count=10)
        for tweet in tweets:
            if self.db.is_processed(tweet['id']):
                continue
            score = await self._score_tweet(tweet)
            self.db.mark_processed(tweet['id'])  # помечаем до действия
            if score >= QUOTE_THRESHOLD:
                opinion = await self._generate_opinion(tweet)
                await self.client.quote_tweet(tweet['id'], opinion)
                quoted += 1
    return quoted

Интересный trade-off: mark_processed вызывается до выполнения действия. Это осознанное решение — лучше пропустить один твит чем цитировать его дважды из-за retry. Spec compliance ревью зафлажил это как отклонение от спека, но после обсуждения оставили — это правильный продакшн-выбор.

Ещё один фикс из code quality ревью — логика достижения лимита:

# Было: continue (неправильно — продолжает цикл)
if likes_today >= MAX_LIKES_PER_DAY:
    continue
 
# Стало: return (правильно — выходим полностью)
if likes_today >= MAX_LIKES_PER_DAY:
    return likes_today

Мелочь, но важная: с continue бот проверял каждый следующий твит даже при достигнутом лимите, делая лишние запросы к API.

Playwright Python документация

Итоги: 107 тестов, 0 предупреждений

После завершения всех 11 задач финальный прогон тестов дал: 107 passed, 0 warnings.

Что было проблемой в процессе:

RotatingFileHandler тестlogging.basicConfig() не добавляет хендлеры если logging уже сконфигурирован (pytest настраивает его первым). Решение: явно проверять что нужный хендлер есть в root_logger.handlers.

timezone-aware vs naive datetime — при сравнении дат обе стороны должны быть либо aware либо naive. Смешивать нельзя — получишь TypeError. Решение: везде использовать datetime.now(timezone.utc).

Параллельные worktree конфликты — при merge из нескольких веток в master надо аккуратно разрешать конфликты в main.py, который правили несколько агентов параллельно.

Выводы и уроки

Первый и главный урок: git — это страховочная сеть для AI-разработки. Любой «зависон», любая потеря сессии — не катастрофа если ты коммитишь промежуточные артефакты. Дизайн-документ — коммит. План — коммит. Каждая задача — коммит. Можно возобновить работу в любой момент с любого места.

Второй урок: двухэтапное ревью реально работает. Spec compliance review и code quality review — это не бюрократия, а фильтр. За два прогона поймали: игнорирование HTTP статуса, хардкод строк логгера, неправильный continue вместо return, неиспользуемые импорты, проблемы с timezone. Всё это мелкие вещи которые в продакшне вылились бы в баги.

Третий урок: параллельные агенты через git worktree — мощный инструмент когда задачи независимы. 11 задач последовательно заняли бы условно X времени, параллельно — X/4. Ключевое условие: задачи должны трогать разные файлы, иначе merge-конфликты съедят выигрыш.

Четвёртый урок: fresh context per task — контр-интуитивно но правильно. Кажется что агент с полным контекстом всего проекта напишет лучший код. На практике длинный контекст замусоривается промежуточными решениями и debugging-треками. Свежий агент с чётким ТЗ и примерами из кодовой базы показывает более предсказуемые результаты.

Проект twitter-manager продолжает развиваться — впереди улучшение логики продвижения, более умный скоринг контента и интеграция с аналитикой. Но этот спринт наглядно показал: правильно выстроенный процесс разработки с AI-агентами позволяет двигаться быстро не теряя в качестве. Даже если одна из сессий зависла на AskUserQuestion.

Паша Вин
Паша Вин

Фулстек-разработчик, строю SaaS-продукты и автоматизации на Next.js, Python и AI. Пишу о реальных кейсах из продакшена.

Связанный проект

Смотреть в портфолио →