ADR 0175 — The Infra Lead converses through the composer, and first its turn goes through TurnoAssincrono
Status
Accepted. 2026-09-27 (AT-141). Maintainer decision: yes — the Infra Lead starts conversing through the composer and becomes the SEVENTH conversational agent. References ADR 0163 (the click responds on accept), which this document does NOT edit: what it decided for the six conversational agents now applies to the seventh, by the SAME mechanism. Also references ADR 0038 (the agent hierarchy, lead and subagent) and ADR 0086 (the Dev Lead suspension, which the Infra Lead does not have). Rule: RN-617. Closes the open decision that RN-584 left in writing.
Context
RN-584 (AT-098) removed the default recipient of
the chat message and gave infra a NAMED refusal (422 agente_sem_conversa),
leaving the product question declared: does the Infra Lead converse through the
composer? And it declared the price of answering "yes" without preparation: the
user_message/2 that InfraLeadServer exported ran the ENTIRE turn inside
handle_call, with a 180 s ceiling on the GenServer.call.
Measured in the code before the change, the Infra Lead was outside ALL the pieces that ADR 0163 and the following RNs gave the other six:
| Piece | The six | The Infra Lead |
|---|---|---|
Immediate accept, working recorded first (RN-578) | TurnoAssincrono.iniciar/3 | the whole turn in handle_call/handle_cast |
Named refusal 409 turno_em_andamento | yes | the second message waited in the process queue |
| "Stop" (RN-122) | via_for/2 of cancel/2 | via_for/2 did not know it, nor was there a Task to kill |
| Orphan turn on restart (RN-586) | TurnoOrfao | "left out — declared in RN-586" |
| Stop on session close (RN-581) | terminate/2 abandons the Task | killed after 5 s of waiting |
| Narrated iteration ceiling (RN-166) | toolloop.limit_reached | the loop ended silently at step 14 |
Also the kickoff: being a cast, it held no click, but it occupied the process
for the entire turn — measured on the AT-089 installation, 8.4 min. A message
arriving in the meantime would wait the 8.4 min in the queue and exit with
:timeout in the controller (500), and "Stop" would only be served afterwards.
And the ceiling: the propose_action of container actions that the api runs
auto-approved waits up to 225 s (RN-605), more
than the 180 s of user_message/2. A message leading the model to propose
container_start could blow the GenServer.call with the proposal still
standing.
Decision
- First the turn, then the clause. The Infra Lead's THREE turns — kickoff,
gate correction (
{:correct, _}) and message — go up throughEngine.Agents.TurnoAssincrono, the SAME module as the six. The kickoff stays acast, fired only on the FRESH start of the accepted handoff; what changes is where it runs (in a Task). The olduser_message/2, which ran inline, ceased to exist: the name stayed, and now it is the accept — there are not two paths. - Consolidation with Workflows runs INSIDE the Task. The end of the turn
(
concluir/1: HALT ofpropose_infra_pr→WorkflowsAgent→ single PR) is part of the turn function and returns thestate; the end signals (agent.done,agent.status: idle) come only fromTurnoAssincrono, in the server process (RN-585). "Stop" also kills the Workflows in progress. - The gate correction is not discarded.
TurnoAssincrono.iniciar/3withoutfromignores the request when a turn is in progress (the defensive kickoff case). For the correction that would lose the gate's request, so it enters a QUEUE (correcoes_pendentes) and goes up at the turn's close — success, failure, crash or "Stop" —, in arrival order. It is the mould of the Arquiteto'shandoff_dev_pendente. - The Infra Lead enters the engine's lists:
@agentes_de_conversaand the own clause ofmessage/2(the RN-584 refusal goes),via_for("infra")in "Stop", andTurnoOrfao(@agentes).Engine.Agents.Conversacionaisalready listed it. - The iteration ceiling is its own, 14, the same as Arquiteto, Dev Lead,
UX Designer and Staff — and step 14 now emits
toolloop.limit_reached. - The screen offers it by the usual rule.
infraentersAGENTES_DE_CHAT(apps/web/src/lib/session-readiness.ts): it is the recipient when it is the most recentagent.activated, with no default recipient (RN-584). The acceptance of ITS handoff stays in the own card of RN-499, and the thread card excludes it by NAME — otherwise the invitation would appear twice and, being the oldest, would again hide the Dev Lead's (RN-136). - Conversing does not open external effects. The tools are the same four;
the infra PR and the two container start-ups still are born as
proposed_action, with the local refusals of RN-566/RN-577/RN-610 intact.
Consequences
- The 180 s ceiling stops mattering for the turn. The
GenServer.callofuser_message/2only waits for the accept (milliseconds); the 225 s of the containerpropose_actionrun inside the Task, with nobody waiting synchronously. The number 180 000 stayed, equal to the other six, as the ACCEPT ceiling. - A message during the kickoff is refused by name. Before it waited; now it
is 409
turno_em_andamento, with the durableagent.error— the same rule as the other six. - The generated list does not change.
SOLO_CONVERSATIONAL_AGENTS(generated bypnpm --filter api gerar:areas) is the list of agents WITHOUT an area that a manual handoff addresses; the Infra Lead is an area lead and stays out of it.AGENTES_DE_CHATis not generated: it is the list thescripts/ci/destinos-do-composer.spec.tsguard checks against the clauses. - The screen narrates its tools. The four got a sentence in the activity
strip (
toolNarration.json), instead of the fallback. - Declared and not closed: the Infra Lead has no
ask_structured_questionsnor backlog reading — conversing with it is conversing with what the kickoff gave it and with the usual tools, and widening that is a separate front. The message does not rerun the kickoff: an Infra Lead brought back up by a message (engine restarted) rehydrates the history and responds, without regenerating artifacts.