Skip to content

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

text
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 ready

InvestigateAsync 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".

GateDefaultVerhalten bei Miss
Severity-Floor (Config.MinSeverity)warningStiller Skip, keine Zeile geschrieben
In-Process-Inflight (pro incident_id)immer anStille Koaleszenz
Concurrency-Cap (Config.MaxConcurrent)5skipped: 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:

go
// 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:

  1. MessageReader.ListMessages(sessionID, limit=100) zieht jeden Turn.
  2. Assistant- + Tool-Messages werden in ein synthetisches „was wir gefunden haben"-Markdown verkettet.
  3. Der Salvage wird durch denselben Pass-2-Extraktor geführt.
  4. Der Bericht wird ready markiert 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 (via RelatedAlertQuerier).
  • evidence — aufgezählte Quellzitate (PromQL-Ausgabe, Log-Zeilen, etc.).
  • suggested_actions — operatorausführbare nächste Schritte.
  • confidence + confidence_factors.
  • tool_call_count — aus chat_messages zurü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

http
POST /v1/alerts/incidents/{id}/investigation
Accept-Language: en

ForceEnqueue läuft den manuellen Pfad:

  1. 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).
  2. Hart-lösche die vorherige investigation_reports-Zeile (deckt soft-gelöschte Zeilen aus früheren Force-Enqueues ab, sodass der eindeutige incident_id-Index das nächste Create nicht ablehnt).
  3. Löse den Inflight-Guard.
  4. Rufe EnqueueWith auf, mit dem aus Accept-Language geparsten 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_topology für den Wirkungsradius-Walk des Investigators offenlegt.
  • Skillscorrelate_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.