RCA (корневая причина)
Когда срабатывает алерт, Ongrid порождает LLM-воркера, который ведёт graph-kernel ReAct-агента на персоне incident-investigator, вызывает инструменты для сбора доказательств и пишет структурированный отчёт обратно в investigation_reports.
Отчёт рендерится на странице SPA /alerts/incidents/:id рядом со срабатывающей серией — человеку-SRE никогда не приходится начинать «с пустого промпта».
HLD-013
Текущий конвейер приземляет Phase 1+2 причинной модели HLD-013. Наивный подход «суммировать что сработало» (PR-2) был заменён, как только стало ясно, что операторы хотели нулевого пациента — конкретный процесс / контейнер / строку, которая запустила каскад — а не пересказ текста алерта.
Lifecycle
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 — публичный шов, который вызывает alert.Usecase — см. usecase.go:301.
Гейты
Три гейта фильтруют до того, как воркер вообще порождается. Каждый отказ сохраняется как строка status=skipped, так что SPA показывает оператору причину вместо «не начато никогда».
| Гейт | По умолчанию | Поведение при промахе |
|---|---|---|
Severity floor (Config.MinSeverity) | warning | Молчаливый пропуск, строка не пишется |
In-process inflight (per incident_id) | всегда вкл | Молчаливое объединение |
Concurrency cap (Config.MaxConcurrent) | 5 | Строка skipped: concurrency limit reached (N workers in flight) |
Concurrency cap защищает rate-limit провайдера LLM и ограничивает RAM, когда 100 инцидентов срабатывают одновременно. Over-cap вызывающие получают явную skipped-строку, не очередь.
Воркер
Рантайм порождает воркера с 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",
})Промпт рендерится renderAlertPrompt и включает:
- Метаданные инцидента (rule, severity, device_id, value, threshold, summary).
- Явную стартовую инструкцию:
Start with correlate_incident to pull metrics + logs + traces + topology around the fire window. - Жёсткий бюджет: 10 вызовов инструментов максимум, должен начать писать отчёт к вызову #7. Этот бюджет живёт в user-сообщении, потому что не-frontier модели (GLM, DeepSeek) следуют user-message ограничениям надёжнее, чем system-message — без этого предел eino MaxStep попадался на каждом втором прогоне.
- Locale-директива, которая переопределяет неявный язык персоны локалью UI оператора (см. Models / Routing, как локаль распространяется).
Бюджет инструментов + salvage
ReAct-граф eino ограничивает общие шаги. Когда воркер исчерпывает предел без написания финального ответа, investigator вытаскивает частичный след:
MessageReader.ListMessages(sessionID, limit=100)тянет каждый ход.- Сообщения assistant + tool конкатенируются в синтетический markdown «что мы нашли».
- Salvage пропускается через тот же Pass-2 extractor.
- Отчёт помечается
readyс предваряющей low-confidence заметкой:工作器超出最大步数预算(exceeds max steps);以下为根据已收集工具结果的局部分析,置信度偏低。
Без salvage оператор видел status=failed без полезных данных — воркер обычно вызвал 10+ инструментов и собрал ответ, он просто никогда не написал synthesis-ход.
Структурное извлечение (Pass 2)
Финальное assistant-сообщение воркера — markdown — SPA нужны структурированные поля. Второй, дешёвый LLM-вызов извлекает:
root_cause— однопараграфный TL;DR.affected_window— когда span симптома начался / закончился.pinpointed_target— конкретный процесс / контейнер / файл, который изменился.related_alerts— co-firing инциденты (черезRelatedAlertQuerier).evidence— буллетированные исходные цитаты (вывод PromQL, лог-строки и т.д.).suggested_actions— operator-runnable следующие шаги.confidence+confidence_factors.tool_call_count— считывается обратно изchat_messages, так что UI показывает, сколько инструментов воркер реально вызвал (не зашитый 0).
Настраиваемая модель + provider через Config.SummarizerProvider / Config.SummarizerModel. По умолчанию 30с таймаут (короткий промпт, короткий ответ, без tool-loop).
Когда extractor не подключён или ошибается, fallback использует firstParagraphOneLine поверх markdown воркера, чтобы заполнить root_cause и отгрузить весь markdown дословно как findings_md.
Ловушка bold-заголовка
firstParagraphOneLine (usecase.go:846) пропускает чистый markdown scaffolding (headings, dividers, полностью bold-заголовки секций), так что root_cause читается как предложение, а не как **现象**. Прежний баг срезал только ведущий ** и оставлял хвостовую пару — исправлено в той же функции.
Ручной re-trigger
POST /v1/alerts/incidents/{id}/investigation
Accept-Language: enForceEnqueue запускает ручной путь:
- Остановить любой текущий воркер для этого инцидента (best-effort — предупреждает и продолжает, если worker_id устарел после рестарта).
- Hard-delete предыдущей строки
investigation_reports(покрывает soft-deleted строки от ранних force-enqueue, чтобы уникальный индексincident_idне отверг следующий Create). - Освободить inflight guard.
- Вызвать
EnqueueWithс локалью, распарсенной изAccept-Language, так что регенерированный отчёт приходит обратно на языке UI оператора.
Severity floor всё ещё применяется — ручные триггеры на info-level инцидентах возвращают ошибки severity below floor.
Boot-time backfill
Свежие установки попадают в курицу-и-яйцо: цепочка структурного RCA подключается только когда сконфигурирован хотя бы один LLM provider, но инциденты могут сработать до того, как оператор добавил provider. BackfillUnstartedIncidents запускается при загрузке, обходит ListIncidentsWithoutReport(since, limit) и переочередит всё, что сработало в окне. Нормальные гейты всё ещё применяются, так что resolved-инциденты и over-floor инциденты пропускаются.
См. cmd/ongrid/main.go для подключения — он запускается при старте с 24-часовым окном.
См. также
- Алерты — что срабатывает.
- Топология — что выставляет
expand_topologyдля blast-radius walk investigator. - Skills —
correlate_incident,get_incident_detail,query_promql,search_logs: инструменты, которые вызывает воркер. - Models / Routing — как per-investigation locale + provider резолвится.
- Models / Budget — глобальный per-day token cap, который ограничивает стоимость расследования.