The Feedback Loop Is All You Need

14 min read Original article ↗

Содержание

Агенты по расписанию бесполезны на моей боевой кодовой базе

В Claude Code на днях завезли агентов по расписанию. Повторяющиеся задачи — нативно, прямо из коробки. То самое, о чём мы мечтали с первых демок AI-кодинга: ставишь агента на ночь, ложишься спать, а наутро PR'ы уже замёржены. Инженер, который работает, пока ты спишь.

А я сижу и думаю: да я ведь даже воспользоваться этим не могу. Ни на боевой кодовой базе — то есть у себя на работе. Я годами работал в archive.com, и за это время мы сменили три дизайн-системы: начали с Shopify Polaris, переехали на Ant Design, когда переросли Shopify, а потом на shadcn/ui и Tailwind — потому что и Ant Design к тому моменту сам оброс легаси. Годы работы, три UI-фреймворка, куча договорённостей, которые жили только в головах, и бизнес-правила, которые никто никогда не записывал. Натрави на это агента — он рванёт вперёд. Выдаст код. Красивый, идиоматичный, дьявольский код, который тащит импорты из всех трёх дизайн-систем в один файл и каким-то чудом проходит все твои проверки.

И что с этим делать? Ревьюить всё подряд нереально. Тормозить агентов нельзя. И уж точно нельзя надеяться, что они сами сообразят, какую дизайн-систему тут использовать.

Вот к чему я в итоге пришёл. Я работаю в Claude Code, поэтому и примеры все оттуда, но по большому счёту разницы нет — хоть Cursor, хоть Copilot, хоть Codex, хоть Devin.


Старый цикл против нового

Старый цикл: пишешь или ревьюишь код, на чутьё ловишь код-смеллы, оставляешь комментарии с объяснением, что имелось в виду, обещаешь поправить «потом» (что обычно означало «никогда»).

Новый цикл: один раз описываешь правила кодом, даёшь агентам биться о них лбом, смотришь, что отваливается, подтягиваешь ограничения. Меньше «запомни на будущее» — больше «такое в принципе не может случиться».

Мне хватило недели, чтобы это прочувствовать. Когда код можно генерировать без остановки, узкое место — это ты сам. Не агент. Ты.


Настоящий враг: техдолг, которого не видно

Вот о чём тебя никто не предупреждает: страшнее всего не когда агенты ломают код. Страшнее всего — когда не ломают. Код компилируется, проходит все тесты, на ревью выглядит безупречно — и при этом тихо нарушает те архитектурные допущения, которые ты считал незыблемыми.

Я видел это своими глазами. Агент добавляет форму на страницу, которую я уже перевёл на shadcn/ui. И берёт <Form.Item> из Ant Design — потому что соседняя форма на той же странице всё ещё на нём. Компилируется. Рендерится. Моя миграция только что откатилась на один компонент назад — и пайплайн этого даже не заметил.

С CSS та же беда. Агент пишет новый компонент на Tailwind-утилитах, всё по нашему текущему стандарту. Но значение паддинга копирует из старого Ant Design-компонента по соседству: p-[24px] вместо нашей шкалы p-6. Одно магическое число тебя не убьёт. Пятьдесят — убьют. Каждый коммит по отдельности выглядел нормально. А спохватываешься, когда уже три недели как тонешь в рассинхроне.

Человек бы это поймал. Взглянул бы на импорт и сказал: «стоп, а зачем мы вообще тащим сюда Ant Design?» У агента такого чутья нет. А без жёстких сигналов ты в пятнадцатый раз пишешь «всё ещё не работает».

AI IDE after I send "Still broken" for the 15th time Источник: ProgrammerHumor.io

Вот тогда я и зациклился на циклах обратной связи. Насколько быстро агент может узнать, что ошибся?


Твой CLAUDE.md — это пожелание. Твой линтер — нет.

Сначала я среагировал ровно как все: надо писать инструкции получше. CLAUDE.md. Библиотеки скиллов. Аннотации в документации. Опишешь всё достаточно чётко — и агент послушается, правда же? Vercel вон даже выкатил библиотеку скиллов — 40+ правил по производительности React, аккуратно написанных, оформленных как файлы SKILL.md для AI-агентов. Реально толковая штука.

Но на одних инструкциях далеко не уедешь. Особенно когда агент выдаёт больше кода, чем человек физически способен отревьюить.

The code is more what you'd call guidelines than actual rules

Пойми меня правильно: инструкции важны. Хороший CLAUDE.md заметно сокращает число итераций. Но между «помогает» и «гарантирует» — пропасть, и CLAUDE.md прочно сидит на стороне пиратского кодекса: это рекомендации, а не правила. По сути мы ставим всю кодовую базу на то, что вероятностная система будет попадать в цель каждый раз. Иногда так и есть. Иногда с первой же попытки получаешь красивый идиоматичный код. А иногда она роняет неиндексированный запрос в горячий путь — и узнаёшь ты об этом, когда база ложится в два часа ночи в пятницу.

И тут до меня дошло: мы же уже решали эту проблему. Десятилетия назад. Юнит-тесты появились, потому что люди забывают про граничные случаи. Линтеры — потому что люди не могут договориться, куда ставить фигурную скобку. CI — потому что «у меня на машине работает» перестало быть смешным после третьего падения прода. Всё это известно сто лет.

И всё равно с LLM мы наступаем на те же грабли. «Просто напиши очень хороший CLAUDE.md». «Просто добавь побольше скиллов». Поразительно, как быстро всё забылось.

CLAUDE.md объясняет «почему» и помогает агенту попасть с первого раза. Lint-правило гарантирует, что он не промахнётся. Скиллы дают скорость. Линтеры держат в рамках. Если выбирать что-то одно — бери линтер.


Guardrails: главное ограничение — это сложность

Вот что у меня прогоняется на каждое изменение:

  • ESLint — потому что у агента нет десяти лет мышечной памяти на твои соглашения об импортах
  • SonarJS — чтобы рубить целые классы багов на корню
  • строгий TypeScript — если типы разболтаны, агент пролезет в любую щель
  • жёсткие правила по React — чтобы в три часа ночи не рождались «креативные» паттерны компонентов
  • Prettier — потому что споры о форматировании закрыты

Но честно? Самое мощное, что я сделал — а перепробовал я много чего, — это зверски строгие лимиты на сложность. Не правила стиля. Именно жёсткие потолки на то, насколько большим и запутанным коду позволено становиться.

Оказалось, в ESLint для этого уже всё есть. complexity режет цикломатическую сложность. max-depth ограничивает вложенность. max-lines-per-function вынуждает дробить код. max-params держит интерфейсы узкими. max-statements не даёт функции делать двенадцать дел за раз. SonarJS добавляет cognitive-complexity, которая умнее обходится с вложенными условиями. Жаль, что я не включил всё это сто лет назад.

Дошёл я до этого на собственном опыте — и опыт был весёлый. Без лимитов на сложность агент бодро выдаст тебе одну функцию на 150 строк: шесть уровней вложенности, три ранних return-а и switch внутри try-catch внутри цикла. Компилируется. Тесты проходят. Даже работает. Потом следующий агент её трогает, делает ещё хуже — и поздравляю, теперь у тебя две функции по 150 строк. Поставь max-lines-per-function: 40, complexity: 10, max-depth: 3, добавь --max-warnings=0 — и смотри, что начнётся. Агенту просто придётся всё разбить. Он начнёт выделять хелперы, нормально называть сущности, разносить ответственность. Будто всё это время умел писать чистый код — просто нужно было настоять. Конкретные числа важны меньше, чем сам факт лимитов. Начинай со строгих значений и ослабляй только тогда, когда они реально начинают мешать.

Я всё время говорю про ESLint, потому что это моя территория, но стек тут по большому счёту не важен. RuboCop, Ruff, clippy — принцип тот же. Готовые линтеры закрывают синтаксис. А вот под твою архитектуру понадобится что-то своё.

Что меня удивило: ещё до того, как писать что-то своё, я здорово выиграл просто за счёт плагинов, до которых у нас годами не доходили руки. SonarJS, unicorn, perfectionist. Они существовали давным-давно — мы их просто так и не подключили. Обычная отмазка была: «там слишком много нарушений, не разгребём». С агентом эта отмазка не работает. Натравливаешь его на нарушения — и он разгребает их пачками. Пять минут, конфиг recommended — и целые классы багов исчезают.

А это можно сделать lint-правилом?

В какой-то момент я поймал себя на одном вопросе: а это можно сделать lint-правилом? И так — каждый раз, когда что-то шло не так. Дошло до автоматизма. И как только этот переключатель в голове щёлкает, ты больше не ревьюер. Ты тот, кто следит, чтобы ошибка не повторилась. Никогда.

Первое правило даётся тяжелее всего. Дальше — как снежный ком. Реальный пример: наши агенты с завидным упорством совали console.log в продакшн вместо нашего логгера, который шлёт всё в Datadog. Каждый. Божий. Раз. Lint-правило на десять строк это закрыло. Запретил console.log, а в подсказке предложил logger.error. Всё. Больше я об этом не думал.

Люди слышат «50 кастомных правил» и хватаются за голову. Понимаю, сам так делал. Часть этих правил и правда окажется неудачной. Но вот что происходит дальше: кто-то налетает на плохое правило, бесится и открывает PR, чтобы его поменять. И внезапно у вас идёт тот самый разговор об архитектуре, которого раньше просто не было. Этот PR ценнее самого правила. Кодовая база с плохими правилами — это состояние, которое можно улучшать. Кодовая база без правил — это просто вайбы. А когда правило требует переписать существующий код, AI вместе с кодмодами превращают эту чистку из квартальной эпопеи в пару часов работы.

Линтинг — это одна половина. Тесты — вторая, и тут AI сделал их до неприличия дешёвыми. Те самые дотошные, исчерпывающие тест-сьюты, до которых я всё обещал себе «когда-нибудь добраться»? Агент выбивает их за минуты.

Одна беда: правила ловят только то, что ты уже видел. А всё, что тебя ещё не укусило, по-прежнему где-то поджидает.


Что на самом деле ловит CI

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

Скриншот-тесты на Playwright стали для меня переломным моментом. То, что они вылавливают, поражает: регрессия z-index, из-за которой модалка прячется под оверлеем; сдвиг вёрстки после рефакторинга flex-контейнера; кнопка, которая отрисовалась, но абсолютно некликабельна. Ничего из этого юнит-тесты не видят. Chromatic делает то же самое для процессов вокруг Storybook. Если на экран никто не смотрит — экран рано или поздно сломается; эту истину я в своё время усвоил на горьком опыте.

Property-based тестирование я годами обходил стороной. Вместо отдельных тест-кейсов ты описываешь свойство, которое должно выполняться всегда: «эта функция никогда не возвращает отрицательное число» или «закодировал и декодировал — получи ровно то, что было на входе». Фреймворк генерирует сотни случайных входов и пытается это сломать. Невероятно эффективно — но я так и не внедрил, потому что писать хорошие описания свойств было занудно. AI это перевернул. Теперь я просто натравливаю агента на код, и он сам соображает, какие свойства должны выполняться. Одна команда прогнала агентов по 933 модулям и получила 984 баг-репорта, из них 56% — настоящие. Это примерно $10 за каждый реальный найденный баг.

Безопасность — вот что меня по-настоящему отрезвило. DryRun Security прогнали Claude Code, Codex и Gemini на сборке двух приложений — и в 87% PR была хотя бы одна уязвимость. Не опечатки. Структурные дыры: WebSocket-эндпоинты без аутентификации там, где у REST API она была; rate-limiting-мидлвара, описанная в файле, но так и не подключённая. Саму мидлвару агент написал правильно. Он просто не знал, что она не работает. Вот это меня и зацепило: статический анализ видит, что файл есть, но не понимает, что он никуда не подключён. Нужен CI, который реально поднимает приложение и проверяет поведение.

И сверху — мониторинг в рантайме. Мы завели Sentry и Datadog на очередь задач. Что-то падает в два часа ночи — и это превращается в задачу, которую подхватывает агент. Я просыпаюсь к готовому фиксу, а не к пожару.

Да, обвязки тут много. И менеджер резонно спросил бы: а во сколько всё это обходится?


Инвестиция

Токены — копейки. SaaS-подписки — копейки. Настоящая цена — это твоё время, а фич всё это не приносит. Знаю. Я сам с собой об этом неделями спорил.

Are ya shipping, son? — I'm building the pipeline that builds the pipeline. Источник: Memedroid

А потом я увидел цифры. CodeRabbit разобрал 470 GitHub-PR и выяснил: в коде, сгенерированном AI, багов в 1,7 раза больше, чем в написанном людьми. Уязвимостей — в 2,74 раза больше. Дословно: «У нас больше нет проблемы с созданием кода. У нас проблема с доверием к нему».

Да, ты платишь за токены. Но таксисты платят за бензин, машину, лицензию, страховку — и всё это на марже в 5–10%, и никто не возмущается. В софте мы немного зажрались: валовая маржа 80%+, а все средства производства — это ноутбук и стул, которые у тебя и так есть. Тулза за $200 в месяц, которая ловит хотя бы один прод-баг в квартал? Чувак, да она уже десять раз себя окупила.

Senior-инженер обходится компании в $150–200 в час со всеми накладными. А прод-баг, который нашёл клиент? Это дни на разбор, экстренные фиксы и подорванное доверие, которое уже не вернёшь. Кастомное же lint-правило пишется за полдня и ловит этот класс багов навсегда. Скриншот-сьют на Playwright поднимается за день. Не понимаю, чего я так долго тянул.

И эффект накапливается. Каждый guardrail умножает то, что агент может выкатить без тебя. Ещё одно правило — на одну заботу меньше на ревью. Десять правил — и целые категории багов просто… перестают появляться.

Карпати сказал это лучше, чем смог бы я: «Цель — забрать себе весь рычаг, который дают агенты, и при этом ничуть не поступиться качеством софта». Собственно, к этому я всю статью на ощупь и продвигался.


Организм

Когда я наконец собрал всё это в единую систему, что-то щёлкнуло. Дальше она начала затягиваться сама собой:

    ┌─────────────────────────────────────────┐
    │                                         │
    ▼                                         │
  Agent ──▶ Rules ──▶ CI ──▶ Observability    │
                                  │           │
                                  ▼           │
                                Tasks ────────┘

Вот как теперь выглядит обычный вторник: агент открывает PR. Кастомное lint-правило ловит нарушение в barrel-файлах — агент чинит. CI прогоняет Playwright, скриншот показывает сдвиг вёрстки — агент правит CSS. Sentry сообщает о всплеске 404-х на стейджинге — заводится задача, агент её подхватывает. Я за кофе просмотрел пару диффов. Больше ни строчки кода никто руками не написал.

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

И я такой не один. Spotify построил фонового кодинг-агента Honk поверх инфраструктуры обратной связи, в которую вкладывался ещё с 2022 года — за три года до всей этой истории с AI. Сейчас они вливают в продакшн больше 650 агентских PR в месяц. У Devin процент мёржа удвоился — с 34% до 67%, когда подтянули понимание кодовой базы. Не модель — контекст вокруг неё.

История каждый раз одна и та же. Модель не стала умнее. Цикл стал плотнее.


С чего начать

Где ты сейчас?

УровеньКак это выглядитПризнак
0 — VibesНикакого кастомного линтинга, никакого CI, всё ревьюишь руками«Между агентом и продом — только мои глаза»
1 — GuardrailsСтандартные линтеры и CI, но без кастомных правил«Агент проходит линт, но по архитектуре всё равно уплывает»
2 — Architecture as CodeКастомные lint-правила, в которых зашиты соглашения команды«Правила из CLAUDE.md переезжают в линтер»
3 — The OrganismСамозатягивающийся цикл: агент → правила → CI → observability → задачи → агент«Я ставлю агентов на ночь и утром смотрю диффы»

Если ты на нулевом — я был там же год назад. Вот что мне самому хотелось бы тогда услышать:

Сегодня: тот комментарий к PR, который твоя команда оставляет снова и снова — про соглашения об импортах, barrel-файлы, console.log в проде, — преврати в lint-правило. Всего одно. С этого всё и начинается.

На этой неделе: добавь скриншот-тесты на Playwright для трёх самых важных страниц. Я сам обалдел от того, сколько всего они ловят — и сколько при этом упускают юнит-тесты.

В этом месяце: поставь агента на что-нибудь безопасное. Обновление зависимостей, поддержка тест-сьюта, чистка протухших веток. Пусть пашет ночью, а ты с утра смотришь PR. Я начинал с Claude Code в браузере; когда этого стало мало, дешёвый VPS дал больше мощности под ту же идею.

Как понять, что всё работает: ты можешь поставить задачу с телефона, глянуть дифф по дороге и довериться результату. Не то чтобы ты стал работать меньше — просто больше не привязан к ноутбуку.

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


Заключение

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

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

LLM-ки вероятностны. В большинстве случаев они угадают — но на реальной кодовой базе «в большинстве случаев» рано или поздно угробит тебе пятницу. Я с этим смирился. Никакой prompt engineering этого не лечит.

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

Линтеры не спят, а CI не устаёт. Про себя я такого сказать не могу.

Заголовок — отсылка к «Attention Is All You Need» (Vaswani et al., 2017), статье, представившей архитектуру Transformer.


Присоединяйтесь к обсуждению

Hacker News · X/Twitter