RCA (Grundursache)
Wenn ein Alarm feuert, spawnt Ongrid einen LLM-Worker, der den Graph-Kernel-ReAct-Agenten auf der incident-investigator-Persona antreibt, Tools aufruft, um Beweise zu sammeln, und einen strukturierten Bericht zurück in investigation_reports schreibt.
Der Bericht rendert auf der /alerts/incidents/:id-Seite der SPA neben der feuernden Serie — der menschliche SRE muss nie „bei einem leeren Prompt" anfangen.
HLD-013
Die aktuelle Pipeline landet HLD-013s Phase 1+2 Kausalmodell. Der naive „fasse zusammen, was gefeuert hat"-Ansatz (PR-2) wurde ersetzt, sobald klar wurde, dass Operatoren den Patient Zero wollten — den spezifischen Prozess / Container / die Zeile, die die Kaskade startete — keine Wiederholung des Alarmtexts.
Lebenszyklus
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 ist die öffentliche Naht, die alert.Usecase aufruft — siehe usecase.go:301.
Gates
Drei Gates filtern, bevor ein Worker je gespawnt wird. Jede Ablehnung wird als status=skipped-Zeile persistiert, sodass die SPA dem Operator einen Grund zeigt statt „nie gestartet".
| Gate | Default | Verhalten bei Miss |
|---|---|---|
Severity-Floor (Config.MinSeverity) | warning | Stiller Skip, keine Zeile geschrieben |
In-Process-Inflight (pro incident_id) | immer an | Stille Koaleszenz |
Concurrency-Cap (Config.MaxConcurrent) | 5 | skipped: concurrency limit reached (N workers in flight)-Zeile |
Der Concurrency-Cap verteidigt LLM-Provider-Rate-Limits und begrenzt RAM, wenn 100 Incidents auf einmal feuern. Over-Cap-Caller bekommen eine explizite Skipped-Zeile, keine Queue.
Der Worker
Die Runtime spawnt einen Worker mit 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",
})Das Prompt wird von renderAlertPrompt gerendert und enthält:
- Incident-Metadaten (Regel, Severity, device_id, Wert, Schwellwert, Zusammenfassung).
- Eine explizite Startanweisung:
Start with correlate_incident to pull metrics + logs + traces + topology around the fire window. - Ein hartes Budget: 10 Tool-Aufrufe max, muss den Bericht bis Aufruf #7 anfangen zu schreiben. Dieses Budget lebt in der User-Message, weil Nicht-Frontier-Modelle (GLM, DeepSeek) User-Message-Constraints zuverlässiger befolgen als System-Message-Constraints — ohne es wurde der eino-MaxStep-Cap bei jedem zweiten Lauf getroffen.
- Eine Locale-Direktive, die die implizite Sprache der Persona mit dem UI-Locale des Operators überschreibt (siehe Modelle / Routing wie das Locale propagiert).
Tool-Budget + Salvage
Der eino-ReAct-Graph cappt Gesamtschritte. Wenn ein Worker den Cap erschöpft, ohne eine finale Antwort zu schreiben, salvagiert der Investigator die teilweise Spur:
MessageReader.ListMessages(sessionID, limit=100)zieht jeden Turn.- Assistant- + Tool-Messages werden in ein synthetisches „was wir gefunden haben"-Markdown verkettet.
- Der Salvage wird durch denselben Pass-2-Extraktor geführt.
- Der Bericht wird
readymarkiert mit einer Low-Confidence-Notiz vorangestellt:工作器超出最大步数预算(exceeds max steps);以下为根据已收集工具结果的局部分析,置信度偏低。
Ohne Salvage sah der Operator status=failed ohne nützliche Daten — der Worker hatte typischerweise 10+ Tools aufgerufen und die Antwort gesammelt, er hatte nur nie den Synthese-Turn geschrieben.
Strukturierte Extraktion (Pass 2)
Die finale Assistant-Message des Workers ist Markdown — die SPA braucht strukturierte Felder. Ein zweiter, günstiger LLM-Aufruf extrahiert:
root_cause— Einzelabsatz-TL;DR.affected_window— wann begann/endete die Symptom-Spanne.pinpointed_target— der spezifische Prozess / Container / die Datei, die sich änderte.related_alerts— co-feuernde Incidents (viaRelatedAlertQuerier).evidence— aufgezählte Quellzitate (PromQL-Ausgabe, Log-Zeilen, etc.).suggested_actions— operatorausführbare nächste Schritte.confidence+confidence_factors.tool_call_count— auschat_messageszurückgelesen, sodass die UI zeigt, wie viele Tools der Worker tatsächlich aufgerufen hat (nicht eine hartkodierte 0).
Konfigurierbares Modell + Provider via Config.SummarizerProvider / Config.SummarizerModel. Default 30s Timeout (kurzes Prompt, kurze Antwort, keine Tool-Loop).
Wenn der Extraktor nicht verdrahtet ist oder Fehler hat, verwendet der Fallback firstParagraphOneLine über das Markdown des Workers, um root_cause zu füllen, und versendet das gesamte Markdown wörtlich als findings_md.
Bold-Header-Falle
firstParagraphOneLine (usecase.go:846) überspringt reines Markdown-Scaffolding (Überschriften, Trenner, vollständig fettgedruckte Sektionstitel), sodass root_cause als Satz liest, nicht als **现象**. Ein vorheriger Bug entfernte nur das führende ** und ließ ein nachgestelltes Paar — in derselben Funktion behoben.
Manueller Re-Trigger
POST /v1/alerts/incidents/{id}/investigation
Accept-Language: enForceEnqueue läuft den manuellen Pfad:
- Stoppe jeden aktuell laufenden Worker für diesen Incident (Best-Effort — warnt und fährt fort, wenn die worker_id nach einem Restart veraltet ist).
- Hart-lösche die vorherige
investigation_reports-Zeile (deckt soft-gelöschte Zeilen aus früheren Force-Enqueues ab, sodass der eindeutigeincident_id-Index das nächste Create nicht ablehnt). - Löse den Inflight-Guard.
- Rufe
EnqueueWithauf, mit dem ausAccept-Languagegeparsten Locale, sodass der regenerierte Bericht in der UI-Sprache des Operators zurückkommt.
Der Severity-Floor gilt weiterhin — manuelle Trigger auf Info-Level-Incidents geben severity below floor-Fehler zurück.
Boot-Time-Backfill
Frische Installationen treffen ein Henne-Ei-Problem: die strukturierte RCA-Chain verdrahtet sich nur, wenn mindestens ein LLM-Provider konfiguriert ist, aber Incidents können feuern, bevor der Operator einen Provider hinzufügt. BackfillUnstartedIncidents läuft beim Boot, durchläuft ListIncidentsWithoutReport(since, limit) und re-enqueued alles, was im Fenster gefeuert hat. Die normalen Gates gelten weiterhin, sodass resolvierte Incidents und Over-Floor-Incidents übersprungen werden.
Siehe cmd/ongrid/main.go für die Verdrahtung — sie läuft beim Start mit einem 24-Stunden-Fenster.
Siehe auch
- Alarme — was feuert.
- Topologie — was
expand_topologyfür den Wirkungsradius-Walk des Investigators offenlegt. - Skills —
correlate_incident,get_incident_detail,query_promql,search_logs: die Tools, die der Worker aufruft. - Modelle / Routing — wie das Per-Investigation-Locale + Provider aufgelöst wird.
- Modelle / Budget — der globale Per-Tag-Token-Cap, der die Untersuchungskosten begrenzt.