Когда я начал делать агентов под разные задачи внутри компании, я довольно быстро понял что задача не сводится просто к хорошему промпту.

Промпт важен, но он закрывает только верхний слой, а дальше начинается обычная инженерная работа - контекст, память, доступы, инструменты, состояние, git, фоновые задачи и т.д. и т.п. И если это не проектировать отдельно, агент может нормально справиться с короткой задачей, но начать сыпаться на длинной работе.

Я собрал 5 ошибок/проблем, которые чаще всего били по качеству, стоимости и устойчивости системы. Не как универсальную методологию и не как попытку кого-то учить, а скорее как заметки, которые могут кому то пригодится, да и просто хочется их зафиксировать.

Ошибка 1. Не проектировать память и контекст как отдельную подсистему

Первая большая проблема появилась не сразу. 

Пока задачи были короткими, все было нормально - агент получил вводные, прочитал пару файлов, сходил в браузер, подготовил результат. Но чем больше становилось задач, тем заметнее контекст превращался в свалку.

В одном месте оставались длинные shell-логи, в другом полные снимки браузера, в третьем история диагностики, которая уже не имела отношения к текущей задаче. Формально все это могло быть полезно, поэтому система продолжала тащить этот хвост за собой.

Но в итоге происходили две неприятные вещи:

  • важные решения и правила терялись среди мусора;

  • фоновые задачи начинали стоить слишком дорого просто потому, что каждый раз стартовали с раздутым контекстом.

Самый показательный пример был с cron-сессиями. 

В каждую изолированную фоновую задачу попадал почти полный список доступных инструментов - больше двухсот штук. Это давало около 50 тысяч токенов на один запуск еще до реальной работы.

Потом всплыл другой симптом. Обычный вопрос вроде “сколько потратили токенов?” запускал живой анализ десятков сессий на дорогой модели и стоил около 1,35 миллиона токенов за один ответ.

После этого стало понятно, что память нельзя воспринимать как одну большую коробку, куда складывается все подряд. Нужна отдельная настроенная схема - что хранить, что сжимать, что выносить в файл, а что вообще не тащить дальше.

И по итогу я пришел к более простой и понятной структуре:

  • сырые ежедневные заметки уходят в daily notes;

  • долгосрочные правила и выводы попадают в курируемую память;

  • перед ответами по прошлым решениям агент ищет нужные фрагменты через semantic search, а не перечитывает все подряд;

  • тяжелые фоновые отчеты заранее готовятся дешевой моделью в markdown-файлы;

  • основная сессия читает уже готовый результат, а не каждый раз поднимает аналитику с нуля.

Отдельно пришлось ограничить инструменты для фоновых задач. Если healthcheck нужен только для пары действий, ему не нужен доступ к двум сотням инструментов. То же самое с диагностикой - VNC, CDP, длинные логи и браузерные снимки лучше выносить в отдельные дешевые сессии, чтобы основная reasoning-сессия не раздувалась.

Вот пример по трем фоновым задачам:

Задача

До

После

Экономия

dashboard-label-watchdog

*/15 мин, toolsAllow 200+, timeout 30c

*/30 мин, toolsAllow 5, timeout 60c

~3.7M/день

shell-healthchecks

каждые 10 мин

каждые 30 мин

~565k/день

kill-runaway-sessions

каждые 30 мин

каждый час

~360k/день

Итого

~4.6M/день — 84% фонового потребления

Плюс заранее подготовленные отчеты по токенам снизили стоимость одного статусного запроса примерно в 675 раз.

Главный вывод здесь - если агент работает дольше одного диалога, память и контекст нужно проектировать как отдельную подсистему, иначе расходы растут очень быстро.

Подробнее про оптимизацию расходов токенов написал в этой статье 👈

Ошибка 2. Разрешить агенту переписывать git-историю без отдельного подтверждения

Вторая ошибка была уже не про токены, а про дисциплину разработки.

Когда агент начинает работать с кодом, миграциями и ветками, довольно быстро появляется обычная git-ситуация - локальная история расходится с удаленной. Человек в этот момент обычно останавливается и пытается выяснить кто что запушил, почему ветки разошлись, можно ли аккуратно смержить. Но вот агент, без жесткого правила, может пойти коротким путем и просто перезаписать удаленную ветку и поехать дальше.

В одном из реальных кейсов нужно было провести миграцию между dev, beta и prod. На dev возникла цепочка проблем - локальная master-ветка разошлась с remote, ff-only pull стал невозможен, затем pull в development-ветке запустил rebase и получил конфликты в шести файлах.

Позже, когда часть проблем уже была решена, первый push все равно отклонился из-за параллельного remote update - коллега успел добавить merge-коммит в ту же ветку.

Технически можно было сделать force-push, но он не решает конфликт, а стирает его следы вместе с чужой работой, а для агентской системы это особенно опасно, потому что потом последствия все равно придется разбирать самостоятельно. 

Поэтому я зафиксировал правило - force-push запрещен без отдельного подтверждения. Если rebase получил конфликт - делаем безопасный rebase --abort, возвращаемся в чистое состояние и останавливаемся. Если удаленная ветка изменилась параллельно - делаем обычную интеграцию чужого изменения, а не переписываем историю.

Параллельно миграция была разложена на четкие шаги:

  • принять задачу и входные данные;

  • проверить remote-доступ;

  • прогреть локальные master/development;

  • проверить рабочую ветку;

  • проверить незакоммиченные изменения;

  • отправить изменения;

  • проверить pipeline, страницы и логи;

  • отдельно зафиксировать в отчете, был ли force-push.

Такой workflow выглядит муторным, но только до первого случая, когда он спасет работу.

В итоге та миграция прошла обычными merge-операциями без force-push, а все три окружения вернули HTTP 200 на проверяемых страницах. 

Вывод в итоге такой - опасные shortcuts нельзя оставлять на усмотрение агента. Если действие может стереть чужую работу, оно должно требовать отдельного разрешения и оставлять след в отчете.

Ошибка 3. Делать ставку на браузерную автоматизацию как на основной рабочий путь

Сначала браузерная автоматизация казалась хорошим способом быстро закрывать задачи, где нет удобного API или нормальной интеграции - открыл Chrome, перешел на страницу, нажал кнопку, вставил текст, загрузил картинку и опубликовал, но в реальной работе быстро выяснялось, что браузер один из самых хрупких слоев системы.

Я пробовал автоматизировать работу через VNC и CDP для разных сервисов - соцсети, редакторы, страницы с авторизацией, интерфейсы без удобного API. Для каждого сервиса приходилось держать отдельный Chromium-профиль и отдельный CDP-порт. Один persistent user-data-dir нельзя открыть двумя Chromium-процессами одновременно, иначе получаешь конфликт профиля.

И когда я настраивал эту автоматизацию, начались еще и такие “головняки”:

  • Facebook не всегда нормально логинился под CDP, поэтому сначала приходилось проходить авторизацию вручную или через QR без remote debugging, а потом переоткрывать тот же профиль уже с CDP. 

  • TigerVNC после серии коротких подключений мог заблокировать localhost

  • Иногда появлялся лишний websockify на соседнем порту. 

  • В интерфейсах часть кнопок была нестабильной, а на отдельных площадках надежнее было идти по прямому URL, чем кликать по UI.

Столкнулся и с продуктовыми ловушками.
Например, в Facebook нельзя было убирать видимый URL из текста после того, как подтянулась preview-карточка - вместе с URL сбрасывались и карточка, и загруженное изображение. В ручной работе это видно глазами, а в автоматизации такое поведение нужно заранее превратить в правило.

Но, я не отказался от браузера полностью, просто понял что использовать его как основной и надежный путь не вариант.

Если есть API, CLI, backend-операция или прямой экспорт/импорт лучше начинать с них. Браузерную автоматизацию стоит оставлять для случаев, где другого нормального интерфейса нет и сразу закладывать вокруг нее страховки:

  • фиксированные профили;

  • фиксированные порты;

  • отдельный state file;

  • healthcheck перед задачей;

  • понятные сценарии для каждой площадки;

  • стоп-условия для login, captcha и 2FA;

  • запрет на параллельный запуск одного и того же профиля.

Главный вывод - браузерная автоматизация полезна как запасной или временный слой, но плохо подходит как фундамент. Если делать на нее основную ставку, команда быстро начинает обслуживать VNC, CDP, профили, порты и нестабильный UI вместо самой задачи.

Вот тут подробно описал как боролся с браузером и к чему в итоге пришел.

Ошибка 4. Не защищать общее состояние от параллельных задач и сессий

Четвертая ошибка была про параллельность задач и сессий.

В агентной системе общее состояние появляется почти везде - git-ветки, очереди задач, браузерные профили, state-файлы, dashboard, фоновые проверки и записи в БД. То есть пока одна задача выполняется в одном месте, то это еще окей, но как только появляются несколько сессий, процессов или пользователей, состояние начинает конфликтовать.

Первый простой пример был в той же миграции - push отклонился, потому что удаленная ветка изменилась параллельно и это, по сути, обычная git-гонка - она лечится аккуратной интеграцией чужого изменения, а не перезаписью истории.

Но вот более неприятные случаи произошли в интерфейсах и очередях.

В Control UI переключение между сессиями ломалось из-за гонки между подпиской на новую сессию, загрузкой истории и обновлением списка сессий. Исправление было не сложным, но по итогу очень важным - переключение сделал последовательным async-flow, чтобы несколько операций не спорили за одно состояние.

Другой пример - dashboard рядом с длинной задачей. Параллельное открытие dashboard могло приводить к EmbeddedAttemptSessionTakeoverError, то есть пользователь хотел просто посмотреть состояние, а в итоге вмешивался в задачу, которая уже выполнялась.

И еще один примечательный случай был в очереди Elementor-builder (агент по сборке страниц) - два пользователя почти одновременно просили собрать страницу, запись о задаче первого создавалась только после уточняющего вопроса и пока первый пользователь отвечал, второй фактически мог перехватить очередь. Проблема была не в модели и не в промпте, а в том, что task record создавался слишком поздно.

После этого стало понятно, что если есть общее состояние, его нельзя защищать договоренностями в тексте инструкции, тут уже нужны механические правила.

Что я поменял:

  • task record создается до уточняющих вопросов, а не после;

  • проверка “у пользователя уже есть открытая задача” уходит на уровень БД/очереди;

  • общий браузерный state хранится в одном месте;

  • операции с задачами и окружением идут через CLI-first интерфейс, а не через набор ручных правок;

  • повторный запуск шага должен быть безопасным или должен останавливаться.

Для меня это был важный сдвиг. Агентская система это не набор отдельных команд, это скорее распределенная рабочая среда. И если общее состояние не защищено от параллельных изменений, отдельный шаг может быть выполнен правильно, а процесс в целом все равно сломается.

Ошибка 5. Запускать длинные задачи без чекпоинтов, логов и защиты от повторного запуска

Пятая ошибка про длинные задачи.

На коротких сценариях можно позволить себе жить внутри одного диалога, но как только задача занимает 20 минут, час или несколько запусков через cron, этого уже мало.

И проблема не в том, что агент не справляется, а в том, что если система не сохраняет промежуточное состояние, то после сбоя непонятно, что уже сделано, что можно повторить, а что повторять опасно.

Я несколько раз упирался в это на задачах, где были длинные проверки, фоновые процессы, браузерные действия или работа с внешними сервисами - сессия могла оборваться, контекст раздуться, внешний сервис вернуть ошибку и т.п. и это происходило постоянно.

Если в этот момент нет чекпоинта, следующий запуск начинается с вопросов:

  • какие шаги уже выполнены;

  • какие файлы или записи уже изменены;

  • можно ли повторить текущий шаг;

  • не создадим ли мы дубль;

  • где последний лог;

  • нужно продолжать, откатиться или остановиться.

И плохой вариант тут просто запустить задачу заново и надеяться, что ничего не сломается. В агентной системе это быстро приводит к дублям, конфликтам и странным полусделанным состояниям. Поэтому длинная задача должна обязательно оставлять след.

Минимальный набор:

  • state-файл или запись в БД с текущим шагом;

  • список уже выполненных действий;

  • ссылки на созданные артефакты;

  • последний безопасный checkpoint;

  • лог ошибки;

  • следующий рекомендуемый шаг;

  • флаг, можно ли повторять операцию без риска.

Для локальных задач это может быть обычный файл вроде .openclaw/tmp/<task>-state.json. Для очередей и пользовательских задач - запись в БД. Важно не место хранения, а именно правило, что если задача длинная, она не должна жить только в контексте модели.

Также, отдельно я ввел ограничение на попытки - если один и тот же шаг падает несколько раз подряд, агент не должен бесконечно перебирать обходы. После ограниченного числа попыток он сохраняет состояние, показывает лог, предлагает варианты и просит человека принять решение.

Это особенно важно для внешних сервисов, публикаций, файлов, миграций и задач, где повторное действие может что-то испортить. Иногда правильный следующий шаг не “попробовать еще раз”, а остановиться и честно сказать где уперлись, что уже сделано и какие есть варианты решения.

Главный вывод - длинная агентная задача должна быть устроена как процесс с чекпоинтами, а не как длинный поток текста в одной сессии. Тогда сбой становится нормальной частью работы, а не причиной начинать все заново и гадать, что уже произошло.

Что я вынес из этих пяти ошибок

Если свести все к одному выводу, получится так - рабочий агент начинается там, где свобода превращается в инженерные ограничения. Да, конечно хочется чтобы агент имел самостоятельную логику и мог находить оптимальные, рабочие решения - но пока это не работает.

Свобода хранить все подряд раздувает контекст и расходы. Свобода сделать force-push рискует стереть чужую работу. Свобода запускать браузер “как получится” превращается в конфликты профилей и портов. То есть так или иначе ты все равно будешь выстраивать рамку вокруг системы и прописывать четкие правила, иначе все будет ехать.

Для рабочей системы нужны более скучные и банальные вещи:

  • состояние;

  • память;

  • маршрутизация моделей;

  • ограничение инструментов;

  • идемпотентные шаги;

  • стоп-условия;

  • отчеты;

  • healthcheck;

  • правила для опасных действий;

  • понятный механизм продолжения после сбоя.

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

У меня в профиле есть несколько подробных статей, в которых я расписал как боролся с ошибками или настраивал агента под конкретную задачу. Если интересно - велком.

У меня все, спасибо за внимание, буду рад обратной связи в комментах!