Eventos
Três famílias de nomes convivem no Brabo, e é fácil confundi-las porque todas
usam assunto.verbo:
| família | onde mora | durabilidade |
|---|---|---|
| Evento de domínio | linha em session_events | permanente e imutável |
| Broadcast | canal Phoenix → navegador | efêmero, não persistido |
| Span | trace do OpenTelemetry | retenção do Tempo (24 h localmente) |
Só a primeira é contrato. As outras duas são transporte e observação.
Evento de domínio
Uma linha em session_events, append-only, com um seq denso por sessão
(RN-002). Colunas:
| coluna | o quê |
|---|---|
id | uuid |
session_id | a sessão dona |
seq | ordem dentro da sessão, começando em 1, sem buracos |
type | o identificador abaixo |
actor | quem causou: {kind, id} — usuário, agente ou sistema |
payload | jsonb, formato próprio de cada tipo |
created_at | — |
typeé umastringlivre no código — não há union do TypeScript nem enum do banco listando os tipos válidos. A lista abaixo foi extraída dos pontos de emissão, e por isso pode envelhecer sem quebrar nada. Veja a nota no fim desta página.
Sessão
| tipo | quando |
|---|---|
session.created | a sessão nasce em created |
session.activated | passou para active |
session.draining | o nó que a hospeda está drenando |
session.closed | encerramento normal |
session.closed_abnormally | encerramento com causa — node_shutdown é a mais comum |
session.reopened | uma sessão closed/closed_abnormally voltou para active (ADR 0183) |
Sessão terminal não recebe mais conversa (RN-581).
Quando a sessão está closed ou closed_abnormally, gravar um evento de
conversa é recusado com 409 e reason: "sessao_encerrada". Conversa é um
tipo que só existe como conversa (chat.message,
chat.message_cancelled, chat.structured_question, chat.structured_question_answered,
agent.status, agent.activated, handoff.offered, handoff.accepted,
readiness.confirmed, necessity.validated,
architecture.readiness_confirmed) ou qualquer evento cujo ator é um agente
conversacional — por isso tool.call/agent.response do Criativo são
recusados, enquanto os mesmos tipos vindos do Psicólogo, que roda sobre a
sessão fechada, continuam entrando. Decisões humanas sobre uma ação pendente
(action.approved/action.denied) também continuam entrando: a fila de ações
sobrevive à sessão. A sessão fechada porque a conversa ficou parada além do
teto leva termination_reason: "conversation_idle_timeout" e termina
closed, como heartbeat_timeout.
Sessão fechada pode ser reaberta (RN-649, ADR 0183).
POST /projects/:projectId/sessions/:sessionId/reopen (developer, ADR 0184) leva
uma sessão closed/closed_abnormally de volta a active e grava
session.reopened com {from, to, closedAt, terminationReason} — o único
registro do intervalo em que ela esteve fechada, já que closed_at e
termination_reason são limpos na linha. Nenhum evento anterior é editado, e
o kind da sessão não muda. Sessão que já tem execution.activated é
recusada com 409 sessao_com_execucao.
Chat e agentes
| tipo | quando |
|---|---|
chat.message | mensagem no fio da sessão, do usuário ou do agente |
chat.message_queued | a mensagem chegou com o agente no meio do turno e entrou na FILA dele em vez de ser recusada (RN-673, ADR 0191). Ator: o agente. Payload: mensagemId (o id do chat.message), texto, idioma e posicao. O texto viaja aqui porque é o que a fila relê depois de um reinício do engine. A mensagem enfileirada fica PENDENTE até um chat.message_delivered ou chat.message_cancelled nomeá-la; no máximo 10 por agente e sessão |
chat.message_delivered | começou o turno que lê a fila: as mensagens pendentes são lidas JUNTAS, em ordem, num turno só. Ator: o agente. Payload: mensagemIds |
chat.message_cancelled | quem ENVIOU uma mensagem pendente a cancelou — nenhum agente a leu. Ator: esse usuário. Payload: mensagemId, agente. Gravado pelo engine; recusado em sessão fechada (RN-581) |
chat.structured_question | o Criativo pediu VÁRIAS respostas de uma vez, por formulário — ferramenta ask_structured_questions (RN-162). Cada pergunta leva id, label, type, options e allowOther — este é a saída de texto livre do select, e vale true quando o modelo não declara nada (RN-171) |
chat.structured_question_answered | o usuário respondeu o formulário; as respostas também voltam como chat.message para o agente ler |
agent.activated | um agente assumiu trabalho na sessão |
agent.response | a resposta completa e consolidada do agente. modelName diz QUAL modelo a gerou, nos três produtores (os cinco agentes conversacionais, o ToolLoop de todo agente de execução/gate e o chat da api sem agente ativo) — null quando o turno falhou antes de resolver o binding, e ausente em eventos gravados antes de a regra existir (RN-175). Vindo de agente conversacional, content é só o texto de FECHO do turno e passos (ausente quando não há) lista, em ordem, o texto das rodadas anteriores, que o fio recolhe sob "Passos do turno" (RN-698, AT-395) |
agent.error | falha do agente, com origem (infra/modelo/codigo/politica) e a mensagem que ele diz no fio (RN-059). Também emitido com reason: turno_interrompido_por_reinicio (origem infra) quando o engine reiniciou no meio do turno e o status working órfão é fechado no boot ou na subida do agente, seguido de agent.status: idle (RN-586). Cobre o turno inteiro e também a falha de UMA ferramenta no meio do laço, com tool e retentativa no payload (RN-163) |
tool_router.decided | o passo em que o Jev escolheu a ferramenta (RN-625, ADR 0179): menuAntes/menuDepois, escolha, confianca, segunda, anterior, aplicado (o menu saiu menor que o catálogo), motivoDaQueda + origemDaQueda (infra/modelo/codigo) quando o Jev caiu, latenciaMs, custoMicros (o usage.cost REAL) e foraDoCardapio (ferramentas que o modelo chamou sem terem sido oferecidas). Gravado pela fachada do engine para todo agente; ausente quando o roteador não foi consultado (provider que não é o OpenRouter, menos de duas ferramentas, chave do workspace desligada). A medida do ganho (AT-239) é lida deste evento e de token_usage, nunca anotada |
tool.result | resultado da execução de uma ferramenta, gravado pelo hook Engine.Harness.Hooks.EventLog e, para os sete agentes conversacionais (o Infra Lead desde a RN-617), pelos servidores deles com tool, ok e resultado (ou erro), cortado em 2.000 caracteres com resultadoTotal quando corta (RN-589). O mesmo payload vem do Infra Lead para toda ferramenta que ele despacha em linha, e do Dev Lead quando uma chamada suspensa é resolvida — nunca enquanto ela espera aprovação (RN-593) |
handoff.offered | um agente ofereceu o trabalho a outro |
handoff.accepted | o destinatário aceitou. Em geral por uma pessoa (ator user). Quando o aceite estava implícito no clique "Estou pronto" (RN-658), o ator continua sendo essa pessoa e o payload — e o do agent.activated que segue — leva também implicito: { via: "readiness.confirmed", readinessEventId }. Desde a RN-660 (ADR 0186) a oferta do PO ao Arquiteto pode ser aceita pelo ator system handoff-auto-accept, e então o payload leva também automatico: true, emNomeDe (quem abriu a sessão — o repositório é provisionado em nome dessa pessoa) e criterio (regras, cobertas, repositorio) |
handoff.auto_accept_failed | o sistema decidiu aceitar a oferta do PO ao Arquiteto (RN-660) e o aceite falhou. Payload: handoffId, toAgent, origem (infra) e error. Ator system handoff-auto-accept. A oferta mantém o botão quando não foi marcada accepted |
handoff.superseded | um handoff offered deixou de ser o vigente e não pode mais ser aceito (RN-635, ADR 0182). Payload: handoffId, toAgent, motivo (agente_ativado — o destino foi ativado por qualquer caminho; nova_oferta — uma oferta mais nova ao mesmo destino no projeto a substituiu) e substitutaId (a oferta que a substituiu, null para agente_ativado). Gravado pelo ator system handoff-lifecycle na sessão da oferta ANTIGA, que pode estar fechada — é registro, não conversa, então a RN-581 o deixa entrar. O status da linha vira superseded; o evento é o histórico |
context.compacted | o gerenciador de contexto resumiu os turnos mais antigos do histórico de um agente para caber na janela. Desde a RN-580 o payload leva, além de tokensBefore/tokensAfter, o summary que substituiu esses turnos, o agent dono do histórico e messagesSummarized. Eventos gravados antes disso levam só as duas contagens — o resumo se perdeu, e a reidratação diz isso em vez de inventar um |
O que um agente conversacional relê (RN-580).
Quando um dos sete agentes conversacionais sobe sobre uma sessão que já tem
conversa, Engine.Agents.Reidratacao reconstrói o histórico dele a partir da
cauda deste log (os últimos 200 eventos): chat.message, agent.response,
chat.structured_question (como turno do próprio agente, com rótulos e opções)
e os PRÓPRIOS tool.call/tool.result (como nota de texto — os eventos não
levam id de chamada, então nunca são repetidos como role: tool).
chat.structured_question_answered é pulado de propósito: a api grava as
mesmas respostas como chat.message logo depois, e foi isso que o agente leu
ao vivo. Um chat.message cancelado na fila, ou ainda pendente para este
agente, também é pulado (RN-673): o primeiro
nunca foi dito, o segundo chega pelo turno que lê a fila. Desde a
RN-589 os sete gravam tool.result com o texto
que a ferramenta devolveu (resultado, cortado em 2.000 caracteres, com
resultadoTotal dando o tamanho real quando corta); uma chamada de sessão
gravada antes disso não tem tool.result, e a nota diz que o log não tem
desfecho. Quando a conversa é maior que a cauda, o histórico abre com uma
mensagem de sistema dizendo quantos eventos anteriores ficaram de fora, o
último resumo de compactação gravado e as mensagens de abertura.
Ações propostas
| tipo | quando |
|---|---|
proposed_action.created | a ação nasceu — antes de qualquer execução. payload.status diz como ela nasceu (pending, auto_approved ou denied), e é isso que distingue decisão humana de política (RN-049). payload.reason diz QUAL regra da política decidiu — a string que decide() devolve, nos três desfechos; ausente em eventos gravados antes de a regra existir, o que quer dizer "não gravado", nunca "sem motivo". A linha da outbox de mesmo nome NÃO o leva (RN-567). Nas ações terminal — o único tipo cuja decisão consulta o escopo de caminho — payload.scopeRoot diz contra QUAL raiz o escopo comparou, como { executionMode, ancora, segmento }: container → ancora: raiz_gerenciada e o workspace_dir_name; mounted → ancora: base_de_projetos e o segmento sob BRABO_PROJECTS_BASE (ancora: indisponivel, segmento: null quando a pasta está fora da base ou não há base); runner → ancora: nome_da_pasta e o workspace_dir_name. NUNCA o caminho absoluto, que exporia o $HOME do usuário a todo membro. Ausente em eventos antigos e nos outros tipos de ação; também não está na outbox (RN-609) |
proposed_action.approved | decidida pelo usuário — actor é quem clicou. A auto-aprovação NÃO passa por aqui: ela aparece em created com status: auto_approved e ator agente |
proposed_action.denied | negada — estado terminal. actor é quem recusou, e payload.reason é o motivo |
proposed_action.executed | executada com sucesso |
action.failed | executada e falhou |
permission.granted | concessão gravada na política |
Backlog
| tipo | quando |
|---|---|
backlog.epic_created | — |
backlog.story_created | — |
backlog.story_transitioned | mudança de estado da história. actor diz quem a promoveu: agent/po no modo auto, user no modo manual (RN-048) |
backlog.story_completed | o PO completou uma história draft existente com complete_story — regras vinculadas, RF/DoD/DoR preenchidos — em vez de recriá-la (RN-720) |
backlog.story_promotion_proposed | o PO terminou uma história COMPLETA num projeto em modo manual: ela fica draft aguardando a decisão do usuário, e nenhuma tarefa dela é pegável até lá (RN-048) |
backlog.story_promotion_returned | o usuário RECUSOU a promoção e devolveu a história ao PO com um motivo — que vira mensagem fixada na sessão dele, igual a um gate devolvido a um dev (RN-048) |
backlog.story_updated | o título e/ou a descrição de uma história draft sem tarefa em execução foi corrigido, pelo usuário (aba Backlog) ou pelo PO (update_story). Leva previousTitle, title e descriptionChanged (RN-727) |
backlog.story_archived | uma história draft sem tarefa em execução foi ARQUIVADA, pelo usuário ou pelo PO (archive_story): ela e as tarefas dela saem do backlog, da cobertura, do plano e do claim; a linha fica. Leva reason e taskIds (RN-727, ADR 0212) |
backlog.story_demoted | um módulo sumiu do module_map; a história voltou para draft (RN-012) |
backlog.story_overlap_warned | a história foi criada, mas TODAS as regras que ela cita já estavam cobertas por outra — aviso, não bloqueio: quem julga se é sobreposição é o usuário (RN-081) |
backlog.semantic_duplicate_warned | foi gravada uma história ou regra de negócio cujo TÍTULO está, por embedding, no limiar ou acima dele em relação a uma existente do mesmo tipo no projeto — aviso, nunca recusa; o payload nomeia o item parecido e o cosseno. Ator system/duplicata-semantica (RN-681) |
backlog.semantic_duplicate_check_skipped | a checagem de duplicata semântica NÃO pôde rodar — sem provider de embedding, daemon fora do ar, o teto de 10 s — e o reason diz qual; o item foi gravado assim mesmo (RN-681) |
backlog.story_modules_assigned | vínculo história ↔ módulo |
backlog.task_created | — |
backlog.task_claimed | um dev agent pegou a tarefa |
backlog.task_status_changed | — |
backlog.task_merge_conflict | o merge aprovado da PR da tarefa foi recusado por conflito de merge: taskId, pullRequestId, conflictingFiles, actionId. Desde a RN-715 a tarefa volta para in_progress e o dev agent dono é acordado pela linha task.merge_conflict da outbox (RN-705) |
backlog.task_blocked | teto de tentativas de correção esgotado, ou um impedimento foi registrado |
backlog.task_unblocked | — |
backlog.epic_without_story | o PO terminou um turno tendo criado um épico e NENHUMA história para ele. Épico não gera tarefa — história gera —, então este é o estado que trava a execução sem erro visível. Desfecho explícito no padrão da RN-059: evento durável com origem, epicIds/epicTitles e a mensagem, mais o broadcast agent.error. Reportado UMA vez por ocorrência, nunca em laço (RN-165) |
backlog.handoff_not_offered | o PO terminou um turno com toda regra de negócio coberta mas não conseguiu oferecer o handoff ao Arquiteto: alguma história está sem tarefa (origem: modelo, com storyIds/storyTitles), ou ler o backlog ou criar a oferta falhou (origem: infra). Com toda história com tarefa, o próprio servidor do PO oferece o handoff (RN-700) |
Execução e gates
| tipo | quando |
|---|---|
execution.plan_proposed | DESCONTINUADO desde o ADR 0086 (RN-284) — sessões antigas ainda podem ter este tipo no log; sessões novas usam uma proposed_action do tipo propose_execution_plan (ver docs/reference/permissions.md), porque o plano do Dev Lead virou decisão de verdade, aprovada ou recusada em Aprovações, e não mais um evento simples |
execution.activated | a fase de execução começou. Só entra em sessão criativa — numa consultiva a gravação responde 409 (RN-097). Continua sendo este evento, e não a coluna sessions.kind, que diz se uma sessão ESTÁ executando |
execution.parallelization_suggested | o sistema propôs paralelizar — emitido na ativação só quando o projeto tem um container running registrado (senão os dev agents ficam bloqueados, dev.blocked_by_container, e mais agentes só multiplicariam os bloqueados; AT-104) |
execution.plan_applied | o humano APROVOU o propose_execution_plan do Dev Lead (ou ele foi auto-aprovado) e essa aprovação ATIVOU a execução: cada tarefa do plano ganhou o módulo dela e ActivateExecutionUseCase rodou (RN-677, RN-678). Payload: actionId, sessaoDeExecucao, modulos, tarefasAtribuidas e — quando o plano leva tarefas — tarefasPorModulo e modulosSemTarefa (módulos que ficaram sem agente, RN-709). Gravado na sessão do Dev Lead, ator system/action-executor |
execution.plan_failed | o plano aprovado NÃO ativou a execução — o plano não bate mais com o module_map vigente, ou a ativação recusou (por exemplo, 409 sem repositório). Payload: os mesmos campos mais motivo, a frase da própria api. A decisão humana fica registrada; nada foi gravado nas tarefas quando o próprio plano foi recusado |
execution.parallelization_accepted | aceita — o subagente herda o teto do agente base |
dev.started | o dev agent começou o ciclo (ativação, paralelização — NÃO reidratação, que nunca redispara) |
dev.working | pegou uma tarefa e montou o worktree |
dev.idle | a fila do módulo está vazia — nenhuma tarefa pegável agora |
dev.blocked_by_container | o agente NÃO pegou tarefa, porque o projeto não tem container REGISTRADO running (RN-502). Ele para em :idle como os dois acima — idle é o único estado que um wake ainda resgata — e este evento é a única coisa que distingue "sem container" de "fila vazia" (dev.idle) e de "o claim falhou" (dev.error). A api nunca é chamada: a guarda roda ANTES de claim_task/1, então nenhuma tarefa fica pega sem dono vivo. Resolve-se sozinho: container.running na outbox acorda todo agente :idle do projeto |
dev.awaiting_approval | as ações de git ficaram pendentes de aprovação (Fase 12e): o gate NÃO abre — sem PR não há o que julgar — e o worktree fica preso até task.pr_settled (RN-050) |
dev.awaiting_gate | PR aberta, esperando o gate (Fase 12b) — task_id/worktree continuam presos |
dev.blocked | a tarefa foi devolvida com diagnóstico (limite de iterações, orçamento estourado, report_blocked, falha de worktree/contexto). Desde a RN-743 leva trabalhoPreservado ({branch, commit} ou null): o trabalho não commitado foi commitado na branch da tarefa como <agente>[bot], e a próxima tarefa do agente parte dessa branch |
dev.idle_tripped | o disjuntor desarmou (RN-047) — N tarefas bloqueadas seguidas param o agente |
dev.credit_exhausted | o provider do modelo recusou por falta de crédito (RN-726) — o agente pausa em idle_tripped sem contar para o disjuntor nem pegar a próxima tarefa; Rearmar retoma |
dev.rearmed | o usuário rearmou um agente travado (Fase 12b) — actor é o USUÁRIO que clicou, não o agente |
pr.gate_changed | a PR passou para um novo gate (awaiting_qa → awaiting_secops → awaiting_user) |
infra.gate_changed | o gate da Infra |
gate.rescue_parked | o GateRescuer ESTACIONOU um ciclo de QA/SecOps parado há mais de 2 h em vez de reiniciá-lo (RN-722, ADR 0207); o fio mostra um botão "Retomar gate" (RN-724). Payload: taskId, gate, idadeSegundos, motivo. Ator system/gate-rescuer |
gate.interrupted | no BOOT do engine, um ciclo de gate in_progress recente não tinha processo vivo em nenhum nó do cluster: o reinício o matou, e o resgatador o reinicia na hora em vez de esperar o limiar de 15 minutos (RN-823). Payload: taskId, gate, ultimaAtividadeEm, retomadoEm. Ator system/gate-rescuer; o fio o mostra como aviso. Não é projetado no grafo |
agent.plan | o agente registrou o plano do turno ou da tarefa com registrar_plano (RN-918): objetivo e passos (texto, estado pendente/em_curso/feito/descartado). Cada evento substitui o anterior; o último é o vigente e alimenta o diário e o cartão do fio |
agent.turn_resumed | o turno interrompido pelo reinício foi retomado (RN-920, RN-921). Payload: retomaDe, ultimaAtividadeEm, automatica (true no boot, false no botão "Retomar"). É o marco do teto de 1 retomada automática; o subagente de gate o grava ao ser retomado |
infra.correction_queued | uma correção de gate chegou ao Infra Lead e ficou pendente, durável (RN-924). Payload: id, gate, reason, diagnosis |
infra.correction_delivered | a correção id foi entregue ao Infra Lead; a que tem queued sem delivered é reentregue no boot |
infra.artifact_blocked | o artefato da Infra foi rejeitado |
Delegação (Fase 8b, ADR 0038)
| tipo | quando |
|---|---|
delegation.completed | a subespecialidade terminou; payload.parecerArtifactId referencia o artifact.qa_verdict INTERNO dela — nunca o que chega ao gate |
delegation.failed | a subespecialidade não terminou; payload.failureOrigin é a ORIGEM (infra|modelo|codigo|politica, nunca por eliminação — ADR 0020) |
delegation.dispensed | o lead decidiu NÃO delegar; payload.justification explica por quê — dispensar nunca é calado |
Os três são emitidos pelo QaLeadServer, um por subespecialidade, SEPARADOS da
chamada a record_gate_verdict — o evento pr.gate_changed acima continua
sendo o único que descreve o gate em si; estes descrevem a área por trás dele.
Laço de ferramentas
| tipo | quando |
|---|---|
toolloop.limit_reached | o laço bateu no teto de iterações. payload leva iteration e max_iterations — o teto é por TIPO de agente (RN-085) |
toolloop.budget_exceeded | o orçamento de tokens daquele laço acabou antes do trabalho; payload leva tokens_spent_micros e token_budget_micros |
Emitidos pelo Engine.Harness.ToolLoop e — desde a
RN-166 — também pelo PoServer, que
tem laço PRÓPRIO e, até então, esgotava o teto calado. Mesmo tipo e mesmo
payload de propósito: é o mesmo fato, e quem lê o log não deveria aprender um
segundo nome só porque o agente conversacional não usa o ToolLoop.
Artefatos
| tipo | schema |
|---|---|
artifact.product_brief | title, summary, rules |
artifact.business_rule | title, description, origin |
artifact.decision_record | context, options, choice, consequences — um "ADR resumido" que qualquer um dos seis agentes conversacionais pode emitir; reusa o padrão genérico em vez do dedicado, e convive com open_adr_pr (só o Arquiteto, documento commitado de verdade) (RN-505) |
artifact.module_map | o mapa de módulos do Arquiteto |
artifact.module_routing | a imagem candidata do Arquiteto por módulo, um item por módulo do module_map vigente — o Arquiteto CANDIDATA, a Infra ELEGE (RN-487, ADR 0131) |
artifact.module_contracts | o contrato entre módulos do Arquiteto: por módulo, o que ele EXPÕE (modulo, expoe: [{tipo, assinatura, descricao}]), com version — a lista inteira a cada vez, a vigente substitui a anterior; o que um módulo consome é o dependsOn do module_map (RN-684, ADR 0200) |
artifact.project_image | image, rationale, network, resources, version (quem decidiu é o ator: arquiteto ou infra-lead) — a imagem de container do projeto, emitida pela api (DecidirImagemDoProjetoUseCase) quando o Arquiteto a decide ou o Infra Lead elege uma candidata (RN-491); versionada, sem tabela. O engine só LÊ se ela existe, para recusar localmente container_start_via_runner sem ela (RN-610) |
artifact.insight | — |
artifact.prototipo_navegavel | personas, jornadas, prototipo (telas, anotacoes), resumo — o protótipo do UX Designer (RN-286, ADR 0087) |
artifact.rfc_staff | — (validado em Engine.Agents.StaffTools, não pelo ArtifactSchemas — mesmo caso de artifact.insight): problema, opcoes (lista de descricao/tradeoffs), recomendacao, poc (escopo, descartavel: true fixo). A RFC do Staff (ADR 0088), devolvida ao Arquiteto por handoff na mesma chamada de ferramenta |
artifact.plano_de_teste | storyId, taskId, planoDeTeste, criteriosExecutaveis, estrategiaDeAutomacao — a entrega de qa-estratégia (ADR 0090), escrita DEPOIS da entrega do dev e consumida pelo ciclo qa-verificada desde o ADR 0192 (RN-674) |
artifact.threat_model | storyId, threatModel, requisitosDeSeguranca — o "segundo momento" do SecOps sobre o DESENHO de uma história (RN-360, ADR 0090) |
Os schemas são fechados: campo faltando recusa a emissão
(Engine.Harness.ArtifactSchemas).
Arquitetura e prontidão
| tipo | quando |
|---|---|
architecture.readiness_confirmed | — |
readiness.confirmed | o usuário clicou "Estou pronto — a necessidade está validada" com o Criativo. Desde o ADR 0185 o payload leva necessidadeValidada: true e aceiteImplicitoDoPo: true, a marca que o aceite implícito do PO lê (RN-658); eventos mais antigos levam {} |
necessity.validated | gate necessidade-validada (Criativo → PO): uma pessoa — nunca uma inferência do modelo — declara que a necessidade de negócio que o Criativo consolidou está certa. Desde o ADR 0185 é gravado pelo MESMO clique de readiness.confirmed, assim que o engine aceita o turno, com productBriefId: null (o brief vem desse turno), via: "readiness.confirmed" e readinessEventId (RN-657). Eventos do clique separado do ADR 0095 (RN-406) levam productBriefId apontando para o artifact.product_brief validado |
Git e bootstrap
| tipo | quando |
|---|---|
project.git_connected | credencial de git vinculada ao projeto |
project.repository_provisioned | repositório criado pelo Brabo |
project.repository_adopted | um repositório que já existia passou a ser do projeto (RN-046) |
bootstrap.repository_adopted | o mesmo fato na sessão dedicada, com a defaultBranch observada no provider |
bootstrap.step_started | um dos seis passos do Gitflow começou |
bootstrap.step_completed | concluído |
bootstrap.step_skipped | já estava feito — isso é sucesso, não erro (RN-029) |
bootstrap.step_degraded | concluído sem uma das capacidades do provider (por exemplo, proteção de branch no Local) |
bootstrap.step_failed | falhou; o bootstrap é retomável a partir deste ponto |
bootstrap.plan_approved | o usuário aprovou o plano de adoção — só a partir daqui o bootstrap roda num repositório adotado (RN-045) |
bootstrap.adopted_as_is | o usuário dispensou o bootstrap; nenhum passo rodou, e o plano fica como prova do que não foi aplicado |
Custo
| tipo | quando |
|---|---|
budget.threshold_crossed | 70%, 90% ou 100% — uma vez por limiar (RN-018) |
Telemetria do RAG (RN-479/480/481)
Estes dois são narração, nunca a fonte da medição, e só existem quando há
sessão — isto é, no caminho do agente. Uma busca vinda da aba RAG é busca de
projeto e não tem sessão nenhuma, e session_events.session_id é
NOT NULL, então medir pelo event log perderia em silêncio justamente as
buscas que um humano fez olhando os scores. A fonte da medição é o par de
tabelas rag_searches/rag_feedback (lido por pnpm --filter api medir:rag);
o evento só põe o mesmo fato na linha do tempo da sessão.
| tipo | quando |
|---|---|
rag.search | um agente fez uma busca híbrida dentro de uma sessão — o payload leva o searchId da linha de telemetria, a consulta, topK, quantos resultados passaram do limiar, se a busca foi degraded e os ids dos chunks (RN-479, RN-481) |
rag.feedback | um agente julgou um chunk recuperado como util/irrelevante — o payload leva searchId, chunkId, o veredito e a posição que aquele chunk ocupava naquela busca (RN-480) |
Runner local (ADR 0103/0104)
| tipo | quando |
|---|---|
project.workspace_verified | o brabo-runner conectou e confirmou o caminho de um projeto com execution_mode: runner — o runner é a fonte de verdade do caminho, sobrescrevendo o que foi digitado na criação (RN-423) |
Psicólogo
| tipo | quando |
|---|---|
psychologist.analysis_completed | — |
psychologist.analysis_failed | com causa classificada: infra, modelo, código ou política |
psychologist.analysis_skipped | a sessão não tinha evento ANALISÁVEL e a análise não rodou — payload.analisaveis e payload.eventCount mostram a diferença (RN-079) |
psychologist.hypothesis_proposed | uma hipótese com evidenceEventIds válidos (RN-021) |
psychologist.hypothesis_accepted | — |
psychologist.hypothesis_dismissed | — |
psychologist.hypothesis_accepted_for_anamnese | aceita e encaminhada — o elo que fecha o laço com a Anamnese |
Anamnese e instruções
| tipo | quando |
|---|---|
anamnese.run_completed | — |
anamnese.run_failed | — |
anamnese.run_skipped | a rodada terminou SEM perfil, de propósito — payload.motivo diz por quê (RN-063) |
anamnese.analysis | o texto que o modelo da Anamnese escreveu durante a rodada (payload.content); nunca desenhado no fio da sessão (RN-695) |
anamnese.profile_updated | o proficiency_profile mudou |
anamnese.agent_deviation | um desvio do AGENTE visto pela Anamnese (laço, cancelado pelo usuário, reparado pelo usuário) — sinal do produto, nunca traço do usuário; o payload é só agente, tipo e evidenceEventIds, sem dado pessoal, não projetado no grafo (RN-717, ADR 0205) |
instruction.rolled_back | rollback de uma versão de instrução — cria uma versão nova, não apaga (RN-027) |
Broadcast
Mensagens do canal Phoenix para o navegador. Elas não são persistidas: quem recarrega a página não as recebe de volta — recebe o event log de volta.
| broadcast | o quê |
|---|---|
agent.delta | pedaço de texto do streaming do modelo |
agent.status | mudança visível de estado do agente |
agent.done | o turno terminou |
agent.error | o turno falhou |
event.appended | um evento novo entrou no log — o gatilho de atualização do painel; leva só type e actorId |
Um evento de domínio quase sempre produz um event.appended; o contrário não
vale. Desde a RN-579 o engine o emite para
toda escrita que a api confirmou pela fachada EngineApiClient (gravações,
ações propostas, handoffs, o backlog do PO), e nunca para uma recusada. As
escritas que a api faz por conta própria — um humano decidindo uma ação, uma
transição de sessão — chegam ao mesmo broadcast desde a AT-157: a api o pede ao
engine depois do commit da escrita (POST /internal/sessions/:id/event-appended),
e não o pede para escritas vindas do engine, que já as anuncia. O navegador o
usa só como gatilho: invalida o que o type afeta (eventos e orçamento sempre;
ações, handoffs ou backlog pelo prefixo), com uma janela mínima por alvo, e com
o canal vivo os polls da tela de sessão caem para 15s (30s para o orçamento).
Quem pode ouvir (RN-108)
Entrar no canal session:<id> — e portanto receber qualquer um destes
broadcasts — exige um ticket opaco de uso único emitido por
POST /projects/:projectId/sessions/:sessionId/socket-ticket (TTL de 30s).
Antes da RN-108, o connect/3 do socket Phoenix não checava nada além de o
session_id existir: quem descobrisse o UUID podia entrar e ouvir tudo. O
ticket não é persistido nem lido no event log — mora em
session_socket_tickets, verificado e consumido pelo próprio engine —, por
isso não aparece no inventário de eventos de domínio abaixo. Ver
RN-108.
Span
Nomes de span do OpenTelemetry. Uma sessão é um trace raiz que atravessa a api e o engine (ADR 0026).
Nomes de domínio
Nomeados à mão, um por operação que importa. São os que se procuram no Tempo:
| span | onde |
|---|---|
session.create | api — a raiz |
agent.turn | engine — uma passada do ToolLoop |
llm.turn | engine — a chamada ao modelo |
tool.call | engine — a execução de uma ferramenta |
gate.scanner | engine — um scanner do SecOps, com o nome do scanner num atributo |
outbox.session_lifecycle | engine — job do Oban, preso ao trace da sessão |
outbox.psychologist | engine — idem, a análise do Psicólogo |
Nomes derivados do código
O decorator @Traced da api
(ADR 0035) nomeia
o span como Classe.metodo — por exemplo TransitionSessionUseCase.execute,
DrizzleOutboxRepository.append. Eles não cabem numa lista aqui: nascem e
morrem com o código, e a lista envelheceria na primeira renomeação.
A distinção importa na consulta: um nome de domínio é estável e pode ir para uma consulta salva ou um dashboard; um nome derivado do código não pode.
Os atributos que esses spans levam — brabo.layer, code.namespace,
code.function — servem para buscar no Tempo e não são contrato, ao
contrário do trace_id. Podem ser renomeados sem quebrar nada fora do Tempo.
Como navegar de uma sessão até um tool.call está documentado no
runbook; o modelo inteiro está documentado em
observabilidade.
Como o evento chega ao engine
Não por uma fila externa. A api grava o evento e a intenção de publicá-lo na mesma transação (transactional outbox); o Oban consome direto do Postgres. É isso que impede "o evento foi gravado, mas o engine nunca soube".
Inventário completo
As seções acima dizem quando cada evento acontece. Esta é a varredura
mecânica dos pontos de emissão, refeita a cada pnpm docs:generate — é ela que
faz um identificador novo aparecer aqui mesmo que ninguém tenha escrito sobre
ele.
⚠️ Bloco gerado por
pnpm docs:generate. Não edite à mão — o próximo build o sobrescreve.
Extraídos dos pontos de emissão: 115 identificadores, dos quais 3 não estão descritos acima.
action.failed(apps/api/src/application/use-cases/actions/execute-git-action.use-case.ts)agent.activated(apps/api/src/application/use-cases/agents/activate-agent.use-case.ts)agent.delta(apps/engine/lib/engine/agents/arquiteto_server.ex)agent.done(apps/engine/lib/engine/agents/turno_assincrono.ex)agent.error(apps/api/src/application/use-cases/agents/aceite-implicito-do-po.use-case.ts)agent.plan(apps/engine/lib/engine/agents/diario.ex)agent.response(apps/api/src/application/use-cases/llm/send-chat-message.use-case.ts)agent.status(apps/engine/lib/engine/agents/diario.ex)agent.turn(apps/engine/lib/engine/harness/tool_loop.ex)agent.turn_resumed(apps/engine/lib/engine/agents/diario.ex)anamnese.agent_deviation(apps/engine/lib/engine/anamnese/tools/report_agent_deviation.ex)anamnese.analysis(apps/engine/lib/engine/workers/anamnese_worker.ex)anamnese.profile_updated(apps/api/src/application/use-cases/anamnese/record-proficiency.use-case.ts)anamnese.run_completed(apps/api/src/application/use-cases/anamnese/record-proficiency.use-case.ts)anamnese.run_failed(apps/engine/lib/engine/session_events/event.ex)anamnese.run_skipped(apps/engine/lib/engine/session_events/event.ex)architecture.readiness_confirmed(apps/api/src/application/use-cases/agents/offer-infra-handoff.use-case.ts)artifact.business_rule(apps/engine/lib/engine/agents/arquiteto_server.ex)artifact.decision_record(apps/api/src/application/use-cases/backlog/perguntas-em-aberto.use-case.ts)artifact.insight(apps/engine/lib/engine/harness/tools/emit_insight.ex)artifact.module_map(apps/api/src/application/use-cases/architecture/create-module-map.use-case.ts)artifact.open_question— ⚠️ não descrito acima (apps/engine/lib/engine/harness/decisoes_do_projeto.ex)artifact.plano_de_teste(apps/engine/lib/engine/gates/qa_lead_server.ex)artifact.product_brief(apps/engine/lib/engine/agents/arquiteto_server.ex)artifact.project_image(apps/engine/lib/engine/harness/ambiente_do_agente.ex)artifact.prototipo_navegavel(apps/engine/lib/engine/agents/ux_designer_tools.ex)artifact.qa_verdict(apps/engine/lib/engine/gates/qa_lead_server.ex)artifact.rfc_staff(apps/engine/lib/engine/agents/staff_tools.ex)artifact.secops_verdict— ⚠️ não descrito acima (apps/engine/lib/engine/gates/secops_agent_server.ex)artifact.threat_model(apps/engine/lib/engine/agents/dev_lead_tools.ex)backlog.epic_created(apps/api/src/application/use-cases/backlog/create-epic.use-case.ts)backlog.epic_without_story(apps/engine/lib/engine/agents/po_server.ex)backlog.handoff_not_offered(apps/engine/lib/engine/agents/po_server.ex)backlog.semantic_duplicate_check_skipped(apps/api/src/application/use-cases/backlog/verificar-duplicata-semantica.use-case.ts)backlog.semantic_duplicate_warned(apps/api/src/application/use-cases/backlog/verificar-duplicata-semantica.use-case.ts)backlog.story_archived(apps/api/src/application/use-cases/backlog/corrigir-historia.use-case.ts)backlog.story_completed(apps/api/src/application/use-cases/backlog/complete-story.use-case.ts)backlog.story_created(apps/api/src/application/use-cases/backlog/create-story.use-case.ts)backlog.story_demoted(apps/api/src/application/use-cases/architecture/create-module-map.use-case.ts)backlog.story_modules_assigned(apps/api/src/application/use-cases/architecture/assign-story-modules.use-case.ts)backlog.story_overlap_warned(apps/api/src/application/use-cases/backlog/create-story.use-case.ts)backlog.story_promotion_proposed(apps/api/src/application/use-cases/backlog/complete-story.use-case.ts)backlog.story_promotion_returned(apps/api/src/application/use-cases/backlog/return-story.use-case.ts)backlog.story_transitioned(apps/api/src/application/use-cases/backlog/transition-story.use-case.ts)backlog.story_updated(apps/api/src/application/use-cases/backlog/corrigir-historia.use-case.ts)backlog.task_blocked(apps/api/src/application/use-cases/execution/mark-task-blocked.use-case.ts)backlog.task_claimed(apps/api/src/application/use-cases/execution/claim-next-task.use-case.ts)backlog.task_created(apps/api/src/application/use-cases/backlog/create-task.use-case.ts)backlog.task_merge_conflict(apps/api/src/application/use-cases/actions/execute-git-action.use-case.ts)backlog.task_status_changed(apps/api/src/application/use-cases/actions/execute-git-action.use-case.ts)backlog.task_unblocked(apps/api/src/application/use-cases/execution/unblock-task.use-case.ts)bootstrap.adopted_as_is(apps/api/src/application/use-cases/git/decide-bootstrap-plan.use-case.ts)bootstrap.plan_approved(apps/api/src/application/use-cases/git/decide-bootstrap-plan.use-case.ts)bootstrap.repository_adopted(apps/api/src/application/use-cases/git/adopt-repository.use-case.ts)bootstrap.step_acknowledged— ⚠️ não descrito acima (apps/api/src/application/use-cases/git/acknowledge-protection-failure.use-case.ts)bootstrap.step_completed(apps/api/src/application/use-cases/git/bootstrap-runner.ts)bootstrap.step_degraded(apps/api/src/application/use-cases/git/bootstrap-runner.ts)bootstrap.step_failed(apps/api/src/application/use-cases/git/bootstrap-runner.ts)bootstrap.step_skipped(apps/api/src/application/use-cases/git/bootstrap-runner.ts)bootstrap.step_started(apps/api/src/application/use-cases/git/bootstrap-runner.ts)budget.threshold_crossed(apps/api/src/application/use-cases/llm/record-llm-usage.use-case.ts)chat.message(apps/api/src/application/use-cases/agents/send-agent-message.use-case.ts)chat.message_cancelled(apps/engine/lib/engine/agents/fila_de_mensagens.ex)chat.message_delivered(apps/engine/lib/engine/agents/fila_de_mensagens.ex)chat.message_queued(apps/engine/lib/engine/agents/fila_de_mensagens.ex)chat.structured_question(apps/engine/lib/engine/agents/reidratacao.ex)chat.structured_question_answered(apps/api/src/application/use-cases/agents/answer-structured-question.use-case.ts)delegation.completed(apps/api/src/application/use-cases/execution/record-delegation.use-case.ts)delegation.dispensed(apps/api/src/application/use-cases/execution/record-delegation.use-case.ts)delegation.failed(apps/api/src/application/use-cases/execution/record-delegation.use-case.ts)event.appended(apps/engine/lib/engine/sessions/live_broadcast.ex)execution.activated(apps/api/src/application/use-cases/execution/activate-execution.use-case.ts)execution.parallelization_accepted(apps/api/src/application/use-cases/execution/accept-parallelization.use-case.ts)execution.parallelization_suggested(apps/api/src/application/use-cases/execution/activate-execution.use-case.ts)gate.interrupted(apps/engine/lib/engine/gates/gate_rescuer.ex)gate.rescue_parked(apps/engine/lib/engine/gates/gate_rescuer.ex)gate.scanner(apps/engine/lib/engine/gates/scanner.ex)handoff.accepted(apps/api/src/application/use-cases/agents/accept-handoff.use-case.ts)handoff.auto_accept_failed(apps/api/src/application/use-cases/agents/aceitar-handoff-automaticamente.use-case.ts)handoff.offered(apps/api/src/application/use-cases/agents/create-handoff.use-case.ts)handoff.superseded(apps/api/src/application/use-cases/agents/ciclo-de-vida-do-handoff.service.ts)infra.artifact_blocked(apps/api/src/application/use-cases/execution/mark-infra-artifact-blocked.use-case.ts)infra.correction_delivered(apps/engine/lib/engine/infra/infra_lead_server.ex)infra.correction_queued(apps/engine/lib/engine/infra/infra_lead_server.ex)infra.gate_changed(apps/api/src/application/use-cases/execution/record-infra-gate-verdict.use-case.ts)instruction.rolled_back(apps/api/src/application/use-cases/instructions/rollback-instruction.use-case.ts)llm.turn(apps/engine/lib/engine/sessions/engine_api_client.ex)necessity.validated(apps/api/src/application/use-cases/agents/confirm-readiness.use-case.ts)permission.granted(apps/api/src/application/use-cases/actions/approve-always-action.use-case.ts)pr.gate_changed(apps/api/src/application/use-cases/execution/open-gate.use-case.ts)project.git_connected(apps/api/src/application/use-cases/git/handle-git-oauth-callback.use-case.ts)project.repository_adopted(apps/api/src/application/use-cases/git/adopt-repository.use-case.ts)project.repository_provisioned(apps/api/src/application/use-cases/git/provision-repository.use-case.ts)project.workspace_verified(apps/api/src/application/use-cases/iam/confirm-project-workspace.use-case.ts)proposed_action.approved(apps/api/src/application/use-cases/actions/approve-action.use-case.ts)proposed_action.created(apps/api/src/application/use-cases/actions/propose-action.use-case.ts)proposed_action.denied(apps/api/src/application/use-cases/actions/deny-action.use-case.ts)proposed_action.executed(apps/api/src/application/use-cases/actions/execute-git-action.use-case.ts)psychologist.analysis_completed(apps/api/src/application/use-cases/execution/propose-hypotheses.use-case.ts)psychologist.analysis_failed(apps/engine/lib/engine/workers/psychologist_worker.ex)psychologist.analysis_skipped(apps/engine/lib/engine/workers/psychologist_worker.ex)psychologist.hypothesis_accepted(apps/api/src/application/use-cases/execution/accept-hypothesis.use-case.ts)psychologist.hypothesis_accepted_for_anamnese(apps/api/src/application/use-cases/execution/accept-hypothesis.use-case.ts)psychologist.hypothesis_dismissed(apps/api/src/application/use-cases/execution/dismiss-hypothesis.use-case.ts)psychologist.hypothesis_proposed(apps/api/src/application/use-cases/execution/propose-hypotheses.use-case.ts)rag.feedback(apps/api/src/application/use-cases/rag/record-rag-feedback.use-case.ts)rag.search(apps/api/src/application/use-cases/rag/hybrid-search.use-case.ts)readiness.confirmed(apps/api/src/application/use-cases/agents/confirm-readiness.use-case.ts)session.activated(apps/api/src/db/seed.ts)session.closed(apps/engine/lib/engine/outbox/drain.ex)session.closed_abnormally(apps/engine/lib/engine/outbox/drain.ex)session.created(apps/api/src/application/use-cases/sessions/create-session.use-case.ts)session.draining(apps/engine/lib/engine/shutdown.ex)tool.call(apps/engine/lib/engine/agents/arquiteto_server.ex)tool.result(apps/engine/lib/engine/agents/arquiteto_server.ex)
TODO(humano):
typeé umastringlivre emapplication/ports/session-event-repository.port.ts— nada impede um erro de digitação de criar um tipo novo em silêncio, e nada garante que esta página cubra todos. Uma union do TypeScript exportada depackages/sharedresolveria os dois problemas e deixaria esta lista ser gerada em vez de mantida à mão. Enquanto ela não existe, a lista acima é uma extração datada dos pontos de emissão.