Перейти к основному содержимому

Реальная валидация — удалённый GitHub и настоящая модель

Валидация Фазы 12 прямым текстом объявляет собственные границы: LocalGitProvider, NoopDevAgent, вердикт gate, написанный самим скриптом. Она существует, чтобы доказать цепочку, не завися ни от сети, ни от суждения модели, и делает это хорошо.

Эта валидация заменяет ровно те три вещи, которые та оставила за бортом, — удалённый GitHub, реальный dev agent, модель через API, — и результат таков, какого и ждут от честного dogfooding: дешёвая половина прошла целиком, а дорогая провалилась так, как не показывал ни один тест.

Скрипт — pnpm --filter api validacao:real -- --repo <owner/repo>.

Что прошло, и это больше, чем кажется​

Всё ниже выполнялось против daneiel/test на настоящем GitHub и без единого цента расходов — это фаза --ate backlog.

чторезультат
удалённое принятие (настоящий getRepo)origin: adopted, ветка по умолчанию main
план в dry-run6 мутаций, 6 диагностик, решение пустое
репозиторий нетронут до решенияпроверено через API: ноль веток, ноль содержимого
повторное принятие сходится6 мутаций на 1-м проходе, 3 на 2-м (RN-046)
единственная story остаётся draft + предложениеда, без автоматического продвижения
claimNext до продвиженияnull — брать нечего
продвижениезарегистрировано с пользователем в качестве актора

RN-045 перестаёт быть доказанной только против локального bare: удалённый репозиторий проверен после принятия и был буквально пуст.

Находка D произошла на самом деле​

Bootstrap остановился на protect_branches с Upgrade to GitHub Pro or make this repository public — приватный репозиторий на бесплатном плане. Это ровно тот сценарий, который задокументировала Фаза D, теперь наблюдаемый вне теста.

И предпосылка RN-078 подтвердилась: при проверке репозитория сразу после этого dev, main и qa существовали, а файлы были закоммичены. Это последний шаг, и единственный, сбой которого оставляет пригодный репозиторий. Скрипт распознаёт сбой и продолжает — это выход, спроектированный именно для этого.

Что показал платный прогон​

Модель openai/gpt-5-mini через OpenRouter, реальный dev agent, одна story с одной task: «Expor GET /saudacao».

Task была заблокирована с limite de iterações atingido, источник modelo. Ни одной PR не открыто, а значит, ни один gate так и не вынес суждения.

Записанная диагностика неудобно ясна: (nenhum terminal rodado).

Измерение​

Извлечено с помощью pnpm --filter api medir:execucao, никогда не записывается вручную:

окно1m36s
сессии · события3 · 59
перезапуск engine посерединенет
круги gateни одного (PR не было)
немые ходыни одного
вмешательства пользователяни одного
агентвызовыinoutстоимостьмодель
dev-api85.671205< US$ 0,01openai/gpt-5-mini
anamnese16.779967< US$ 0,01deepseek-v4-flash
psicologo13.2121.981< US$ 0,01deepseek-v4-flash

Критерий ноль перезапусков выполнен. А стоимость — наименее интересная часть: 205 выходных токенов за восемь вызовов — это агент, который ничего не написал.

Что продукт обнаружил сам​

Psicólogo работал на тяжёлом tier и выдал три гипотезы. Первая и вторая — верный диагноз, без чьей-либо подсказки:

O dev-api não tinha (ou não utilizou) um ambiente terminal funcional […] em toda a sessão não existe nenhum tool.call de execução de comando […] apenas search_workspace/read_file.

A ferramenta search_workspace está sub-indexada ou mal configurada para este repositório: não encontrou nenhum arquivo de código ou manifesto […] Isso enganou o agente, que ficou tentando entender "onde está o projeto".

Это заслуживает отдельной записи: интроспекция продукта сработала. Psicólogo прочитал event log неудачного прогона и назвал причину с большей точностью, чем любое утверждение скрипта.

Находки, для сортировки 13c​

Дисциплина фазы действует здесь, как и везде: новая находка входит как пункт, никогда как исправление.

X. Dev agent сжигает потолок итераций, исследуя пустой репозиторий​

Получив task в только что подготовленном репозитории — где есть только шаблон Gitflow, без кода, — агент потратил все восемь итераций на search_workspace/read_file, ища «где проект», и так и не выполнил ни одной команды и не записал ни одного файла.

Симптом — limite de iterações atingido с источником modelo, что технически верно и практически бесполезно: модель не ошиблась в суждении, она так и не дошла до суждения. Причина — отсутствие сигнала о том, что искать нечего: пустой репозиторий неотличим доступными инструментами от репозитория, где поиск не удался.

Стоит отметить, что это первый сценарий продукта, в котором dev agent начинает с абсолютного нуля. Каждый тест и каждое демо исходили из workspace с кодом.

Y. search_workspace не отличает «пусто» от «не нашёл»​

Прямое следствие предыдущего и, вероятно, та деталь, с которой можно работать: первые пять вызовов вернули nenhum resultado, что агент прочитал как «ищи лучше» вместо «здесь ничего нет». Psicólogo пришёл к тому же выводу самостоятельно.

Второй прогон: исправление Y НЕ закрыло X​

После закрытия находки Y тот же прогон выполнили снова — та же story, тот же репозиторий, та же модель. Между ними изменилась только фраза инструмента.

1-й прогон2-й прогон
вызовы dev-api88
выходные токены205248
исходlimite de iterações atingidolimite de iterações atingido
PRнетнет

Поведение изменилось наблюдаемым образом — один поиск вместо пяти, за которым следует read_file, — а исход нет. Новое сообщение дошло до агента и было верным для этого случая (o workspace tem 2 arquivo(s), então a busca funcionou), потому что в репозитории был шаблон Gitflow и он не был пуст.

Записанная здесь гипотеза была неверной. Прежний текст говорил «a correção é a frase, não o teto». Доказательства говорят другое: из восьми итераций семь ушли на исследование. Остаётся ОДНА на то, чтобы записать файл, закоммитить, сделать push и открыть PR, — с этим не справился бы даже идеальный агент.

TOOL_LOOP_MAX_ITERATIONS по умолчанию равен 8 (apps/engine/config/runtime.exs:100). Это число появилось для разговорного агента и никогда не пересматривалось для dev agent, которому нужно исследовать репозиторий, прежде чем действовать.

Psicólogo снова оказался точнее, чем утверждение скрипта:

o workspace não contém o código do projeto: não há package.json, nem src, nem README

ficou apenas em modo leitura/exploração

Чему это учит об измерении​

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

Третий прогон: потолок БЫЛ причиной​

С TOOL_LOOP_MAX_ITERATIONS=25 вместо 8 и без каких-либо других изменений dev agent перешёл на другой уровень:

инструмент2-й прогон (потолок 8)3-й прогон (потолок 25)
search_workspace24
read_file56
write_file03
terminal01
исходлимит итерацийdev.awaiting_approval

Он исследовал, записал три файла и вызвал npm test --silent. Гипотеза, записанная выше, была верна: потолок 8 — унаследованный от разговорного агента — не подходит dev agent, которому нужно понять репозиторий, прежде чем действовать.

И причина остановки изменилась полностью. Блокировки не было: действие terminal стало proposed_action с require_approval, и агент перешёл в dev.awaiting_approval — приостановлен, удерживая worktree и историю, как спроектировал ADR 0052. Он остановился и ждал, вместо того чтобы сжигать итерации, стуча в дверь.

Четвёртый прогон и что он показал о Фазе F​

Разрешение npm, pnpm, node и npx в allow проекта не разблокировало: агент выполнил ls -la — глагол, которого не было в списке, — и снова оказался в ожидании.

Это обнажает разницу между тем, что поставила Фаза F, и тем, что было запрошено. Запрос был «permita sempre comandos desde que seja na pasta do projeto». ADR 0055 поставил потолок: команда, затрагивающая путь вне папки, никогда не одобряется автоматически, сколько бы глагол ни был в allow. Потолок защищает путь — глагол по-прежнему управляется allowlist, который по замыслу является закрытым списком.

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

Я не считаю это дефектом, а считаю вопросом объёма: ADR 0055 никогда не обещал продвигать глагол. Но разрыв между запросом и поставкой реален и становится пунктом сортировки.

Ловушка самого инструмента​

Первая попытка настроить политику не дала никакого эффекта, и причина заслуживает записи, потому что повторяется: скрипт запускался с хоста, поэтому PROJECT_WORKSPACES_ROOT упал на значение по умолчанию /tmp/brabo-project-workspaces, и permissions.json появился в файловой системе, которую engine не видит.

Это та же ловушка подопытного репозитория в /tmp, которую валидация Фазы 12 уже документирует, — вернувшаяся другим путём, в тот же день. Заголовок скрипта теперь прямым текстом требует запуска изнутри контейнера.

Пятый прогон: цепочка доходит до GitHub​

При запуске изнутри контейнера (недостававшее условие) и с разрешёнными глаголами terminal цепочка прошла целиком:

действиеполитикаисход
terminal ×2auto_approve✅ executed
git_commitauto_approve✅ executed
git_pushauto_approve✅ executed
pr_openauto_approve❌ failed

Ветка агента существует на GitHub: feature/task-d4b36a5b, рядом с dev, main и qa. Код, написанный моделью, закоммиченный под идентичностью dev-api[bot] и отправленный в настоящий удалённый репозиторий.

pr_open упал с Requires authentication, и причина — находка AA: api определяет git-токен по action.decidedBy, который равен NULL, когда политика одобряет автоматически. Push сработал, потому что отправляет engine, который подставляет учётные данные owner (RN-076).

Шестой прогон: PR открылась на GitHub​

С RN-082 на месте цепочка замкнулась до PR:

PR #1 — "Rota pública de saudação — Expor GET /saudacao", из feature/task-636ef1aa, открыта в daneiel/test.

Код, написанный моделью, закоммиченный как dev-api[bot], отправленный и опубликованный как pull request в настоящем удалённом репозитории. Gate открылся (pr.gate_changed, gateStatus: awaiting_qa), и область QA отработала.

qa-performance-seguranca был корректно освобождён (delegation.dispensed, обоснование «story sem RNF») — освобождение с обоснованием, никогда не молчание, как спроектировал ADR 0038.

qa-automacao упал, и это стало находкой AB: он вызвал составную команду, последний сегмент которой (head) не был в allow, ToolLoop приостановился в awaiting_approval, а QA Lead классифицировал приостановку как «desfecho inesperado» с источником infra. Это дефект, который ADR 0052 исправил для dev agent и который не дошёл до агентов gate.

Седьмой прогон: расширения allowlist не хватило, и это было предсказуемо​

С 25 разрешёнными глаголами — явный критерий: то, что ЧИТАЕТ или СОБИРАЕТ, никогда то, что ходит в сеть или разрушает, — gate снова застрял. И не из-за нехватки глагола:

ls -la && echo "---" && cat package.json 2>/dev/null; …

ls, echo и cat все были в allow. Блокирует 2>/dev/null, и это становится находкой AC: парсер трактует > как разделитель, перенаправление становится сегментом с глаголом /dev/null, а тот же токен к тому же является абсолютным путём вне проекта.

Прогноз, записанный до прогона, подтвердился. Allowlist — закрытый список, а модель придумывает команды; расширять список — заплатка. 7-й прогон доказывает это так, что трудно оспорить: 25 глаголов, а застрял на ФОРМЕ команды, а не на глаголе.

Восьмой прогон и аргумент, который он закрывает​

С исправленными Y, AA, AB и AC dev agent сделал единственный вызов: bash -lc npm test --silent. Глагол bash, вне списка, require_approval.

Отказ правилен — разрешение bash обнулило бы весь allowlist, включая встроенные deny. Но три последних прогона вместе говорят то, чего ни один из них не говорил по отдельности:

прогонзастрял накатегория
6-йheadглагол
7-й2>/dev/nullформа
8-йbash -lcвызов

Три разные категории за три раунда. Расширение списка решает первую и не затрагивает две другие. Allowlist глаголов не сходится против агента, который свободно выбирает, как вызвать то, что хочет запустить.

Это не дефект allowlist: он выполняет то, что обещает, и отказ в bash — доказательство, что граница держит. Это граница ОБЪЁМА — он не был спроектирован для обеспечения автономии и не обеспечивает её.

Практический вывод 13b таков, и он ценнее PR: путь к gate через LLM не проходит через ослабление политики. Он проходит через то, чтобы агент ждал решения, а не умирал (находка AB), — что ADR 0052 уже сделал для dev agent.

Девятый прогон: дешёвое исправление, которое уничтожило бы гарантию​

Девятый застрял на том же bash, что и восьмой, и очевидное исправление было в одной строке: положить bash в allow и смотреть, как конвейер становится зелёным.

Этого не сделали, и в этом результат прогона. Разрешение bash не расширяет allowlist — оно его обнуляет, потому что у каждой заблокированной команды появляется разрешённая форма вызова, включая встроенные deny. Раунд прошёл бы, а гарантия исчезла бы, и ничто в результате не указало бы на подмену.

Поэтому девятый закрепил диагноз: проблема никогда не была в том, какой глагол есть в списке, а в том, что агент gate умирал, когда политика велела спросить. Отсюда появился ADR 0057, распространяющий на gate то, что ADR 0052 уже сделал для dev agent: перед действием, требующим решения, приостановиться и ждать, а не классифицировать собственную приостановку как сбой infra (находка AB).

Десятый прогон: вся цепочка, от начала до конца​

С ADR 0057 на месте десятый закрыл всё, что девять предыдущих оставили открытым, без единого перезапуска engine:

этаписход
удалённое принятиеorigin: adopted против daneiel/test
план репозиториявыполнен только после вашего решения
продвижение storyвручную, с пользователем в качестве актора
реальный dev agentнаписал код, закоммитил как <agente>[bot]
push и удалённая PRопубликована на GitHub
gateоткрылся (pr.gate_changed)
область QAделегировала и освободила с обоснованием
субагентприостановился на одобрении и не умер
ваш отказвозобновил цикл вместо того, чтобы его завершить
вердиктchanges_requested, вынесен LLM

Важны две последние строки. То, что субагент остановился и остался жив, — это работающий ADR 0057; отказ пользователя, возобновляющий цикл, — это точка, до которой не доходил ни один предыдущий прогон: человеческое решение входит посреди работы агента, и он продолжает оттуда, а не начинает заново и не сдаётся.

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

TODO(humano): стоимость в долларах и число вызовов этих двух прогонов в своё время не были извлечены с помощью medir:execucao. Если token_usage сессий ещё сохранился, стоит заполнить — остальные измерения этого документа все получены скриптом, и эти два — исключение.

Прогоны с двумя модулями: параллелизм на проверке​

Первые десять выполнялись с одним модулем. Этого достаточно, чтобы доказать цепочку, и недостаточно, чтобы доказать параллелизм: с единственной историей Dev Lead отказывается параллелить — «столкнулись бы в одних и тех же файлах» — и он прав. Потолок RN-083 никогда не запрашивался реальной работой.

Затем пришли три раунда с --modulos 2 (одна история в api, одна в web). Первые два описаны в находке AF — тот, что сломался, и тот, что доказал исправление. Далее — третий, существующий для измерения.

Dev Lead спланировал сам:

2 agentes em 2 módulos — "cada módulo tem exatamente uma história, então um agente por módulo é o mínimo justificável sem desperdício."

И раунд закрылся, измеренный через medir:execucao, а не вручную:

длительность3m56s, 182 события в 3 сессиях
вызовы33 (dev-api 10, dev-web 9, arquiteto 7, qa-automacao 6, dev-lead 1)
стоимость< US$ 0,01
перезапуск engineнет
немые ходыни одного
gatesqa approved, secops approved

Потолок потребовал решения. С двумя работающими агентами следующий запрос остановился в aguardando_autorizacao с действием parallelize, ожидающим в базе — 2 активных, потолок 2. Ничего не поднялось: если бы поднялось, авторизация была бы театром.

Что сломали два модуля до этого раунда​

Этот раунд прошёл чисто, но он третий с двумя модулями, и цену заплатили два первых. В первом dev-web взял task и умер на fatal: not a git repository до первого хода — ноль потраченных токенов, task заблокирована. Это находка AF: охрана быстрого пути Workspace.ensure!/4 спрашивала, существует ли .git, а git init создаёт .git до fetch; второй агент читал «готово» и пропускал lock.

Lock существовал с Фазы 4 и был корректен. Ошибочным был критерий, решавший, стоит ли его брать, — и ни один из десяти предыдущих прогонов не мог это показать, потому что ни в одном не было второго агента. После исправления dev-web перешёл от 0 к 16 вызовам в следующем раунде, и именно отсюда этот третий наследует право быть просто измерением.

Инструмент тоже научился: утверждение о потолке проверяло число (3-й запрос просит авторизацию), что было верно для одного модуля и провалило прогон, в котором продукт действовал правильно, — с двумя модулями активация уже заполняет потолок. Теперь оно проверяет правило: пока помещается, поднимается без вопроса; когда не помещается, останавливается.

Чего эта валидация ещё НЕ доказывает​

Честно об охвате, как и в её сестре:

  • Merge. По-прежнему вне по замыслу (RN-014) и останется так: кнопку в защищённой ветке нажимаете вы.
  • Остальные пять providers. Только OpenRouter работал с реальными учётными данными; остальные по-прежнему без smoke из-за отсутствия ключа, а то, что верно для одного provider, не переносится на другие рассуждением.
  • Изоляция. Агент выполняется в том же контейнере, что и этот monorepo. ADR 0055 говорит о себе, что это политика, а не изоляция — .. проваливается, но symlink изнутри наружу не обнаруживается.
  • Автономия без политики на пути. Allowlist глаголов не сходится (находки Z и AD), и это граница объёма, а не баг для исправления.
  • Потолок параллелизма, запрошенный САМИМ Dev Lead. С двумя модулями он спланировал 2 агента, а потолок 2 — поместилось. Потолок превысил сценарий, вызывая use case напрямую. Чтобы увидеть, как lead просит больше, чем может, понадобились бы 3+ модуля, а это ещё не запускалось.

Сама цепочка — от принятия до вердикта gate через LLM — доказана на реальной сети. То, что остаётся открытым выше, — не цепочка, а среда, в которой она работает, и поверхность, которую она покрывает.

Ссылки​