ADR 0009 — Agente PO, backlog (epics/stories/tasks) e rastreabilidade
- Status: aceito
- Data: 2026-07-24
- Fase: 3b (sessão 2)
Contexto
A 3b sessão 1 entregou o Criativo conversacional (emite business_rule,
consolida product_brief e oferece handoff ao PO — handoffs com status
offered, regra de ativação que exige accepted). Esta sessão fecha o ciclo:
o usuário aceita o handoff, o PO é ativado, consome o brief + as regras
e produz o backlog (épicos → histórias → tarefas) com DoD/DoR e vínculo
obrigatório a regras de negócio, mantendo rastreabilidade regra→stories.
Decisões
1. PO como GenServer conversacional com loop de tool use por turno
O Engine.Agents.PoServer espelha o CriativoServer (GenServer por sessão,
estado + rehydration + streaming pelo canal Phoenix), mas cada turno roda um
loop bounded de tool use: despacha create_epic/create_story/create_task e
injeta o resultado (id/erro) de volta como mensagem tool, re-chamando o
modelo — assim ele encadeia (id do épico → create_story → create_task). O
Criativo não faz isso (single-turn). Ativado pelo handoff aceito (não por
comando do usuário), o PO faz um :kickoff no start FRESCO (lê o brief + regras
do event log e gera o backlog); restart/reativação não regeram.
2. Backlog em tabelas próprias; arrays em JSONB; vínculo a regra por ULID
epics/stories/tasks (migração 0011). Como o projeto não usa arrays do
Postgres, RF/RNF/DoD/DoR/business_rule_ids são jsonb ($type<string[]>).
business_rule_ids referencia session_events.id (o artefato
artifact.business_rule) por texto ULID sem FK — mesma escolha de
handoffs.artifact_id (vínculo lógico, não relacional). Tarefas herdam o
vínculo da história (derivado, não armazenado).
3. Prontidão draft→ready validada no DOMÍNIO
domain/backlog/story-readiness.ts (assertReady → StoryNotReadyError) exige
DoD, DoR não vazios, ≥1 RF e ≥1 regra vinculada — "validação no domínio, não só
no prompt". story-state-machine.ts cobre draft→ready→in_progress→done. O
create_story cria em draft e promove pra ready na mesma operação quando
a regra é satisfeita (o PO usa só as 3 ferramentas nomeadas — não há tool de
transição); TransitionStoryUseCase expõe a transição explícita (testada) e
serve os estados futuros.
4. Ferramentas do PO passam pela api (nunca SQL/insert direto)
create_epic/create_story/create_task são tools :direct que chamam o
EngineApiClient → endpoints internos → use-cases com validação de domínio.
CreateStoryUseCase valida que cada business_rule_id referencia mesmo um
evento artifact.business_rule existente (recusa ids inventados; nada é
criado). Cada criação emite backlog.* no event log (narrativa do PO).
5. Rastreabilidade regra→stories (pendência = descoberta)
domain/backlog/coverage.ts (computeCoverage, puro) + GET /projects/:id/ coverage: para cada artifact.business_rule do projeto, quais histórias a
cobrem (via business_rule_ids). Regra sem cobertura é uma descoberta — a
UI a mostra em vermelho na tab Backlog. Nível de projeto (agrega as regras das
sessões via join em sessions).
6. Aceitar handoff ativa o PO pela regra da sessão 1
AcceptHandoffUseCase (ação do usuário): offered→accepted + handoff.accepted
→ chama ActivateAgentUseCase pro toAgent. A regra de ativação (que exige um
handoff accepted) agora passa exatamente por causa desta aceitação. UI: botão
"Aceitar handoff e iniciar PO" na sessão.
Consequências
- A tab Backlog é project-level (estado local, sem rota nova), lida do poll de
GET backlog+GET coverage— reflete o PO gerando em tempo real. - O composer da sessão roteia pro agente ativo (PO tem precedência sobre o
Criativo) via
sendAgentMessage. - Testes: domínio (
story-readiness,story-state-machine,coverage), use-case (create-storyrecusa regra inexistente;transition-storyrejeita draft→ready sem DoD), epo_server_test(kickoff encadeia create_epic→ create_story com injeção de resultado; regra inválida vira tool-result de erro sem derrubar o loop; broadcast; rehydration).
Escopo
Só o PO + backlog + rastreabilidade. O Arquiteto (ADRs em docs/adr/ do repo
do projeto, module_map, validar que story referencia módulo existente) é a
próxima sessão. in_progress/done e edição manual do backlog na UI ficam pra
depois (a máquina de estados já cobre).