Skip to content

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 :

yaml
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 :

yaml
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-only

Conséquences clés :

  • Lecture seule. Pas de host_restart_service, pas d'execute_skill, pas de run_shell. Si l'investigation conclut qu'un service doit être redémarré, elle renvoie ça comme proposition au coordinator ; le coordinator dispatche specialist-ops avec 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) et query_change_events (corréler symptômes avec mutations récentes config / règle / device autour du fired_at de 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 :

  1. KB d'abord (obligatoire). Une fois incident_id en main, query_knowledge exactement 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.
  2. Symptôme + blast radius. get_incident_detail pour 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à.
  3. Timeline. correlate_incident tire métrique + log + trace pour la même fenêtre d'incident en un seul appel. Triez par fired_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 ».
  4. 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_topology amont (et non blast radius aval) / find_topology_node.
    • Tracer la chaîne d'appel ?query_traceql pour trouver l'originateur du span le plus lent.
    • Première erreur ?query_logql grep par device_id pour la première ERROR / PANIC / OOM avant fired_at.
    • Qui a bougé en premier ?query_promql pour trouver la métrique qui a dévié en premier.
  5. 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 ».
  6. 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 :

markdown
**根因(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 :

  1. Seeder un incident synthétique swap_high sur node-01 (device_id=7) avec un processus searxng épinglé à 95% RSS pendant 30 minutes.
  2. Câbler le pipeline d'alerte pour que l'incident parte avec les labels attendus + fired_at.
  3. Dispatcher l'investigator avec prompt = "rca incident 1234".
  4. Asserter que la réponse finale contient :
    • Règle swap_high mentionnée dans 现象.
    • Processus searxng identifié 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.

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_events ne 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 first de l'investigator les découvrira.
  • Ajuster la whitelist d'outils — ajouter des variantes query_traceql si votre stack de tracing n'est pas Tempo, ou retirer host_du_summary si vous ne voulez pas que l'investigator dispatche l'inspection disque inline (le défaut est de déléguer à specialist-disk via 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.