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

ADR 0175 — Infra Lead разговаривает через composer, а прежде его ход проходит через TurnoAssincrono

Статус​

Accepted. 2026-09-27 (AT-141). Решение сопровождающего: да — Infra Lead начинает разговаривать через composer и становится СЕДЬМЫМ разговорным агентом. Ссылается на ADR 0163 (клик отвечает при принятии), который этот документ НЕ редактирует: решённое им для шести разговорных начинает действовать для седьмого тем ЖЕ механизмом. Ссылается также на ADR 0038 (иерархия агентов, lead и субагент) и ADR 0086 (приостановка Dev Lead, которой у Infra Lead нет). Правило: RN-617. Закрывает открытое решение, которое RN-584 оставила письменно.

Контекст​

RN-584 (AT-098) убрала адресата по умолчанию у сообщения чата и дала infra НАЗВАННЫЙ отказ (422 agente_sem_conversa), оставив объявленным продуктовый вопрос: разговаривает ли Infra Lead через composer? И объявила цену ответа "да" без подготовки: user_message/2, экспортируемый InfraLeadServer, выполнял ход ЦЕЛИКОМ внутри handle_call, с пределом 180 с в GenServer.call.

Измерено в коде до изменения: Infra Lead был вне ВСЕХ частей, которые ADR 0163 и последующие RN дали остальным шести:

ЧастьШестьInfra Lead
Немедленное принятие, working записан заранее (RN-578)TurnoAssincrono.iniciar/3весь ход в handle_call/handle_cast
Названный отказ 409 turno_em_andamentoдавторое сообщение ждало в очереди процесса
"Parar" (RN-122)via_for/2 из cancel/2ни via_for/2 его не знал, ни Task не было, чтобы убить
Осиротевший ход при перезапуске (RN-586)TurnoOrfao"fica de fora — declarado na RN-586"
Остановка при закрытии сессии (RN-581)terminate/2 бросает Taskубит после 5 с ожидания
Озвученный предел итераций (RN-166)toolloop.limit_reachedцикл молча заканчивался на 14-м шаге

Также kickoff: будучи cast, он не удерживал никакого клика, но занимал процесс на весь ход — измерено в установке AT-089, 8,4 мин. Сообщение, пришедшее в это время, ждало бы 8,4 мин в очереди и выходило бы по :timeout в controller (500), а "Parar" было бы обслужено только потом.

И предел: propose_action для действий с контейнером, которые api выполняет автоодобренными, ждёт до 225 с (RN-605), больше, чем 180 с user_message/2. Сообщение, побудившее модель предложить container_start, могло переполнить GenServer.call при ещё стоящем предложении.

Решение​

  1. Сначала ход, потом клаузула. ВСЕ ТРИ хода Infra Lead — kickoff, исправление gate ({:correct, _}) и сообщение — поднимаются через Engine.Agents.TurnoAssincrono, ТОТ ЖЕ модуль, что у шести. Kickoff остаётся cast, запускаемым только при СВЕЖЕМ старте принятого handoff; меняется то, где он выполняется (в Task). Старый user_message/2, выполнявшийся inline, перестал существовать: имя осталось, и теперь это принятие — двух путей нет.
  2. Консолидация с Workflows выполняется ВНУТРИ Task. Конец хода (concluir/1: HALT propose_infra_pr → WorkflowsAgent → единая PR) является частью функции хода и возвращает state; сигналы конца (agent.done, agent.status: idle) исходят только из TurnoAssincrono, в процессе сервера (RN-585). "Parar" убивает и идущий Workflows.
  3. Исправление gate не отбрасывается. TurnoAssincrono.iniciar/3 без from игнорирует запрос, когда идёт ход (защитный случай kickoff). Для исправления это потеряло бы запрос gate, поэтому оно входит в ОЧЕРЕДЬ (correcoes_pendentes) и поднимается при завершении хода — успех, сбой, crash или "Parar", — в порядке поступления. Это образец handoff_dev_pendente Arquiteto.
  4. Infra Lead входит в списки engine: @agentes_de_conversa и собственная клаузула message/2 (отказ RN-584 уходит), via_for("infra") в "Parar" и TurnoOrfao (@agentes). Engine.Agents.Conversacionais уже его перечислял.
  5. Предел итераций — его собственный, 14, тот же, что у Arquiteto, Dev Lead, UX Designer и Staff, — и 14-й шаг начинает выдавать toolloop.limit_reached.
  6. Экран предлагает его по обычному правилу. infra входит в AGENTES_DE_CHAT (apps/web/src/lib/session-readiness.ts): он является адресатом, когда он — самый свежий agent.activated, без адресата по умолчанию (RN-584). Принятие ЕГО handoff остаётся в собственной карточке RN-499, а карточка ленты исключает его по ИМЕНИ — иначе приглашение появлялось бы дважды и, будучи самым старым, снова скрывало бы приглашение Dev Lead (RN-136).
  7. Разговор не открывает внешнего эффекта. Инструменты те же четыре; PR инфраструктуры и два подъёма контейнера по-прежнему рождаются proposed_action, с нетронутыми локальными отказами RN-566/RN-577/RN-610.

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

  • Предел 180 с перестаёт иметь значение для хода. GenServer.call user_message/2 ждёт лишь принятия (миллисекунды); 225 с propose_action контейнера идут внутри Task, без синхронного ожидания. Число 180 000 осталось, как у остальных шести, пределом ПРИНЯТИЯ.
  • Сообщение во время kickoff отклоняется с именем. Раньше ждало; теперь это 409 turno_em_andamento с durable agent.error — то же правило, что у остальных шести.
  • Генерируемый список не меняется. SOLO_CONVERSATIONAL_AGENTS (генерируемый pnpm --filter api gerar:areas) — это список агентов БЕЗ области, которых адресует ручной handoff; Infra Lead является lead области и остаётся вне его. AGENTES_DE_CHAT не генерируется: это список, который защита scripts/ci/destinos-do-composer.spec.ts сверяет с клаузулами.
  • Экран повествует его инструменты. Все четыре получили фразу в полосе активности (toolNarration.json) вместо fallback.
  • Объявлено и не закрыто: у Infra Lead нет ни ask_structured_questions, ни чтения backlog — разговор с ним — это разговор с тем, что ему дал kickoff, и с обычными инструментами, а расширение этого — отдельное направление. Сообщение не перезапускает kickoff: Infra Lead, поднятый сообщением (перезапущенный engine), регидратирует историю и отвечает, не перегенерируя артефакты.