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

ADR 0190 — Infra поднимает контейнер при принятии handoff, шагом сервера и с засеянной автономией, и именно она передаёт работу Dev Lead

Статус​

Accepted. 2026-10-01 (AT-260, история HS-065, эпик EP-030, раунд 35; решение владельца от 01.10: «container_start рождается засеянным auto_approve при принятии handoff Infra, и сервер сам запускает подъём, когда есть module_routing, не завися от модели»). Пересматривает фразу «никогда не засеивается в автоодобрение» из ADR 0133 (RN-491) и опирается на страницу контейнеров из ADR 0136, не редактируя ни один из них: путь выбора, потолки и отказы, которые они установили, остаются; меняется то, КТО запускает первый подъём и ждёт ли он клика.

В том же раунде и тем же решением от 01.10 (AT-262, RN-672): «Dev Lead предлагается Infra, только при контейнере running; двойной handoff Arquiteto уходит». Это меняет отношение между ролями, установленное ADR 0053 (Dev Lead рождается от Arquiteto) и зафиксированное ADR 0085 в docs/fluxo.yml, не редактируя их, — решение 7 ниже.

Контекст​

Измерено в коде dev 2026-10-01, до изменения:

  • При реальном использовании 29.09 (сессия be70) Infra написала, что поднимет контейнер «параллельно» с PR инфраструктуры (seq 282), и не вызвала propose_container_start; единственной попыткой был container_start_via_runner (seq 280), отклонённый по режиму (mounted). Контейнер поднялся лишь в 06:47:53, человеком на /containers.
  • Подъём целиком зависел от модели: kickoff Infra Lead (build_kickoff) просил выбор на шаге 4, и ничто в коде не делало его, если модель не делала. AT-264 (RN-668) уже закрыла обе половины МОЛЧАНИЯ — весь пакет ответа диспетчеризуется до HALT PR, а завершение хода говорит, фразой сервера, когда подъём НЕ был предложен, — но оставила объявленным, что подъём как шаг сервера — это эта задача. То, что обещает RN-668 ("não foi proposta"), было правдой, но было обычным исходом.
  • Текст карточки говорил, что kickoff делает подъём необязательным («до, после или никогда», infra_lead_server.ex:955-961). Больше не воспроизводится: RN-668 переписала шаг 4 («происходит, только если вызван ДО неё или в ТОМ ЖЕ ответе»). Остальное воспроизводится.
  • Принятие handoff Infra засевало ДВЕ автономии (INFRA_AUTONOMY_SEEDS в accept-handoff.use-case.ts): open_infra_pr: auto_approve и terminal: deny. container_start по умолчанию оставался require_approval — maintainer в decide.ts, ВНЕ блока абсолютных потолков и ДОКАЗАННО способный достичь auto_approve через agent_autonomy (decide.spec.ts, "agent_autonomy auto_approve CONSEGUE chegar a auto_approve"), — а ProposeActionUseCase уже выполняет подъём на пути auto_approved (ветка существовала для тех, кто настраивал permissions.json). То есть весь механизм уже существовал, не хватало семени и запуска.
  • /containers (ADR 0136, RN-521) предлагает как ЧЕЛОВЕК (actor.kind: 'user'), а agent_autonomy читается только когда актор — агент. Засев для Infra ничего не меняет на этой странице.

Решение​

  1. Семя. Принятие handoff, адресованного infra, засевает ТРЕТЬЮ автономию: container_start: auto_approve, рядом с двумя прежними. Только её: container_start_via_runner и container_stop остаются настраиваемыми и никогда не засеваются, а container_remove остаётся в абсолютном потолке decide.ts (RN-495), который не пересекает никакая автономия.

  2. Запуск — шаг СЕРВЕРА, а не новый инструмент. В kickoff Infra Lead — ходе, рождающемся из принятого handoff, — ДО первого обращения к модели сервер читает контекст инфраструктуры (одно чтение, то же, что для текста kickoff) и, если есть ДЕЙСТВУЮЩАЯ маршрутизация (artifact.module_routing со status: roteado и хотя бы одним кандидатом) и проект поднимается через broker (container/mounted), предлагает container_start ТЕМ ЖЕ путём, что инструмент (propor_container_start/2): сначала локальные отказы по режиму и состоянию (RN-566, RN-610), затем propose_action, где api отказывает без broker (RN-591), разрешает автономию и — auto_approved — выполняет ExecuteContainerStartUseCase, который выбирает образ через DecidirImagemDoProjetoUseCase с decidedBy: 'infra-lead' (RN-491). Никакого параллельного пути выбора не рождается.

  3. Выбор сервера детерминирован. Кандидат с НАИБОЛЬШИМ числом модулей маршрутизации; при ничьей — тот, что встречается первым. Сеть none и ресурсы по умолчанию — те же значения по умолчанию, что у инструмента. rationale говорит, что это был сервер и почему («кандидат Arquiteto для N из M модулей»). Это всегда один из кандидатов, и api отклоняет любой другой.

  4. След — инструментальный, без лжи о том, кто вызвал. Шаг записывает долговременные tool.call (с origem: "servidor") и tool.result и НЕ кладёт сообщение role: "tool" в историю модели: модель его не вызывала, а ответ инструмента без вызова providers отклоняют. Модель узнаёт о подъёме из ТЕКСТА kickoff, который начинает говорить, что УЖЕ произошло (предложение и возвращённый статус или отказ и причину), вместо того чтобы просить выбор.

  5. runner остаётся как есть. Путь container_start_via_runner (поднимает уже решённый образ на машине пользователя) не получает ни шага сервера, ни семени: решение владельца его не коснулось. Kickoff проекта runner сохраняет прежние шаги, а подъём по-прежнему предлагается моделью и решается человеком.

  6. RN-668 остаётся согласованной. Шаг сервера помечает ход ТЕМ ЖЕ registrar_subida/3, что и инструмент: подъём, предложенный сервером, не получает фразы "não foi proposta" в завершении; ОТКЛОНЁННЫЙ подъём сервера (и не исправленный моделью) получает фразу отказа, которая правдива. Завершение по-прежнему говорит в случаях, когда подъём не был делом сервера (нет маршрутизации, runner), а модель его не предложила.

  7. Dev Lead предлагается Infra, при контейнере running (RN-672). «Confirmar arquitetura pronta» начинает предлагать только Infra: offerDevHandoff, маршрут engine offer-dev-handoff и :offer_dev_handoff/handoff_dev_pendente у Arquiteto уходят. Сервер Infra Lead предлагает infra → dev-lead (режим if_absent, ADR 0182), когда контейнер проекта ЗАРЕГИСТРИРОВАН running, спрашивая в конце каждого своего хода и при приходе container.running из outbox — второй потребитель этого события, рядом с wake dev agents (RN-502). Вторая дверь репозитория при принятии Dev Lead (RN-582) остаётся: принятие то же, изменилось лишь, кто предлагает.

Последствия​

  • Обещание становится правдой по построению в обычном случае. У проекта container/mounted с маршрутизацией подъём предложен — и, с семенем, выполнен — до того, как модель что-либо скажет.
  • «Никогда не засеивается» перестаёт быть правдой, и это ЕДИНСТВЕННОЕ меняющееся правило. Минимальная роль остаётся maintainer, читаемой как ЭФФЕКТИВНАЯ роль того, кто открыл сессию (resolveEffectiveRole.forProject(session.createdBy)): сессия, открытая developer, получает deny, как раньше, и kickoff говорит об отказе. deny из permissions.json по-прежнему побеждает автономию.
  • Выключение — это правка СПЕЦИФИЧНОГО правила. Переключатель ручной/авто на карточке агента пишет шаблон (*, RN-153), а специфичное правило побеждает шаблон: перевод Infra в «ручной» НЕ выключает это семя — та же ситуация, что уже действовала для open_infra_pr начиная с Фазы 4a. Кто хочет подъём с кликом, должен поменять правило container_start у Infra. Объявлено, здесь не решено.
  • Повторное принятие Infra заново засевает. Принятие делает upsert, как уже делало с двумя прежними семенами: новый handoff Infra в новой сессии снова ставит auto_approve. То же поведение, что у существующих семян.
  • Kickoff может ждать подъёма. propose_action контейнера, выполненный автоодобренным, отвечает после start broker (потолок 225 с в engine, RN-605), а шаг выполняется до первого обращения к модели: ход kickoff остаётся working это время, в Task, с «Parar», обслуживаемым как всегда. Большой образ, превышающий потолок pull (RN-605), приходит как failed, и kickoff говорит это модели.
  • Контекст инфраструктуры, который падает, ничего не поднимает. Без чтения контекста нет маршрутизации для выбора; kickoff деградирует, как раньше, и подъём в этом ходе снова зависит от модели. Подъём сервера также происходит только в kickoff: исправление gate и сообщение composer его не повторяют.
  • Dev Lead перестаёт быть принимаемым без контейнера. Цепочка становится Arquiteto → Infra → (контейнер running) → Dev Lead → исполнение, и docs/fluxo.yml говорит это: выход handoff-duplo у Arquiteto становится одним handoff, к Infra, а Infra получает два выхода к Dev Lead (контейнер и handoff). Проект, чей контейнер никогда не поднимается, не доходит до Dev Lead этим путём; ручной handoff агенту по выбору (ADR 0109) продолжает существовать.
  • Уведомление из outbox — at-most-once, как Engine.Dev.Wake: без работающего Infra Lead, когда приходит container.running (сессия закрыта, engine перезапускается), предложение ждёт конца следующего хода Infra. Проект runner зависит от этого уведомления, потому что его подъём никогда не происходит внутри хода Infra.
  • Комментарий в decide.ts, говорящий «не засеян в INFRA_AUTONOMY_SEEDS», устаревает в этом изменении, потому что файл принадлежит другому направлению, идущему в том же раунде; само правило (container_start вне потолков, способный достичь auto_approve) не меняется.