Циклы: как движок гонит работу до результата (loop-until-dry, кап итераций)
Что это
Цикл — это ограниченная петля «сделай → проверь → если не готово, почини класс проблемы → проверь снова». Движок гонит её сам, но с жёсткими тормозами, чтобы не крутиться вечно. В спеке есть два уровня этой механики, и важно их не путать.
Уровень 1 — orchestration.loopUntilDry (ревью-петля)
Это петля вокруг ревью призмами. Пока есть находка critical/block — чинить и пере-судить. Экспортируется буквальным текстом-рецептом в пакет:
Если BLOCK — почини КЛАСС проблемы (не точечный симптом) и пере-суди ТОЛЬКО затронутыми углами. Повторяй до «сухо» (нет critical/block), максимум N итераций; после капа — эскалируй владельцу.
В Quest maxIterations: 4. Ключевое слово — класс проблемы: не залатать один симптом, а починить всю категорию и пере-проверить только затронутыми углами.
Уровень 2 — engine.cycles[] (общие ограниченные петли)
Статус.
engine.cycles[]— слой конструктора: поддержан схемой и экспортёром, но в базовый флагманский сид Quest пока не входит (в разработке для флагмана). Уровень 1 (loopUntilDry,maxIterations: 4) в Quest есть и работает из коробки; уровень 2 — то, что вы добавляете сами в конфигураторе, когда вам нужны более широкие петли. Всё в этом разделе описывает форму слоя, а не поведение купленного пакета.
Это отдельный примитив для более широких сценариев, с пятью видами: until-goal, for-each-work-item (проход по бэклогу), refine-until-quality, retry-with-backoff, reconcile-config. Здесь maxIterations — обязательное поле без дефолта, потолок 12. Это сделано намеренно: петля без жёсткого потолка на слабой модели может крутиться бесконечно, поэтому «нельзя описать цикл, не назвав его предел».
Три предохранителя от runaway
- Счётчик — из файла, не из памяти. Рецепт прямо говорит: считай итерации из файла-леджера
.engine/cycles/<name>.count, а НЕ по памяти — «память врёт про счётчик». Каждый круг: инкремент в файле → работа → выполненный verify. - Токен-бюджет обязателен. Схема отклоняет спеку на сохранении, если есть
cycles[], но не заданpermissions.budget.tokensSoftCap. Формально: «петля без токен-капа может сжечь неограниченную стоимость на слабой модели». - STOP-правило. Достиг
maxIterationsИЛИ бюджета токенов → ОСТАНОВИСЬ и эскалируй владельцу. «Не крути цикл дальше на всякий случай».
Есть и защита соседей: у цикла есть offLimits — пути, которые петля НЕ трогает никогда, чтобы runaway-цикл не затёр смежное.
Когда применять слой cycles
for-each-work-item— прогнать бэклог из N задач до verify-green по каждой.reconcile-config— привести конфиги/сервисы к желаемому состоянию (этот вид обязателен, если в спеке естьsetup.systemConfig— иначе сохранение отклоняется).refine-until-quality— полировать артефакт, пока не пройдёт планку.
Как это выглядит на слое конструктора
Пример ниже показывает форму слоя engine.cycles[] — как он описывается в конфигураторе. Это не сценарий базового Quest (там из коробки есть только уровень 1 loopUntilDry); это то, что делает движок, в который вы добавили cycles.
Вход: бэклог из 3 багов, движок с engine.cycles: [{ name: 'backlog', kind: 'for-each-work-item', maxIterations: 6 }] и tokensSoftCap: 400000.
Что делает движок с таким слоем (буквально по рецепту в пакете):
- Создаёт
.engine/cycles/backlog.count = 0. - Круг 1: инкремент →
.count = 1. Берёт баг №1, чинит класс, запускает verify (npx tsc --noEmit,npm test). Зелено — фиксирует. - Круг 2 →
.count = 2. Баг №2. Тест падает — чинит, пере-verify до зелёного. - Круг 3 →
.count = 3. Баг №3. Готов. - Бэклог пуст (цель
for-eachдостигнута) — цикл завершается на 3 из 6 итераций, штатно.
Альтернативный исход (важный): если бы баг №2 не чинился и .count дошёл до 6 — движок останавливается на капе и пишет честный отчёт: «достиг maxIterations на баге №2, вот что осталось», а не имитирует, что всё исправлено. Точно так же он остановится, если токены пересекут 400000.
Результат: три бага закрыты с выполненным verify на каждом; счётчик итераций объективен (из файла); стоимость под контролем бюджета; на нерешаемом — эскалация, а не бесконечный цикл.
Честная оговорка
Сначала о статусе: уровень 1 (loopUntilDry) есть в базовом Quest; уровень 2 (engine.cycles[]) — слой конструктора, во флагманский сид пока не входит (в разработке). Раздел про пять видов циклов и worked-пример относятся к движку, в который вы этот слой добавили сами.
Дальше о границе гарантии. Дисциплина леджера и STOP-правила — ADVISORY проза (инструкция целевой модели), потому что счётчик в файле сам по себе не блокирует код выхода. ENFORCED тут — только те verify-команды внутри круга, у которых есть реальный код выхода (тесты, tsc, линтер). Токен-кап и кап итераций как правила схемы блокируют сборку невалидного движка, но на этапе выполнения их соблюдение обеспечивает следование рецепту.