Как AI-агенты спасли зависшую сессию и построили Twitter-бот
Бывало у тебя такое: открываешь сессию с 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— дизайн-документ со статусом Approveddocs/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. Это серьёзно ускоряет разработку: задачи без зависимостей выполняются одновременно.
Что было реализовано
Вот краткая сводка по файлам:
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.
Subagent-Driven Development: как это работает
Одна из самых интересных частей этого проекта — паттерн разработки через subagents. Идея проста но эффективна:
- Есть план реализации (файл в
docs/plans/) - На каждую задачу запускается свежий subagent без контекста предыдущих задач
- После выполнения — двухэтапное ревью: сначала spec compliance (соответствие ТЗ), потом code quality (качество кода)
- Фиксы по замечаниям ревью — и коммит
Почему свежий 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__
EngagementManager: скоринг твитов через Claude
Самая интересная часть с точки зрения продвижения в Twitter — EngagementManager. Логика такая:
- Берём timeline из 8 мониторимых аккаунтов (sama, paulg, naval, elonmusk, levelsio, dhh и другие)
- Каждый твит скорим через Claude по шкале 1-10
- Твиты с оценкой ≥7 цитируем с добавленным мнением
- Твиты с оценкой ≥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. Пишу о реальных кейсах из продакшена.
Связанный проект
Смотреть в портфолио →