ADR 0175 — Infra Lead 通过 composer 对话,在此之前它的回合先经过 TurnoAssincrono
状态
已接受。 2026-09-27(AT-141)。维护者的决定:是——Infra Lead 开始通过 composer 对话,成为第七个对话式 agent。引用 ADR 0163(点击在接受时响应),本文档不编辑它:它为六个对话式 agent 决定的内容,通过同一个机制对第七个也生效。还引用 ADR 0038(agent 的层级、lead 和子 agent)和 ADR 0086(Dev Lead 的挂起,Infra Lead 没有)。规则:RN-617。关闭 RN-584 以书面形式留下的悬而未决的决定。
背景
RN-584(AT-098)去掉了聊天消息的默认收件人,并给了 infra 一个具名的拒绝(422 agente_sem_conversa),留下声明的产品问题:Infra Lead 是否通过 composer 对话?并声明了在没有准备的情况下回答“是”的代价:InfraLeadServer 导出的 user_message/2 在 handle_call 内部运行整个回合,GenServer.call 上限为 180 秒。
修改之前在代码中测得,Infra Lead 不在 ADR 0163 及其后续 RN 给其他六个的所有部件之中:
| 部件 | 六个 | Infra Lead |
|---|---|---|
立即接受,先写入 working(RN-578) | TurnoAssincrono.iniciar/3 | 整个回合在 handle_call/handle_cast 中 |
具名拒绝 409 turno_em_andamento | 是 | 第二条消息在进程的队列中等待 |
| “Parar”(RN-122) | cancel/2 的 via_for/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(500)结束,而“Parar”只会在之后才被处理。
还有上限:api 自动批准执行的容器动作的 propose_action 最多等待 225 秒(RN-605),超过 user_message/2 的 180 秒。一条让模型提议 container_start 的消息,可能在提议仍然有效时让 GenServer.call 超时。
决策
- 先是回合,然后是子句。 Infra Lead 的三种回合——kickoff、gate 纠正(
{:correct, _})和消息——都通过Engine.Agents.TurnoAssincrono启动,与六个使用的是同一个模块。kickoff 仍然是cast,只在被接受 handoff 的全新启动时触发;改变的是它在哪里运行(在一个 Task 中)。旧的内联运行的user_message/2不复存在:名字保留了,现在它是接受——没有两条路径。 - 与 Workflows 的整合在 Task 内部运行。 回合的结束(
concluir/1:propose_infra_pr的 HALT →WorkflowsAgent→ 唯一的 PR)是回合函数的一部分,并返回state;结束信号(agent.done、agent.status: idle)只从TurnoAssincrono发出,在服务器的进程中(RN-585)。“Parar”也会杀死进行中的 Workflows。 - gate 纠正不会被丢弃。 不带
from的TurnoAssincrono.iniciar/3在有回合进行中时会忽略请求(kickoff 的防御性情况)。对于纠正,这会丢失 gate 的请求,所以它进入一个队列(correcoes_pendentes),并在回合结束时启动——成功、失败、崩溃或“Parar”——按到达顺序。这是 Arquiteto 的handoff_dev_pendente的模子。 - Infra Lead 进入 engine 的列表:
@agentes_de_conversa以及message/2自己的子句(RN-584 的拒绝移除)、“Parar”中的via_for("infra"),以及TurnoOrfao(@agentes)。Engine.Agents.Conversacionais本来就列着它。 - 迭代上限是它自己的,14,与 Arquiteto、Dev Lead、UX Designer 和 Staff 相同——第 14 步开始发出
toolloop.limit_reached。 - 界面按一贯的规则提供它。
infra进入AGENTES_DE_CHAT(apps/web/src/lib/session-readiness.ts):当它是最近的agent.activated时,它就是收件人,没有默认收件人(RN-584)。它的 handoff 的接受仍然在 RN-499 的专用卡片中,消息流的卡片按名称排除它——否则邀请会出现两次,而且由于它是最早的,会再次掩盖 Dev Lead 的那个(RN-136)。 - 对话不会打开外部效果。 工具还是那四个;infra 的 PR 和两种容器启动仍然以
proposed_action诞生,RN-566/RN-577/RN-610 的本地拒绝保持不变。
后果
- 180 秒的上限对回合不再重要。
user_message/2的GenServer.call只等待接受(毫秒级);容器propose_action的 225 秒在 Task 内部流逝,没有人在同步等待。数字 180 000 保留了,与其他六个相同,作为接受的上限。 - kickoff 期间的消息会以具名方式被拒绝。 以前它会等待;现在是 409
turno_em_andamento,带持久的agent.error——与其他六个相同的规则。 - 生成的列表不变。
SOLO_CONVERSATIONAL_AGENTS(由pnpm --filter api gerar:areas生成)是手动 handoff 所指向的没有领域的 agent 的列表;Infra Lead 是领域的 lead,仍然不在其中。AGENTES_DE_CHAT不是生成的:它是守卫scripts/ci/destinos-do-composer.spec.ts与子句对照的列表。 - 界面会叙述它的工具。 这四个在活动条中获得了句子(
toolNarration.json),而不是回退。 - 已声明而未关闭: Infra Lead 没有
ask_structured_questions,也没有 backlog 的读取——与它对话,就是与 kickoff 给它的东西以及一贯的工具对话,扩展这一点是独立的战线。消息不会重新执行 kickoff:一个被消息重新拉起的 Infra Lead(engine 重启后)会重新水合历史并回答,而不重新生成构件。