Incident investigator
incident-investigator est le plus profond des workers d'Ongrid. Le coordinator dispatche vers lui chaque fois que l'utilisateur veut une cause racine, et non pas juste un résumé de symptôme. La persona remonte la chaîne causale de l'alerte observée jusqu'à la source d'origine (« patient zéro » — 0 号病人 dans le prompt de la persona) et renvoie un rapport structuré.
C'est la persona derrière le RCA
Le pipeline RCA causal HLD-013 (env de test, rollout mai 2026) est bâti sur cette persona. Quand /incident/<id> → Get RCA tourne, le manager spawne ce worker avec l'id de l'incident pré-rempli dans le prompt.
Quand le coordinator la choisit
Le fichier de persona déclare les patterns de déclenchement. Citation verbatim du frontmatter :
when_to_use: |
coordinator 在用户问以下场景时 spawn 本 worker:
• "这条告警的根因是什么 / 到底是谁导致的"
• "incident 123 怎么排查 / 受影响范围 / 持续多久"
• "这个告警是不是误报 / 跟上次那个相关吗"
• "这台机器 mem 飙了,看一下"Traduction : à chaque fois que l'utilisateur demande pourquoi plutôt que quoi. Le livrable de l'investigator est la chaîne causale jusqu'à la source, pas un snapshot d'« état actuel ».
Sac d'outils
Whitelistés dans la persona — le filtre du runtime retire tout le reste :
tools:
- query_knowledge # KB / vault / uploads
- get_incident_detail
- query_incidents
- correlate_incident # metric+log+trace pulled together
- query_change_events # config / rule / device mutations
- query_promql
- query_logql
- query_traceql
- get_edge_summary
- query_alert_rules
- query_devices
- get_host_load
- get_host_processes
- expand_topology
- find_topology_node
- host_find_large_files
- host_du_summary
- host_stat_file
disallowed_tools:
- execute_skill
- host_restart_service
- run_shell
permission_mode: read-onlyConséquences clés :
- Lecture seule. Pas de
host_restart_service, pas d'execute_skill, pas derun_shell. Si l'investigation conclut qu'un service doit être redémarré, elle renvoie ça comme proposition au coordinator ; le coordinator dispatchespecialist-opsavec une intention mutante, et le reviewer la gate. - Topologie et événements de changement. L'avantage unique qu'il a sur les specialists est
expand_topology+find_topology_node(remonter le graphe de service vers les sources amont) etquery_change_events(corréler symptômes avec mutations récentes config / règle / device autour dufired_atde l'alerte). - Bash multi-host et probes par host sont absents. Ceux-ci vivent sur les specialists (
specialist-network,specialist-compute,specialist-disk) ; l'investigator coordonne sur le plan de données d'observabilité.
Le workflow en 5 étapes
Le corps de la persona encode le workflow. Chaque investigation déroule :
- KB d'abord (obligatoire). Une fois
incident_iden main,query_knowledgeexactement une fois avec le nom de la règle + le symptôme comme requête en langage naturel (par ex. « swap_high 告警怎么排查 »). Un hit (score ≥ 0.6) signifie suivre le playbook ; la réponse finale porte une citation(参考 KB: <title>). Un miss signifie passer à l'étape 1. - Symptôme + blast radius.
get_incident_detailpour le nom de règle / sévérité / cible /fired_at/ labels. C'est la fin de la chaîne causale (l'effet), pas la cause racine. Ne vous arrêtez pas là. - Timeline.
correlate_incidenttire métrique + log + trace pour la même fenêtre d'incident en un seul appel. Triez parfired_at/ heure de première déviation. Le signal le plus précoce est le candidat source ; les CPU élevés / latence élevée en aval sont généralement des effets, pas la cause. « Le plus bruyant » ≠ « le plus précoce ». - Un bond causal en amont. Choisissez un outil avec un objectif clair :
- Qu'est-ce qui a changé ? →
query_change_events(around_ts=fired_at). Les changements côté produit sont souvent le patient zéro. - Dépendances ? →
expand_topologyamont (et non blast radius aval) /find_topology_node. - Tracer la chaîne d'appel ? →
query_traceqlpour trouver l'originateur du span le plus lent. - Première erreur ? →
query_logqlgrep par device_id pour la première ERROR / PANIC / OOM avantfired_at. - Qui a bougé en premier ? →
query_promqlpour trouver la métrique qui a dévié en premier.
- Qu'est-ce qui a changé ? →
- Récursion. Traitez le candidat amont comme le nouveau point courant. Répétez l'étape 3 jusqu'à l'une des situations :
- Toucher le fond — plus aucun amont in-system ne reste. La feuille est un processus / un changement unique / une dépendance externe = patient zéro.
- Signal épuisé — impossible d'aller plus loin. Rapportez « profondeur maximale atteinte + quel signal permettrait de continuer ».
- Valider. La cause racine proposée doit expliquer toute la chaîne aval — temporellement antérieure au symptôme, magnitude et direction cohérentes. Sinon, rétrogradez en « hypothèse » et dites-le.
Le budget de 18 outils — creuser profond, jamais tourner en rond
La persona impose une discipline d'itération stricte. Tiré du corps :
Vous avez ~18 appels d'outils de budget (assez pour remonter 4-6 couches). Creuser profond est permis, mais les branches mortes sont coupées immédiatement :
- Outil renvoie vide (
result:[]/streams:[]) : le premier vide, on peut changer d'angle ; le deuxième vide stoppe immédiatement cette ligne, changer de direction ou s'arrêter pour remonter à partir d'ici.- Même outil en échec / vide ≥2 fois → il faut changer d'outil ou de direction, interdit de tourner en boucle en permutant les expressions (les échecs de v0.7.51-55 venaient tous de là).
- Chaque étape doit progresser vers « remonter d'une couche de plus » — avant chaque appel demandez-vous « cette étape me rapproche-t-elle de la source ? ».
- Si vous avez atteint 4-5 couches sans toucher le fond, ou utilisé ~15 du budget : stop, sortez « la couche la plus profonde actuelle + le signal manquant », ne tournez pas pour remplir le budget.
Le plafond max_turns: 40 dans le frontmatter est le plafond dur — le graphe d'eino compte MaxStep = MaxIterations*2+2, donc 40 → MaxStep=82 → environ 41 tours de ChatModel. Le budget de 18 outils est l'orientation plus souple dans le prompt ; le plafond est le filet de sécurité runtime.
Les branches mortes ne se négocient pas
Le prompt de l'investigator en fait une règle explicite « ne pas faire » parce que les évals précoces v0.7.51-55 voyaient le worker brûler 30+ tours à re-permuter des expressions PromQL en essayant de faire renvoyer des données à une range query vide. Un seul résultat vide est de l'information ; un deuxième résultat vide signifie que vous avez déjà appris ce que vous allez apprendre — pivoter ou monter.
Format de sortie
La réponse finale au coordinator est du Markdown verbatim de cette forme :
**根因(0 号病人)**
{One-line patient zero — process / change / upstream service+node /
config. Concrete identifiers (pid + cmdline / service name / change
key) — this is the source of pinpoint_target. If we didn't hit bottom:
"未触底,最深到 X;要继续需 Y 信号".}
**因果链**
{Source → … → alert symptom. One line per hop, each with "why this
caused the next" plus evidence (PromQL / LogQL / trace span /
process line).}
**现象**
{1-2 sentences: when did it start / which host / what crossed
threshold / for how long.}
**置信度与验证**
{High / medium / low + reason. Plus: "what query or action would
further validate or falsify this root cause".}Le coordinator synthétise ceci dans la réponse user-facing (une langue, un paragraphe, la citation si hit KB). Le Markdown brut de l'investigator est aussi persisté comme Result du worker dans la session — l'UI RCA l'affiche verbatim sous le disclosure Reasoning.
Le test e2e F1
F1 est l'éval end-to-end qui exerce cette persona contre un incident seedé sur l'environnement de test. Forme :
- Seeder un incident synthétique
swap_highsurnode-01(device_id=7) avec un processussearxngépinglé à 95% RSS pendant 30 minutes. - Câbler le pipeline d'alerte pour que l'incident parte avec les labels attendus +
fired_at. - Dispatcher l'investigator avec
prompt = "rca incident 1234". - Asserter que la réponse finale contient :
- Règle
swap_highmentionnée dans现象. - Processus
searxngidentifié dans根因(0 号病人)avec un pid + ligne de commande. - Au moins 2 bonds causaux dans
因果链(symptôme → amont). - Citation
(参考 KB: …)SI le KB seedé a un playbook matchant.
- Règle
La première version de HLD-013 a échoué F1 parce que default_provider n'était pas configuré en DB — le résolveur retombait sur openai avec un nom de modèle glm, le chat model erronait, et le worker renvoyait une analyse vide. La leçon : F1 couvre aussi le résolveur LLM comme effet de bord, ce qui en fait le gate canonique « est-ce que le RCA s'est réellement câblé end-to-end ? ».
Raisons courantes pour lesquelles il s'arrête court
La persona renvoie honnêtement « patient zéro non atteint » quand :
- La cause est hors du cluster — fournisseur DNS, API amont, électricité.
query_change_eventsne voit pas les changements d'infra hors du scope du manager. - Les données de traces manquent — les requêtes TraceQL renvoient pas de spans pour le service pertinent. L'investigator ne peut pas remonter une chaîne d'appel sans traces ; il rapporte « 缺失 trace 信号 ».
- La ligne de log qui pointerait vers le déclencheur a tourné. Rétention Loki < le temps-jusqu'à-première-investigation. L'investigator le dit et recommande d'étendre la rétention pour les investigations répétées.
Cette honnêteté est par design. Une réponse fausse mais confiante est pire que « on a touché le signal X et il faut Y pour continuer ».
Tuning
Ce que vous voudriez réalistement changer dans un fork de cette persona :
- Ajouter des hits KB spécifiques au domaine — écrire des playbooks pour vos modes de défaillance courants, les livrer via le vault, l'étape
KB firstde l'investigator les découvrira. - Ajuster la whitelist d'outils — ajouter des variantes
query_traceqlsi votre stack de tracing n'est pas Tempo, ou retirerhost_du_summarysi vous ne voulez pas que l'investigator dispatche l'inspection disque inline (le défaut est de déléguer àspecialist-diskvia la propre sortie du worker — mais la persona porte les outils pour s'inspecter quand c'est rapide). - Resserrer le budget — si le coût par token de votre modèle importe, descendez
max_turnsà 25-30. Le budget souple de 18 appels dans le corps de la persona garde déjà la plupart des investigations sous ce seuil.
Voir Agents personnalisés pour comment monter votre fork par-dessus l'intégrée.