НовостиМакроЭксперимент с loop engineering показал, что циклы обратной связи и верификаторы могут незаметно давать сбой

Эксперимент с loop engineering показал, что циклы обратной связи и верификаторы могут незаметно давать сбой

Автор: Towards AI·

Ключевые выводы

  • Эксперимент оценивал два компонента loop engineering: цикл обратной связи с использованием реальных ошибок тестов и схему maker/checker, отделяющую генерацию кода от окончательной оценки.
  • Первый тестовый запуск не показал пользы от реальной обратной связи, потому что hidden-test harness возвращал голые assertion errors без полезных деталей сбоя.
  • После добавления в harness входных данных, вызвавших сбой, ожидаемых выходов и фактических результатов реальная обратная связь решила одну задачу, с которой не справился общий цикл повторной попытки.
  • Верификатор, запускающий тесты, ложно принял 3 из 8 неправильных кандидатов, что оказалось более высоким показателем, чем у двух оценочных чекеров в измеренном сравнении.
  • Финальная объединенная система улучшила результат главным образом за счет цикла обратной связи, тогда как верификатор выдал одну ложную приемку, пойманную только оценкой на скрытых тестах.
Эксперимент с loop engineering показал, что циклы обратной связи и верификаторы могут незаметно давать сбой

Обновлено 27 июля 2026 года редакционной командой. Изначально опубликовано на Towards AI.

«Моя работа — писать циклы».

Эти слова принадлежат Boris Cherny, который руководит Claude Code в Anthropic. Cherny говорил, что перестал напрямую промптить Claude и теперь тратит время на проектирование циклов, которые промптят модель за него [1]. Это замечание, вместе с несколькими похожими комментариями, помогло запустить в этом году волну объясняющих материалов о loop engineering [1][2]. Прочитав шесть из них, я построил один такой цикл.

Точнее, я построил два компонента, которые описывает почти каждый разбор, но лишь немногие, похоже, запускают от начала до конца. Первым был цикл run-until-done: вместо того чтобы после неудачи просить модель угадать снова, он передает модели ее собственные реальные ошибки тестов. Вторым была схема maker/checker: модель, которая пишет код, не получает права быть окончательным судьей того, корректен ли этот код.

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

Я реализовал оба компонента с нуля примерно в 600 строках Python, подключил их к claude-opus-4-8 и оценил на MBPP+ [3]. MBPP+ — это бенчмарк EvalPlus с небольшими задачами программирования на Python, что сделало его полезным для изоляции поведения циклов без усложнений масштаба репозитория. Совокупная стоимость всех экспериментов, обсуждаемых здесь, составила менее двух долларов. Второй компонент — тот, который часто считают более безопасной половиной, потому что он «действительно запускает тесты», а не доверяет словам модели, — в моих измерениях дал результаты, о которых эти разборы не предупреждали.

Кратко: два ключевых компонента loop engineering легко соединить и легко незаметно собрать неправильно. Мой цикл с «реальной обратной связью» сначала выглядел неотличимо от случайных повторных попыток, пока я не нашел ошибку в собственном тестовом harness. Мой «безопасный» верификатор, запускающий тесты, имел более высокий уровень ложных приемок, чем чекер, который просто спрашивал модель, насколько она уверена. Построить цикл — это легкие 20% работы.

Подключение цикла к пустому сигналу

Значительная часть материалов о loop engineering останавливается на схеме подключения. В них перечисляются триггер, проверяемая цель, инструменты, состояние и правила остановки — пять блоков со стрелками между ними. Подразумеваемый вывод: как только блоки соединены, цикл работает.

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

Именно с таким сбоем я столкнулся в своем цикле «реальной обратной связи». Он был подключен к фактическому выводу тестов, а не к общему промпту повторной попытки. В теории он должен был явно превзойти цикл, который получал только сообщение: «это было неправильно, попробуй снова». Мой первый запуск показал обратное.

Грейдер, который проверяет грейдер

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

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

Затем я проверил весь pipeline на 75 эталонных решениях MBPP+. Все 75 прошли. Только после этого я стал доверять каким-либо числам, которые выдавал цикл.

Цикл, который выглядел правильным, но таким не был

Сам цикл был очень простым. Он генерировал решение, оценивал его и при неудаче возвращал реальный stderr — не общий вариант «попробуй снова», а фактическую ошибку — максимум в течение трех попыток.

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

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

Вместо того чтобы сосредоточиться на верхнеуровневой метрике, я изучил сбои и нашел проблему: hidden-test harness в MBPP+ падал с голым AssertionError. В нем не было ни входных данных, на которых произошел сбой, ни ожидаемого значения, ни фактического значения. В результате «реальная обратная связь» была информационно идентична «попробуй снова», потому что модель не получала ничего, на чем могла бы действовать.

Я доработал harness так, чтобы он сообщал входные данные, вызвавшие сбой, ожидаемый результат и фактически возвращенное кодом значение. На тех же 35 задачах во втором запуске реальная обратная связь восстановила одну задачу, которую общая ветка решить не смогла, ценой примерно 2,500 дополнительных входных токенов за весь запуск.

Цикл не был сломан. Сигнал, к которому он был подключен, был пустым. Только контрольная ветка выявила это; одна лишь главная метрика этого бы не сделала.

Верификатор дал сбой не так, как предсказывала теория

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

Я сравнил этот верификатор с тремя более слабыми чекерами на 41 кандидате, созданном моим циклом: 33 правильных и 8 неправильных. Измеряемой величиной был уровень ложных приемок, то есть то, как часто каждый чекер пропускал код, который на самом деле был сломан.

ЧекерЛожная приемкаЛожное отклонение
Доверять всему8/8 — 100%0/33 — 0%
Спросить модель, уверена ли она2/8 — 25%4/33 — 12%
Вторая модель читает код2/8 — 25%5/33 — 15%
Пишет тесты и запускает их3/8 — 38%1/33 — 3%

Я ожидал, что чекер, запускающий тесты, безоговорочно победит по ложным приемкам. Этого не произошло. Он пропустил более высокую долю неправильного кода, чем любой из оценочных чекеров.

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

Оценочные чекеры «выиграли» это сравнение по ложным приемкам главным образом потому, что в целом были осторожнее. Та же осторожность объясняет, почему они ошибочно отклоняли в четыре-пять раз больше корректного кода.

Запуск тестов не дает автоматической защиты от ложных приемок. Это другой вид доказательства: он включает конкретный вход, ожидаемое значение и фактическое значение, а не общее впечатление. Именно поэтому его уровень ложных отклонений в 3% можно использовать как реальный шлюз. Чекер, который отклоняет 15% хорошей работы, может утопить workflow в повторных попытках раньше, чем предотвратит плохой merge.

Объединение двух компонентов

Финальная композиция сначала запускает цикл обратной связи. Если он не справляется, она сэмплирует новых кандидатов и позволяет верификатору — а не уверенности модели — решать, что будет отправлено. В ней также есть явный путь отказа, если ничто не проходит планку.

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

Почти всю работу сделал цикл: он добавил 14.2 пункта, восстановил пять из шести single-shot неудач и потребовал около 10 дополнительных API-вызовов. Стадия верификатора сработала ровно один раз — на единственной задаче, которую цикл не смог решить. Первый сэмплированный кандидат прошел собственные самописные тесты верификатора, но все равно провалил скрытые тесты.

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

Общая стоимость этой стадии составила 45 вызовов, или примерно тринадцать центов.

Где подход ломается

Этот подход работает, когда цель действительно проверяема: функция со скрытыми тест-кейсами, схема, которая либо проходит валидацию, либо нет, или oracle, который цикл не может обойти разговорами. Он не работает, когда сама спецификация неоднозначна, потому что same-model checker может унаследовать то же неверное прочтение, что и генератор. В таком случае исправление — не обязательно более умный чекер. Это более ясная спецификация или независимый oracle из совершенно другого семейства моделей.

Я также тестировал это только на небольших, самостоятельных функциях MBPP+. Эксперимент не рассматривал большой кодовый репозиторий с межфайловыми зависимостями. Я не строил и не тестировал изоляцию worktree для параллельного запуска нескольких циклов. Это реальная проблема, но не та, которую измерял данный эксперимент.

Цикл также останавливается на стадии «проверено». Он не решает, нужно ли автоматически применять изменение или передать его человеку. Как только система получает реальный доступ на запись, это становится отдельной и более сложной задачей.

С чего начать

Начните с грейдера. Напишите self-test — known-good, known-bad и infinite-loop — прежде чем писать какую-либо логику цикла. Этот пятиминутный скрипт является основной защитой от вымышленного главного показателя.

Если у кодовой базы есть хотя бы небольшой набор unit tests, затем подключите цикл обратной связи и с первого дня постройте рядом контрольную ветку с общей повторной попыткой. Не доверяйте улучшению, пока оно не победит «попробуй снова».

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

References

[1] Rohan Mistry, “Prompt Engineering Is Dead. Loop Engineering Is Here.,” Towards AI, July 2026.

[2] Mehmet Özel, “Loop Engineering for AI Agents : Building Verifiable, Self-Correcting Coding Workflows,” Towards AI, June 2026.

[3] EvalPlus, “MBPP+ Dataset,” Hugging Face Datasets.