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

Находки реального прогона конвейера

Собраны в сессии исполнения, проведённой через браузер 2026-08-05, с моделью по API (не локальной) и настоящим git-провайдером. Это входные данные для триажа FASE 13c: у каждого пункта есть файл:строка или событие, которое его доказывает, и ни один не был исправлен «попутно» — у закрытых указан PR.

Проект СОЗДАН (не принят) в GitHub через мастер. Реальный репозиторий: daneiel/hello-api, приватный. Сессия мастера: f15b0cc9. Здоровая сессия: 36abf7e7.

Исправлено во время прогона (блокировало)​

  1. Измеритель не измерял (PR #136, смержен) — db.execute деструктурировался как массив + агент читался из актора события вместо payload.
  2. llm_turn_stream без receive_timeout (PR #137, смержен) — падал в 15 с по умолчанию у Req; четыре разговорных агента проходят только там.
  3. ApprovalCard ронял экран сессии (будет закоммичено) — ActionType в web был подмножеством backend; ACTION_ICON[actionType] → undefined для git_repo_create/git_branch_create/git_branch_protect, а это именно то, что порождает ЛЮБОЙ проект, созданный у провайдера. «Универсальный запасной вариант» существовал только в комментарии. Не проявлялось лишь в ПРИНЯТОМ проекте (без bootstrap).

Открыто — для триажа 13c​

A. Сессия provisioning рождается без процесса в engine (СЕРЬЁЗНО)​

ЗАКРЫТО — стало RN-067. Четыре места вызова, перечисленные ниже, стали создавать сессию через CreateSessionUseCase (provision-repository:119 и :125, adopt-repository, activate-execution), и правило объявляет его единственным местом, создающим сессию. Записано, потому что доказательство от противного ниже — то, что сделало дефект видимым.

CreateSessionUseCase — единственное место, порождающее session.created в outbox. Создают сессию напрямую в репозитории, минуя это:

  • apps/api/src/application/use-cases/git/provision-repository.use-case.ts:112 и :121
  • apps/api/src/application/use-cases/git/adopt-repository.use-case.ts:176
  • apps/api/src/application/use-cases/execution/activate-execution.use-case.ts:156 ← это сессия, в которой работают DEV AGENTS

Эффект: engine никогда не узнаёт о существовании сессии → вечный REFUSED JOIN, без канала, без живого обновления, без heartbeat, и сессия никогда не закрывается (остаётся active навсегда). UI жалуется только в консоли.

Доказательство от противного:

сессияsession.createdengine.session_statesканал
мастер f15b0cc9нетпустоREFUSED JOIN
обычный маршрут 36abf7e7даactiveJOINED

B. Стартовая модель (РЕШЕНИЕ ПОЛЬЗОВАТЕЛЯ)​

Сессия всегда рождается на модели по умолчанию workspace (llama3.2:1b, локальной) — ADR 0020 запрещает локальные 7B/1B на семантическом шаге, и пришлось вручную менять в обеих сессиях. Запрос: настраиваемая стартовая модель, наследующая модель Criativo в этом сценарии, потому что он всегда — входная дверь проекта.

C. Говорит модель, а не агент (ДЕФЕКТ, указанный пользователем)​

Пузырь, транслируемый вживую, подписан именем МОДЕЛИ ("DeepSeek V4 Flash Latest"); агент (po) появляется только с приходом сохранённого события. Эффекты: сначала появляется неверное имя, а сообщение на экране ДУБЛИРУЕТСЯ (пузырь потока + пузырь события). Поток не сверяется с сохранённым событием.

D. Невыполнимый шаг блокирует мастер без выхода​

Proteger branches падает в приватном репозитории на бесплатном плане — сам мастер ПРЕДУПРЕЖДАЕТ об этом заранее. Но единственное действие, предлагаемое потом, — «Tentar novamente», которое будет падать всегда. Не хватает признать это и продолжить.

E. Превью репозитория лжёт​

В apps/web/src/routes/NewProjectWizard.tsx:331 зашито repo: brabo/{slug}. Настоящий владелец берётся из PAT (createForAuthenticatedUser), то есть daneiel/hello-api. Ошибка доходит до экрана ПОДТВЕРЖДЕНИЯ.

F. Мастер анонсирует rc​

Шаг «Política de branches» перечисляет rc среди постоянных и rc ← qa в каскаде. Действующая политика содержит только dev/qa/main; возврат rc находится в backlog ADR 0030.

G. Приглашение Criativo не появляется в созданном проекте​

Пустое состояние «A vez é sua» отрисовывается только при пустой ленте. В созданном проекте в ленте уже есть карточки bootstrap, поэтому пользователь не получает никакого приглашения.

H. Обезличенная лента активности​

Все 10 событий bootstrap отображаются как «atividade em system», без указания, что произошло.

I. Карточка действия показывает ТЕКУЩУЮ модель сессии​

Смена модели сессии задним числом переписывает подпись на карточках старых действий. token_usage замораживает правильную цену; дело только в экране.

J. Psicólogo запускается в пустой сессии​

ЗАКРЫТО — стало RN-079. Анализ запускается, только если есть АНАЛИЗИРУЕМОЕ событие, а «анализируемое» не учитывает машинные шаги bootstrap и след самих аналитиков — без этого второго вычета первый анализ навсегда сделал бы сессию заполненной. Без материала выдаётся psychologist.analysis_skipped, и ничего не тратится. Сессия из находки воспроизведена как тест (14 событий, ни одного анализируемого). В сессии b2fceb9e, только что открытой: получил гипотезы предыдущей сессии с логом новой (пустым), попытался сослаться на несуществующие seq 60-78, дважды получил отказ в доказательстве и сдался (psychologist.analysis_failed). Проверка удержала выдумку — но запускать анализ в сессии без событий — пустая трата.

K. Дублирующееся бизнес-правило не дедуплицируется​

ЗАКРЫТО ЧАСТИЧНО — стало RN-080. ТОЧНЫЙ дубликат (тот же заголовок без учёта регистра, диакритики и пробелов) отвергается при выпуске, в рамках проекта — именно между сессиями он и рождается. Семантический дубликат остаётся открытым и так и объявлен: отличить «Saudação com nome» от «Quem chama pode se identificar» — это суждение, а не if. Двукратный запуск Criativo в одном проекте оставил 10 правил, 5 из них сиротами («descoberta — sem história»). Эффект моего сценария, а не продукта, но он показывает, что нет ни дедупликации, ни предупреждения.

L. Кнопка в подвале устаревает​

Продолжает показывать «Estou pronto para produzir» после того, как лента уже перешла к PO.

Сработало так, как задумано (тоже записать)​

  • RN-059: долговременный agent.error с origem: infra, агент говорит в ленте и возвращается в idle (замечено 2 раза на таймаутах 15 с).
  • Проверка доказательств Psicólogo, отвергающая выдуманные id событий.
  • Bootstrap Gitflow в GitHub: 4/5 шагов, реальный репозиторий с main/dev/qa.
  • Конвейер proposed_action: 6 действий bootstrap, автоматически одобренных политикой.
  • 12c: ручное продвижение по умолчанию, story_promotion_proposed как предложение.
  • Прослеживаемость правило ↔ история, с разделением покрытых и непокрытых.
  • Живой прогресс bootstrap, где сбой показывает сообщение GitHub.

M. ARQUITETO СЛЕП К СОБСТВЕННОМУ module_map (P1 — сбой прогона)​

ЗАКРЫТО — стало RN-066 и подтверждено в production в последнем разделе этой страницы: 4 вызова assign_story_modules вместо 18, ни одного выдуманного имени, 1 module_map вместо 4.

Сессия 36abf7e7, seq 80-131. Arquiteto выпустил карту (модули saudacao и api_http) и сразу после этого не смог её перечитать. Нет инструмента для чтения действующего module_map, а отказ assign_story_modules не возвращает допустимые имена. Результат: перебор.

18+ попыток наугад: api, core, http, greeting, domain, web, hello-api, hello, greeting-api, saudacao (угадал по везению), app, server, publico, public-api, api-publica, …

Его собственными словами (seq 94, 99, 124):

"os nomes que tentei (api, core) não batem. Vou descobrir os nomes válidos testando candidatos plausíveis" "Preciso descobrir os nomes reais dos 2 módulos. Vou testar candidatos adicionais."

Три последствия, по степени тяжести:

  1. Неверные данные, объявленные верными. Все 4 истории оказались в ["saudacao"], включая историю ENDPOINT. api_http остался вообще без истории. А исход (seq 130) утверждает: «Todas as 4 histórias foram vinculadas com sucesso aos módulos.» Лог заканчивается уверенной ложью.
  2. Ломает исполнение ниже по течению. activate-execution поднимает по одному dev agent на МОДУЛЬ. Когда всё в saudacao, модуль api_http не получает агента, и спроектированная архитектура — не та, что будет построена.
  3. Ни один tool.result не записывается для assign_story_modules. Весь цикл невидим в event log — его можно лишь вывести по повторяющимся tool.call.

Стоимость цикла: 9 вызовов LLM у архитектора, 27.804 вход / 8.012 выход. Итого по сессии: 7.271 микро (US$ 0,007) — дёшево только потому, что модель flash.

Цикл module_map (PR #135) был СИМПТОМОМ этого: Arquiteto перевыпускал карту именно для того, чтобы попытаться закрепить имена, которые не мог прочитать. #135 закрыл порчу данных; слепота осталась.

Исполнение (проект 17229425, сессия исполнения dbb84ce8) — 2026-08-05​

N. ПОЛОВИНА ИСПОЛНЕНИЯ РАБОТАЕТ ТОЛЬКО С ПРОВАЙДЕРОМ local (P1, блокирует 13b)​

Engine.Projects.ProjectRepository.get_local_repo_path/1 возвращает {:error, {:unsupported_provider, "github"}} для любого провайдера, кроме local. Это задокументировано как сокращение рамок в самом moduledoc:

"Só suporta o provider 'local' (github/gitlab remotos ficam fora de escopo do executor de terminal por ora)" — apps/engine/lib/engine/projects/project_repository.ex:25-27

От него зависят пять мест вызова:

  • lib/engine/dev/worktree_manager.ex:20 — worktree dev agent
  • lib/engine/actions/terminal_executor.ex:39 — выполнение команды
  • lib/engine/gates/diff.ex:17 — diff, который читают QA и SecOps
  • lib/engine/harness/project_context.ex:30 — контекст проекта

Наблюдаемый эффект: я активировал исполнение в проекте GitHub, 3 dev agents поднялись, взяли по задаче, и все 3 были заблокированы в одну и ту же секунду с falha ao preparar o worktree.

Ключ — асимметрия: api говорит с GitHub по HTTP (именно она создала репозиторий, закоммитила шаблон и создала dev/qa). Engine работает в файловой системе и знает только локальный bare-репозиторий. Поэтому проект в GitHub выполняет РАЗГОВОРНУЮ половину (Criativo, PO, Arquiteto) и bootstrap, но не половину ПОСТРОЕНИЯ.

Последствие для FASE 13b: сценарий из CLAUDE.md («ПРИНЯТЫЙ проект из форка через удалённый GithubProvider, настоящий DevAgent, dev реализует → удалённый PR → gates») сегодня невыполним. Это не попутный баг: поддержка удалённого требует clone, учётных данных внутри engine и push — фича с ADR.

O. Dev agent рождается на локальной модели (тот же корень, что у находки B)​

Три dev agents поднялись с Llama 3.2 1B (local), унаследованной от значения по умолчанию workspace. ADR 0020 запрещает маленькую локальную модель на семантическом шаге, а dev agent, пишущий код, — самый дорогой семантический шаг из существующих.

P. Событие блокировки без источника​

{"origin": null, "reason": "falha ao preparar o worktree", "diagnosis": "{:unsupported_provider, \"github\"}"}. CLAUDE.md требует, чтобы любой исход сбоя записывал ИСТОЧНИК (infra | modelo | código | política). Здесь это código (известное ограничение продукта), а пришло null. Диагноз спас положение, но правило не было соблюдено.

Q. agent.error с «неопределённым источником»​

В Criativo ход прерван: лента показала falha · origem indeterminada. RN-059 сработало (долговременная ошибка, агент сказал, чистое возобновление), но «indeterminada» — не один из четырёх источников, это диагноз методом исключения, который ADR 0020 запрещает.

R. PO породил перекрывающиеся истории​

ЗАКРЫТО ЧАСТИЧНО — стало RN-081. Идентичный заголовок отвергается; история, не добавляющая покрытия правилам, которые она цитирует, становится backlog.story_overlap_warned, предупреждением, а не блокировкой. Точная пара из этой находки по-прежнему проходит — у разных заголовков и обоснований для одного endpoint нет ничего механического, что их связывало бы. Есть тест, утверждающий это ограничение, чтобы оно было видимым, а не неявным. «Endpoint público de saudação determinística» и «Endpoint público GET /hello que responde saudação imediata» покрывают один и тот же endpoint. Ни дедупликации, ни предупреждения.

Прогон hello-limpo (проект 9c7c84f0, сессия 1f94de49) — 2026-08-06​

Настоящий dev agent, DeepSeek V4 Flash через OpenRouter, с включённым конвейером одобрения и каждым действием, решённым вручную. Задача была «Expor rota GET pública /api/saudacao». Она так и не началась: 18 ходов, 292.211 входных токенов, US$ 0,0275 и ноль написанных строк, и прогон закончился ошибкой провайдера.

S. Накопленный контекст превышает лимит провайдера и УБИВАЕТ исполнение (P1)​

На 18-м ходе вызов модели вернул {413, %{"message" => "request entity too large", "statusCode" => 413}}, ToolLoop не смог продолжить, и задача была заблокирована (dev.blocked, artifact.task_blocked, seq 151–152).

Причина механическая и накопительная: каждая терминальная команда вываливает весь вывод в историю цикла, а история идёт вместе с каждым следующим ходом. Самый большой УСПЕШНЫЙ запрос, записанный в token_usage, был на 28.993 входных токена; упавший не успел записать расход. Превышение касается размера запроса в байтах, а не окна контекста — find или git ls-files с длинным выводом весит в байтах гораздо больше, чем в полезных токенах.

Это напрямую связано с ADR 0055, под углом, которого ADR не предвидел: лестница одобрений не только удорожает исполнение — она его убивает. Каждый вопрос пользователю подталкивает агента ещё к одной исследовательской команде, вывод которой остаётся в истории навсегда. Гонка заканчивается 413 раньше, чем будет написана первая строка кода.

Триажу нужно решить, чьё это исправление — ContextManager (сжимать или обрезать вывод инструментов по размеру, а не только по давности), терминального исполнителя (потолок байт на вывод, с меткой обрезки) или обоих. Сегодня потолка нет нигде на этом пути.

  • Доказательство: session_events seq 150–152 сессии 1f94de49; token_usage актора dev-http-api.

T. Источник сбоя по-прежнему вне четырёх (повторение P и Q)​

dev.blocked из находки S записал:

{
"origem": "indeterminada",
"reason": "parou sem concluir nem reportar bloqueio",
"diagnosis": "falha na chamada ao modelo: {413, %{\"message\" => \"request entity too large\", \"statusCode\" => 413}}"
}

Это не новая находка: это третье нарушение того же правила, и поэтому оно записано как повторение, а не отдельный пункт. P поймал origin: null в dev.blocked; Q поймал "indeterminada" в agent.error. Здесь они встречаются: событие блокировки, значение "indeterminada".

Что добавляет этот случай и что делает его сильнее предыдущих: источник был тривиально выводим. Известный HTTP-статус провайдера — это modelo, без какой-либо двусмысленности, — само поле diagnosis называет его в той же строке, где origem сдаётся. Это не пограничный случай, а путь ошибки, не смотрящий на то, что он сам только что написал.

reason тоже лжёт: «parou sem concluir nem reportar bloqueio» описывает молчание, а произошёл сбой с установленной причиной. Тот, кто прочтёт на панели только reason, решит, что модель заблудилась.

  • Доказательство: session_events seq 151 сессии 1f94de49.
  • Нарушенное правило: CLAUDE.md («любой исход сбоя записывает ИСТОЧНИК — infra | modelo | código | política — никогда диагноз методом исключения»), источник — ADR 0020.

U. У терминального исполнителя нет границы проекта (P1)​

Внутри контейнера, выполняющего действия, /workspace — это монорепозиторий самого Brabo, а не worktree проекта, который находится в /data/project-workspaces/<projectId>/.worktrees/<agentId>.

Dev agent проекта hello-limpo тратил там ходы, думая, что это его проект: читал apps/engine/mix.exs и даже предлагал cat lib/engine/actions/git_executor.ex и sed -n '1,120p' lib/engine/dev/context_builder.ex — git-исполнитель и построитель контекста платформы, которая его выполняла.

И охват не ограничивается Brabo. for по /data/project-workspaces/*/ перечислил worktree другого проекта (dbd3e508-e0c7-4e29-b134-5d393f518269) с его коммитами и файлами; следующим шагом агент предложил войти в него, чтобы прочитать git remote -v и git log. Это было отклонено вручную.

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

ADR 0055 проектирует ПОЛИТИЧЕСКУЮ половину этого и прямо говорит, что не решает другую: область путей зависит от того, верно ли правило, а не хватает изоляции — монтирования на проект или контейнера на проект. Эта находка — та половина, которую ADR оставил объявленной открытой.

  • Доказательство: proposed_actions d1bfeda3 (отклонено) и 56374def (одобрено, перечислило другой проект) сессии 1f94de49.

V. Сессия числится closed, пока исполнение продолжается (P2)​

sessions.status сессии 1f94de49 — closed с 23:34:42, через тридцать секунд после рождения. Исполнение продолжалось до 00:56 — больше часа агент работал, предлагал действия и тратил токены в сессии, которую база считает завершённой.

Обе стороны одновременно непоследовательны: UI показывает «Sessão closed — não é possível enviar mensagens» и отрисовывает карточки одобрения, которые работают как обычно. Одобрение в закрытой сессии выполняет настоящую команду.

Это противоречит машине состояний, которую объявляет CLAUDE.md (created → active → closing → closed | closed_abnormally): closed должен быть терминальным. Это также отравляет любое измерение по сессиям — длительность, стоимость и «сколько сессий закончилось хорошо» читают состояние, не описывающее произошедшее.

Я не исследовал, кто записал closed и почему; это работа фазы, которая этим займётся, а не триажа.

  • Доказательство: sessions.updated_at = 23:34:42 со status = closed; session_events той же сессии до seq 152, в 00:56:46.

Подтверждено в production в этом прогоне​

  • RN-066 (слепота Arquiteto): 4 вызова assign_story_modules вместо 18, ни одного выдуманного имени, 1 module_map вместо 4, каждая история в семантически правильном модуле. Стоимость сессии упала с 7.271 до 3.259 микро.
  • RN-067 (сессия без процесса): сессия исполнения dbb84ce8 появляется в sessions И в engine.session_states — раньше сессия activate-execution рождалась сиротой.

Прогон проверки Фазы 12 — 2026-08-07​

Первый реальный прогон pnpm --filter api validacao:fase-12, незавершённого пункта 13a.1. Критерий закрылся (выход 0), но только после четырёх исправлений — трёх в ИНСТРУМЕНТЕ и одного в ПРОДУКТЕ. Исправления инструмента описаны в validacao-fase-12.md; здесь — исправление продукта.

W. Dev agent УМИРАЕТ, когда очередь модуля пустеет (P1) — ЗАКРЫТО​

При пустой очереди POST /internal/sessions/:id/tasks/claim отвечает 201 с content-length: 0. Сценарий использования возвращает null, но NestJS сериализует это как ПУСТОЕ тело — Req отдаёт "", а это не nil.

AgentIo.try_claim/2 сопоставлялся с ветвью найденной задачи и вызывал run_task(""), падая с BadMapError в Map.get("", "id", nil). Поскольку сервер restart: :temporary, агент умирал окончательно, а Monitor тут же стирал строку состояния.

Это касается НАСТОЯЩЕГО dev agent, а не только Noop: try_claim/2 живёт в общем AgentIo. И срабатывает на самом частом исходе — опустевшей очереди. Эффект — ровно противоположное тому, что дала Фаза 12b: вместо dev.idle под надзором и пробуждаемого событием — мёртвый процесс.

Набор тестов этого никогда не ловил, потому что fake правильно возвращает nil. Только реальное исполнение это выявляет — что, буквально, и есть тезис этой фазы.

ЗАКРЫТО — исправлено на границе (EngineApiClient.claim_task/4 нормализует пустое тело) и закреплено в контракте (AgentIo.try_claim/2 принимает "" наряду с nil), без изменения HTTP-статуса маршрута. Исключение из заморозки FASE 13 разрешено пользователем по той же причине, что и в Фазе F: без этого измерение было недостижимо. Проверено мутацией.

Реальный прогон с удалённым GitHub (FASE 13b) — 2026-08-07​

Первый прогон против настоящего удалённого репозитория (daneiel/test), с настоящим dev agent и openai/gpt-5-mini. Цепочка до продвижения прошла целиком; dev agent — нет. Подробности и измерения в validacao-real.md.

X. Dev agent сжигает потолок итераций в пустом репозитории (P1)​

Задача «Expor GET /saudacao» в только что подготовленном репозитории — только шаблон Gitflow, без кода. Агент потратил все восемь итераций на search_workspace/read_file, ища «где проект», ни разу не запустил команду и ни разу не записал файл. Блокировка: limite de iterações atingido, источник modelo, диагноз (nenhum terminal rodado).

Источник modelo технически верен и практически бесполезен: модель не ошиблась в суждении, она так и не дошла до суждения. Стоимость: 8 вызовов, 205 выходных токенов.

Это первый сценарий, в котором dev agent начинает с абсолютного нуля — все тесты и демо стартовали с workspace, где был код.

Y. search_workspace не отличает «пусто» от «не нашёл» — ЗАКРЫТО​

Первые пять вызовов вернули nenhum resultado, и агент прочитал это как «ищи лучше», а не «здесь ничего нет». Это была исполнимая часть находки X.

ЗАКРЫТО — инструмент стал отвечать по-разному в разных ситуациях. Workspace без единого файла возвращает «o workspace está VAZIO […] CRIE os arquivos necessários (write_file) em vez de continuar procurando»; workspace с файлами возвращает их число, сообщая, что поиск сработал, а не встречается именно термин.

Исправление — это фраза, а не потолок. Агенту не нужно было больше итераций — ему нужно было знать, что искать нечего. И случай из находки (только .github/ и docs/ из bootstrap) — НЕ пустой: есть тест, утверждающий, что там правильный ответ — число, а не указание создать.

Новый прогон был сделан, и X НЕ закрылся. Тот же исход — limite de iterações atingido, 8 вызовов, ни одного PR. Поведение изменилось (один поиск вместо пяти), результат — нет. Гипотеза, что причиной была фраза, оказалась НЕВЕРНОЙ: из восьми итераций семь — исследование, и остаётся одна на то, чтобы написать, закоммитить, запушить и открыть PR.

Причиной БЫЛ потолок. С TOOL_LOOP_MAX_ITERATIONS=25 dev agent исследовал, записал ТРИ файла и запустил npm test — и остановился на dev.awaiting_approval, а не на блокировке. Потолок 8 родился для разговорного агента и не подходит dev agent, которому нужно понять репозиторий прежде, чем действовать (apps/engine/config/runtime.exs:100).

X перестаёт быть «сжигает потолок, исследуя» и становится «потолок не тот для этого агента». Исправление продукта — НЕ поднять глобальное значение по умолчанию: Criativo не нужны 25 итераций, чтобы разговаривать. Это потолок по типу агента, и это продуктовое решение: остаётся в триаже.

ЗАКРЫТО в FASE 14d (RN-085). Потолок стал зависеть от типа в Engine.Harness.Iteracoes: 8 для разговаривающих, 60 для dev agent и субагента QA. Критерий повышения — не «много работает», а наличие token_budget_micros, сдерживающего расход, и поэтому infra-workflows использует тяжёлый инструмент и остаётся на 8: он работает без бюджета, и для него потолок — единственный существующий тормоз.

Z. Allowlist терминала управляет ГЛАГОЛОМ; область защищает только ПУТЬ​

С решённым потолком исполнение стало останавливаться на одобрении терминала. Просьба от 2026-08-06 была «permita sempre comandos desde que seja na pasta do projeto»; ADR 0055 дал ПОТОЛОК (вне папки никогда не автоодобряет), а глагол по-прежнему управляется закрытым списком.

Разрешения npm/pnpm/node/npx оказалось недостаточно: агент запустил ls -la. Каждый новый глагол попадает в require_approval.

Это не дефект ADR 0055, который никогда не обещал продвигать глаголы, — это разрыв между тем, что просили, и тем, что дали, и он держит лестницу.

Сработало так, как задумано (тоже записать)​

Psicólogo сам поставил диагноз, на тяжёлом тарифе, читая event log провалившегося исполнения, — и назвал обе причины точнее любого утверждения скрипта: отсутствие терминального tool.call и search_workspace, вводящий агента в заблуждение. Интроспекция продукта работает.

AA. Автоодобренный pr_open не имеет учётных данных и всегда падает на удалённом (P1)​

ЗАКРЫТО — стало RN-082. Api стала разрешать учётные данные git по OWNER workspace, переиспользуя тот же резолвер RN-058, а не реализуя правило заново. Проверено мутацией.

Обнаружено в 5-м прогоне 13b, первом, где цепочка дошла до PR.

ExecuteGitActionUseCase разрешает git-токен из action.decidedBy — пользователя, РЕШИВШЕГО действие (execute-git-action.use-case.ts:100). Когда политика автоодобряет, никто не решает: decided_by остаётся NULL, accessToken — undefined, и GitHub отвечает Requires authentication.

Контраст в том же запуске — доказательство:

действиекто выполняетучётные данныеисход
git_pushengineподставлены от владельца (git_auth.ex, RN-076)✅ executed
pr_openapiaction.decidedBy → NULL❌ failed

Push дошёл до GitHub — ветка feature/task-d4b36a5b существует на удалённом. Упал только REST-вызов, открывающий PR.

Это тот же класс, что RN-058 исправило для LLM («ключ, который тратит агент, — ключ OWNER»): путь git в api всё ещё разрешает по «кто решил», а не по «чей workspace».

Практическое последствие: при настроенной автономии — а это режим, ради которого существует Фаза F, — ни один dev agent не может открыть PR у удалённого провайдера. Путь работает, только когда человек кликает по каждому PR, что ровно и есть лестница, объявленная невыполнимой.

AB. Агент GATE не умеет ждать одобрения — превращается в сбой infra (P1)​

ЗАКРЫТО — ADR 0057. Агенты gate теперь ПРИОСТАНАВЛИВАЮТСЯ и возобновляются, как dev agent со времён ADR 0052: субагент возвращает {:awaiting, ...} со всем ctx, а QaLeadServer хранит состояние в полёте, подписывает Wake за субагентов и продолжает область с того места, где остановился, когда приходит решение.

Пока ожидание длится, область не консолидирует, не выносит вердикт и не блокирует задачу — она ждёт. Отказ тоже возобновляет, с причиной на месте результата.

Заявленное ограничение: перезапуск посреди ожидания теряет цикл, потому что pendente живёт в памяти lead. Gate снова запускается через Dispatcher, когда задача возвращается в цикл.

AC. Перенаправление (2>/dev/null) делает любую команду неодобряемой (P1)​

ЗАКРЫТО в обеих частях.

  1. parseCommand перестал считать >/>>/< разделителем — они не сцепляют никаких команд. Цель остаётся ТОКЕНОМ сегмента, намеренно: так echo x > /etc/passwd по-прежнему блокируется потолком области. Глагол стал правильным, а путь не стал свободным.
  2. /dev/null, /dev/stdin, /dev/stdout и /dev/stderr перестали считаться пользовательским путём. Список именно такой, и не весь /dev — есть тест, утверждающий, что /dev/sda по-прежнему вне области, потому что открытие /dev обменяло бы неудобство на дыру.

Проверено мутацией в обе стороны: ослабление /dev роняет тест диска, а отключение сцепления роняет 11 тестов.

Обнаружено в 7-м прогоне 13b, после расширения allowlist до 25 глаголов. Агент QA запустил:

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

Все глаголы были разрешены — ls, echo, cat. И всё равно получилось require_approval. Прогон чистых функций против команды:

segmentos: [["ls","-la"],["echo","---"],["cat","package.json","2"],["/dev/null"],…]
tokens de caminho: ["/dev/null","/dev/null"]
no escopo? false

parseCommand считает > разделителем сегментов, поэтому 2>/dev/null становится отдельным сегментом. Это ломается двумя независимыми способами:

  1. «глагол» сегмента /dev/null — сам /dev/null, которого никогда не будет в allow, а составная команда требует, чтобы был разрешён ЛЮБОЙ сегмент;
  2. /dev/null — токен АБСОЛЮТНОГО пути вне папки проекта, поэтому потолок RN-075 понижает auto_approve.

Последствия велики, потому что 2>/dev/null идиоматичен. Модели постоянно используют его, чтобы заглушить ожидаемую ошибку. На практике любая команда с перенаправлением вывода неодобряема политикой — проходит только по клику человека.

Это не нехватка глагола в списке, а форма команды. Расширение allowlist не помогает, и 7-й прогон — доказательство: 25 разрешённых глаголов, и всё равно застряло.

Две отдельные части для триажа:

  • сегментация по > в command-matcher.ts (существовавшая ранее и спорная: перенаправление — не композиция команд, как && и |);
  • потолок области, считающий /dev/null пользовательским путём (path-scope.ts, из Фазы F).

AD. Агент оборачивает команды в bash -lc, и у allowlist нет ответа (P1)​

Восьмой прогон 13b, с уже исправленными находками Y, AA, AB и AC. Dev agent сделал ОДИН вызов инструмента:

bash -lc npm test --silent

Глагол — bash. Его нет в allow, и получилось require_approval.

Отказ правильный, и важно, чтобы он был правильным. Разрешение bash обнулило бы весь allowlist: bash -lc <что угодно> обходит проверку глагола, включая встроенные deny. Allowlist, принимающий bash, — не allowlist.

Но это замыкает аргумент, который выстраивали прогоны 6, 7 и 8:

прогончто застрялочто я сделал
6-йhead вне спискарасширил до 25 глаголов
7-й2>/dev/null (ФОРМА, не глагол)исправил парсер и область (AC)
8-йbash -lc (ВЫЗОВ)—

Каждый прогон выявлял новую категорию, и ни одна не была «не хватило глагола». Allowlist глаголов не сходится против агента, который свободно выбирает, как вызвать то, что он хочет запустить, — а три формы (глагол, форма, вызов) — разные пространства, а не пункты одного списка.

Чего это НЕ означает: что allowlist неправилен. Он делает ровно то, что обещает, и отказ bash доказывает, что граница работает. Чего он не делает — не делает возможной автономию агента без вмешательства человека.

Два направления для триажа, разных по природе:

  1. Приостанавливаемые агенты gate (находка AB, открытая половина). Не устраняет одобрение — заставляет агента ЖДАТЬ его, а не умирать, и тогда клик пользователя разблокирует, а не проваливает. Это путь, который ADR 0052 уже открыл для dev agent.
  2. Политика по профилю агента. Dev agent в изолированном worktree отличается от агента, касающегося workspace пользователя. Сегодня оба используют один permissions.json. Это продуктовое решение с ADR.

AE. Агент QA пытается исправить код, который оценивает (P2)​

В финальных прогонах 13b, когда gates на LLM наконец заработали, субагент QA попытался исправить код, который оценивал, — вопреки своей роли. Кто судит, тот не чинит: gate, редактирующий то, что анализирует, перестаёт быть gate и становится ещё одним автором, а вердикт начинает касаться его собственной работы.

У него не получилось, и причина структурная прежде, чем политическая. Реестр инструментов QA — [ReadFile, SearchWorkspace, Terminal, EmitQaVerdict] (apps/engine/lib/engine/gates/qa_tools.ex:10): write_file не существует. Чтобы писать, ему пришлось бы пройти через terminal — и там он встречает два барьера, существовавших по другим причинам:

  1. allowlist глаголов, управляющий тем, что может выполняться (RN-075 и находки Z/AD);
  2. потолок области путей, понижающий до require_approval любую команду вне папки проекта.

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

Почему P2, а не P1. Ничего не утекло: поведение было попыткой и было остановлено. Находка фиксирует расхождение между тем, что просит промпт, и тем, что делает модель, и это расхождение — данные: оно говорит, что одна инструкция не удерживает роль, а удерживает структурный барьер.

Что не измерено. Сколько раз пытался, в каком именно прогоне и какой командой: у прогонов 9 и 10 нет записанных подробностей в validacao-real.md, который покрывает до восьмого. Запись находки без этих чисел намеренна — альтернативой было их выдумать, а урок Фазы 10 был как раз в том, что число, записанное по памяти, не считается.

Направление для триажа — то же, что «политика по профилю агента» в находке AD: правильный инструмент для gate не тот же, что для автора. Сегодня разница достигается упущением (реестр QA не включает write_file), что работает, но нигде не объявлено как гарантия — исчезнет в день, когда кто-нибудь добавит инструмент «для удобства».

Прогоны с ДВУМЯ модулями — 2026-08-08 и 2026-08-09​

Две истории, одна в api и одна в web, два dev agents, поднимающихся в одно и то же время. Такой прогон важен, потому что потолок параллелизма RN-083 что-то значит только при по-настоящему независимой работе — с одним модулем Dev Lead отказывается распараллеливать, и обоснованно.

Было три прогона, и их порядок и есть аргумент:

  1. 2026-08-08, сломавшийся. dev-web взял задачу и умер на fatal: not a git repository до первого хода: ноль потраченных токенов, задача заблокирована, агент idle. Это находка AF ниже.
  2. 2026-08-08, сразу после исправления. dev-web перешёл с 0 на 16 вызовов, и оба модуля были реализованы параллельно.
  3. 2026-08-09 (проект 9443f1f1, сессия 94428b1f), замер. Чистый прогон с уже работающим исправлением, полностью извлечённый через medir:execucao: 3m56s, 33 вызова, < US$ 0,01, без перезапуска engine, без немого хода, и оба gate (qa и secops) одобрили. Служит базовой линией стоимости исполнения двух модулей.

AF. Два dev agents, поднимаясь вместе, портят workspace друг друга (P1)​

Engine.Actions.Workspace.ensure!/4 сериализует инициализацию рабочего дерева по проекту через :global.trans — именно на случай N dev agents, поднимающихся при активации исполнения. Блокировка была правильной. Страж, решавший, стоит ли её брать, — нет.

Быстрый путь без блокировки спрашивал, существует ли .git (apps/engine/lib/engine/actions/workspace.ex:51). А init_from_bare! начинается с git init, который создаёт .git на первой строке — до fetch и до checkout. Пришедший в это окно читал «готово», пропускал блокировку целиком и запускал git worktree add против наполовину готового репозитория:

fatal: not a git repository

Почему это прошло через десять прогонов незамеченным. Окно существует, только если второй агент приходит во время инициализации, а активация поднимает лишь одного агента на модуль. С одной записью в module_map второго агента никогда не было. Дефект относится к Фазе 14d (параллелизм) и потребовал первого прогона, который действительно распараллеливал, чтобы проявиться.

Исправление — метка на диске, .brabo-workspace-pronto, записываемая только в конце инициализации; быстрый путь начинает спрашивать о ней. Блокировка не сменила места — сменился критерий «уже готово», который теперь истинен, только когда это действительно так. Workspace, созданный до появления метки, принимается и штампуется, но никогда не переинициализируется: переинициализация стёрла бы незакоммиченную работу.

Чему это учит о тесте, который не поймал. Уже существовал тест на 8 конкурентных ensure/3, и он проходил с багом. Все 8 стартуют одновременно, все видят пустой каталог, все идут к блокировке — окно никогда не проверяется. Попытка воспроизвести его по времени тоже не работает: против локального bare fetch заканчивается слишком быстро, а тест, гоняющийся за окном, проходит всегда, ничего не доказывая.

Дыру в apps/engine/test/engine/actions/workspace_test.exs закрыло построение промежуточного состояния вручную вместо попытки его подгадать по времени: git init в каталоге, без fetch и без checkout, — ровно то, что видел второй агент. Со старым стражем ensure! возвращает каталог и никогда не пишет метку — и именно отсутствие метки проверяет утверждение. Два других теста стерегут миграцию (workspace до метки не переинициализируется) и реальный случай двух конкурентных ensure!.

Это тот же шаблон, что три пробела самой Фазы 14d, записанный там: проверить деталь — не значит проверить путь к ней. Здесь деталь (блокировка) была правильной и протестированной; неверно решал путь к ней.