ADR 0090 — QA-strategy and AppSec as a second MOMENT, not a new agent
- Status: Accepted
- Date: 2026-08-17
- Context: conscious decision of the product owner to anticipate the
trigger — not "the natural trigger"
docs/fluxo.ymlforesaw (hoje_absorvido_por+criterio_de_separacao), but the actual work of building the thing that connects the gate - Activates: the
implementavelgate (docs/gates.yml,status: plannedsince PHASE 14d) - Revises part of: ADR 0053
(which declared the
implementavelgate asplanned) - Direct precedent: ADR 0038 (the QA
area — Lead + subspecialties), ADR 0085
(
docs/fluxo.yml, which already foresaw both roles asproposto)
Context
docs/fluxo.yml (ADR 0085) registers two target-model roles as
proposto, each with the separation criterion written into the file
itself:
qa-estrategia — hoje_absorvido_por: area-qa (which only acts AFTER, on the PR). criterio_de_separacao: When the
implementavelgate activates — the test plan is its input. It can be the qa-lead itself in a second MOMENT, not necessarily a new agent: the separation is of deliverable.appsec — hoje_absorvido_por: secops (which only acts on the PR). criterio_de_separacao: Same pattern as QA: two MOMENTS, not two agents for now. Threat model at design time activates when the
implementavelgate exists. A separate agent only with real production.
The declared trigger ("when the implementavel gate activates") never
fired organically — nothing in the product needed it yet. The product
owner decided to anticipate: the trigger is the actual work of
building the mechanism that connects the gate, not a usage symptom
waiting to happen.
This delivery covers qa-estrategia in full. appsec remains
proposto, under the SAME doctrine (two moments, not two agents), with
the implementation in a separate round — the pattern and the decision are
one and the same, described here; appsec/threat-model code is left
for when that round runs.
Decision
QA-strategy is the qa-lead in a second MOMENT — the same process, a
separate deliverable. No new agent, no new GenServer with its own name
on the roster. Engine.Gates.QaLeadServer gains an ADDITIVE entry point,
run_design/3, alongside the usual run/2 (PR review):
The pieces
Engine.Gates.QaEstrategiaContext.fetch/3— LIGHT context: just the story (fromEngineApiClient.list_backlog/1, the tree the PO already reads since RN-164) and the currentmodule_map(fromEngineApiClient.get_infra_context/2, the SAMEGetInfraContextUseCasethe Infra Lead consumes, here only via themoduleMapfield) — NOdev_state, NOworktree_path. No new api route: both functions already existed, and theimplementavelgate runs PRE-DEV — there's no dev agent, worktree, ortask_idyet.Engine.Gates.QaEstrategiaAgent— a STATELESS module (not aGenServer), the same FORM asEngine.Gates.QaPerformanceSegurancaAgent: a tool registry WITHOUTTerminal(ReadFile,SearchWorkspace,EmitPlanoDeTeste), running the harness's genericToolLoop. None of the three tools go throughEngine.Harness.Hooks.ActionPipeline(onlyterminal/write_filedo) — this agent NEVER suspends, andrun_design/3runs synchronously insidehandle_castitself. It emitsartifact.plano_de_teste(schema validated inEngine.Harness.ArtifactSchemas) into the event log of the session that called it.Engine.Gates.QaLeadServer.run_design/3—GenServer.cast, same style asrun/2, WITHOUTDevAgentState.find_by_task_id. Called throughEngine.Gates.Dispatcher.run_qa_estrategia/3(the SAME test-swappable indirectionrun_qa/2/run_secops/2already use — it avoids bringing up a real GenServer in a light test).Engine.Agents.DevLeadTools.assess_implementability— a new Dev Lead tool. Reads the most recentartifact.plano_de_testefor the story from the Dev Lead's OWN session history:- no plan yet — triggers
Dispatcher.run_qa_estrategia/3and returns{:error, text}asking to try again. A tool error is INPUT to the loop, not the end of the line (RN-163) — the Dev Lead has 14 iterations to try again once the plan exists; - with plan — builds the verdict (
implementavel/inviavel+ justification, with the test plan embedded in the payload) and callsEngineApiClient.propose_action/5with"assess_implementability", the SAME three-outcome pattern aspropose_execution_plan(RN-284, ADR 0086).
- no plan yet — triggers
decide.tsgainsassess_implementability, minimum rolemaintainer, DELIBERATELY outside the absolute-cap block — same reasoning aspropose_execution_plan: it's an initial session decision, not an overrun of an already-authorized cap.docs/gates.yml,implementavel:status: planned→active, withevidenciapointing to the event log (proposed_action.createdfiltered byactionType: assess_implementability).severidaderemainswarn— untouched; the record already said "born warn even once active."
Why the iteration cap gained no new clause
Engine.Harness.Iteracoes.tipo/1 classifies an unknown agent as
:conversacional (cap 8). "qa-estrategia" falls under that default,
and it's the RIGHT call — not a gap. The criterion in
RN-085 isn't "who works a lot": it's
"what holds the spend back beyond the iteration cap." This agent runs
WITHOUT token_budget_micros (there's no task, it's PRE-DEV), the same
situation as infra-workflows — which also stays at 8 even while using a
tool, because raising the cap without a budget underneath would multiply
the worst case with nothing to contain it.
Consequences
For
- Behavior now matches what
docs/fluxo.ymlalready declared — the same class of fix as ADR 0086 (closing the divergence between the declared flow and the code), except here the "code" had never existed at all. - The deliverable separation proves itself with no new infrastructure cost: zero new GenServer, zero new HTTP route, zero table.
- The
implementavelgate gains measurable evidence in the event log, same spirit as ADR 0054 — the recorded decision is what makes the gate's passage measurable.
Against
- The waiting window between "I triggered the evaluation" and "the plan
exists" is asynchronous and has no callback. The Dev Lead only finds
out the plan is ready when the MODEL decides to call
assess_implementabilityagain — there's no automatic resumption (unlike ADR 0086's suspend-for-approval, which IS event-driven). Accepted becauseEngine.Gates.QaEstrategiaAgentruns in a separate process (qa-lead) and the Dev Lead can't block a synchronousrun_turn/2waiting on a result from ANOTHER process without coupling the two — and the cap of 14 iterations gives enough slack for the model to try again within the SAME turn. appsecremainsproposto. This delivery doesn't resolve the second role declared in the samedocs/fluxo.ymlblock — a conscious scope decision, recorded here so the next round can reuse the SAME pattern without reopening the decision.
Alternatives considered
A new qa-estrategia agent, its own GenServer. Rejected: docs/fluxo.yml
itself already argued against it — "not necessarily a new agent: the
separation is of deliverable." A new GenServer would duplicate the QA
area's Wake/Registry wiring for no gain, since this path never
suspends and doesn't need the resumption machinery that justifies a
dedicated process.
assess_implementability blocks waiting for the test plan (a fully
synchronous call). Rejected — it would require the Dev Lead to call
QaEstrategiaAgent.run/4 directly, in its OWN process, and run the
QA-strategy ToolLoop INSIDE the Dev Lead's turn. Technically possible
(neither toolloop suspends), but it couples the two agents — a read
error in QA-strategy would bring down the Dev Lead's whole turn, and one's
iteration cap would start eating into the other's. Keeping the processes
separate, with the result passing through the event log, is the SAME
design all api↔engine communication already uses.
Reuse Engine.Dev.ContextBuilder.fetch/3 (the builder
QaPerformanceSegurancaAgent already uses) instead of a new builder.
Rejected: that builder is keyed by task_id — it assumes a dev agent,
worktree, and code already committed to a branch. Forcing a fictitious
task_id for a PRE-DEV evaluation would invert the order the gate exists
to guarantee (evaluate BEFORE burning tokens writing code).
References
docs/fluxo.yml— rolesqa-estrategia(nowactive) andappsec(remainsproposto)docs/gates.yml—implementavelgate (nowactive)- ADR 0053 — declared the
implementavelgate asplanned - ADR 0038 — the QA area (Lead + subspecialties), the structural precedent for this design
- ADR 0086 — the
three-outcome
proposed_actionpatternassess_implementabilityrepeats apps/engine/lib/engine/gates/qa_estrategia_agent.ex,qa_estrategia_context.ex,qa_lead_server.ex,dispatcher.exapps/engine/lib/engine/agents/dev_lead_tools.ex,dev_lead_server.exapps/api/src/domain/actions/decide.ts