RCA (causa raiz)
Quando um alerta dispara, o Ongrid dá spawn em um worker LLM que guia o agent ReAct de graph-kernel na persona incident-investigator, chama tools para coletar evidências, e escreve um report estruturado de volta em investigation_reports.
O report renderiza na página /alerts/incidents/:id do SPA ao lado da série disparando — o SRE humano nunca precisa começar "do prompt em branco".
HLD-013
O pipeline atual entrega Phase 1+2 do modelo causal do HLD-013. A abordagem ingênua "resumir o que disparou" (PR-2) foi substituída quando ficou claro que os operadores queriam o paciente zero — o processo / container / linha específico que iniciou a cascata — não um resumo do texto do alarme.
Ciclo de vida
incident.fire (isNew=true)
└─ alert.Usecase.RecordFiring
└─ Investigator.InvestigateAsync(incident)
└─ Enqueue → Gate 1-3 (severity, inflight, semaphore)
└─ repo.Create(pending row) ← UI shows "investigating…"
└─ go run(reportID, incident, dedupKey, locale)
├─ spawner.SpawnWorker(incident-investigator persona)
├─ worker drives the ReAct loop with tools
├─ Pass-2 structured extraction (cheap model)
└─ repo.MarkReady(report fields) ← UI shows readyInvestigateAsync é a costura pública que alert.Usecase chama — veja usecase.go:301.
Gates
Três gates filtram antes mesmo de um worker ser spawned. Cada rejeição é persistida como uma linha status=skipped para que o SPA mostre ao operador uma razão em vez de "nunca iniciado".
| Gate | Padrão | Comportamento em miss |
|---|---|---|
Severity floor (Config.MinSeverity) | warning | Skip silencioso, nenhuma linha escrita |
In-process inflight (por incident_id) | sempre on | Coalesce silencioso |
Cap de concorrência (Config.MaxConcurrent) | 5 | Linha skipped: concurrency limit reached (N workers in flight) |
O cap de concorrência defende rate-limits do provider LLM e limita RAM quando 100 incidentes disparam ao mesmo tempo. Callers acima do cap recebem uma linha skipped explícita, não uma fila.
O worker
O runtime dá spawn em um worker com chatruntime.SpawnRequest:
// internal/manager/biz/alert/investigator/usecase.go:571
worker, err := uc.spawner.SpawnWorker(ctx, chatruntime.SpawnRequest{
AgentName: uc.cfg.AgentName, // "incident-investigator"
Prompt: prompt,
Background: false,
SessionKind: "investigation",
})O prompt é renderizado por renderAlertPrompt e inclui:
- Metadados do incidente (rule, severity, device_id, value, threshold, summary).
- Uma instrução de início explícita:
Start with correlate_incident to pull metrics + logs + traces + topology around the fire window. - Um budget rígido: máximo 10 chamadas de tool, precisa começar a escrever o report até a chamada #7. Esse budget vive na mensagem do usuário porque modelos non-frontier (GLM, DeepSeek) seguem restrições de user-message mais confiavelmente que de system-message — sem ele, o cap MaxStep do eino era atingido a cada run.
- Uma diretiva de locale que sobrescreve o idioma implícito da persona com a locale da UI do operador (veja Modelos / Roteamento para como a locale se propaga).
Budget de tool + salvage
O grafo ReAct do eino limita o total de passos. Quando um worker esgota o cap sem escrever uma resposta final, o investigator salvaguarda a trilha parcial:
MessageReader.ListMessages(sessionID, limit=100)puxa cada turn.- Mensagens de assistant + tool são concatenadas em um markdown sintético "o que achamos".
- O salvage é alimentado pelo mesmo extractor Pass-2.
- O report é marcado
readycom uma nota de baixa confiança prefixada:工作器超出最大步数预算(exceeds max steps);以下为根据已收集工具结果的局部分析,置信度偏低。
Sem salvage o operador via status=failed sem dado útil — o worker tipicamente tinha chamado 10+ tools e reunido a resposta, só nunca tinha escrito o turn de síntese.
Extração estruturada (Pass 2)
A mensagem final assistant do worker é markdown — o SPA precisa de campos estruturados. Uma segunda chamada LLM barata extrai:
root_cause— TL;DR de um parágrafo.affected_window— quando o span do sintoma começou / parou.pinpointed_target— o processo / container / arquivo específico que mudou.related_alerts— incidentes co-disparando (viaRelatedAlertQuerier).evidence— bullets com citações de fonte (saída PromQL, linhas de log, etc.).suggested_actions— próximos passos rodáveis pelo operador.confidence+confidence_factors.tool_call_count— lido de volta dechat_messagespara que a UI mostre quantas tools o worker realmente invocou (não um hardcoded 0).
Modelo + provider configuráveis via Config.SummarizerProvider / Config.SummarizerModel. Timeout padrão 30s (prompt curto, resposta curta, sem loop de tool).
Quando o extractor não está conectado ou erra, o fallback usa firstParagraphOneLine sobre o markdown do worker para preencher root_cause e envia o markdown inteiro verbatim como findings_md.
Armadilha do bold-header
firstParagraphOneLine (usecase.go:846) pula scaffolding markdown puro (headings, dividers, títulos de seção totalmente em negrito) para que root_cause leia como uma frase, não como **现象**. Um bug anterior só removia o ** da frente e deixava o par final — corrigido na mesma função.
Re-trigger manual
POST /v1/alerts/incidents/{id}/investigation
Accept-Language: enForceEnqueue roda o caminho manual:
- Para qualquer worker rodando atualmente para esse incidente (best-effort — avisa e continua se o worker_id estiver stale pós-restart).
- Hard-delete da linha
investigation_reportsanterior (cobre linhas soft-deletadas de force-enqueues anteriores para que o índice uniqueincident_idnão rejeite o próximo Create). - Libera o guard de inflight.
- Chama
EnqueueWithcom a locale parseada deAccept-Languagepara que o report regenerado volte no idioma da UI do operador.
O severity floor ainda se aplica — triggers manuais em incidentes nível info retornam erros severity below floor.
Backfill em boot
Instalações novas batem em um chicken-and-egg: a cadeia RCA estruturada só conecta quando pelo menos um provider LLM está configurado, mas incidentes podem disparar antes do operador adicionar um provider. BackfillUnstartedIncidents roda no boot, percorre ListIncidentsWithoutReport(since, limit), e re-enqueua qualquer coisa que disparou na janela. Os gates normais continuam se aplicando, então incidentes resolvidos e acima do floor são pulados.
Veja cmd/ongrid/main.go para o wiring — roda no startup com uma janela de 24 horas.
Veja também
- Alertas — o que dispara.
- Topologia — o que
expand_topologyexpõe para o walk de blast radius do investigator. - Skills —
correlate_incident,get_incident_detail,query_promql,search_logs: as tools que o worker chama. - Modelos / Roteamento — como o locale + provider por-investigação é resolvido.
- Modelos / Budget — o cap global de tokens por dia que limita o custo de investigação.