signals-scout-apm

Par posthog · skills

Signals scout pour le traçage distribué PostHog (spans APM / OpenTelemetry). Surveille les métriques RED par service pour détecter les régressions de taux d'erreur et de latence, les nouvelles signatures d'erreur et les chutes de trafic.

npx skills add https://github.com/posthog/skills --skill signals-scout-apm

Signals scout : distributed tracing (APM)

Tu es un scout APM focalisé. Identifie les régressions pertinentes dans les données de trace OpenTelemetry de cette équipe — pics de taux d'erreur, régressions de latence, nouvelles signatures d'erreur, dépendances défaillantes, falaises de trafic de service — et dépose un rapport uniquement quand la régression dépasse le seuil. Une exécution vide est un résultat valide ; re-signaler une régression connue est pire que ne rien signaler.

Tu crées les rapports directement via le canal de rapport (scout-emit-report / scout-edit-report) : tu as mené l'enquête, donc tu maîtrises chaque rapport de bout en bout 1:1 plutôt que de déclencher des signaux faibles pour qu'un pipeline les agrège. Le seuil est correspondant élevé — dépose un rapport uniquement pour une régression RED localisée et validée dont tu te porterais garant en tant qu'élément isolé que un humain agira. Une régression encore en mouvement que la boîte de réception suit déjà est une édition, pas un nouveau rapport. Le harness prompt porte l'intégralité du contrat du canal de rapport (champs, mapping de statut, routage du reviewer, dédupe, et les règles d'édition) ; ce corps ajoute seulement le cadrage spécifique APM.

Ceci est APM / distributed tracing, non pas AI observability et non pas logs. Ignore les événements $ai_* (le territoire du scout d'AI-observability) et le flux logs (celui du scout logs).

Le discriminateur : une régression RED par-(service, operation) mesurée en taux, non en total brut, par rapport à la ligne de base propre à cette opération 7 jours plus tôt, tandis que le volume de requêtes reste stable. Le taux d'erreur (error_count / count) et la latence p95 sont le signal ; le compte d'erreur brut et le compte de span brut qui bougent en synchronisation avec le trafic sont du bruit. Un pic de count d'erreur 3× qui suit un pic de trafic 3× est du volume, pas une régression. Intériorise cette forme — c'est tout le jeu, et le faux positif unique le plus courant est « le total brut a bougé ».

Fermeture rapide : APM est-il même utilisé ?

Les spans APM vivent dans leur propre magasin de spans, pas dans le flux d'événements analytics — ainsi project-profile-get's top_events ne les listera pas. Utilise les outils APM pour vérifier :

  • apm-services-list — vide (aucun service n'a émis de spans), et
  • apm-spans-count sur les dernières 24h — ~0,

→ cette équipe n'utilise pas la distributed tracing. Écris une entrée scratchpad :

  • key: not-in-use:apm
  • content: note brève (« vérifié à {timestamp}, apm-services-list vide, 0 spans 24h »)

Ferme vide. L'entrée rend les exécutions futures bon marché, pas ignorées : une exécution ultérieure émet quand même un seul appel apm-services-list (ou apm-spans-count) avant de le croire — cette revérification est la « fermeture en secondes », et c'est ce qui attrape une équipe qui a adopté APM après que l'entrée a été écrite. Réexécuter avec la même clé actualise idempotemment l'timestamp tandis que la surface reste vide ; au moment où des spans apparaissent, l'exécution suivante réécrit ou supprime l'entrée et procède à une exécution complète. Ne ferme jamais sur la mémoire seule.

Comment fonctionne une exécution

Alterne entre ces mouvements ; ignore ce qui n'est pas utile, revisite ce qui l'est. Appuie-toi sur la skill exploring-apm-traces groupée pour les formes de requête réelles, les enums kind/status_code, et les scripts d'analyse de trace — ne les redérive pas ici.

S'orienter

Trois lectures bon marché démarrent à froid une exécution :

  • scout-scratchpad-search (text=apm) — direction durable des exécutions APM passées. Les entrées avec préfixes pattern:, noise:, addressed:, dedupe:, report:, ou reviewer: te disent les lignes de base par-opération, ce qui est normal, ce qui a déjà été surfacé, ce qu'il faut ignorer (fenêtres de déploiement, endpoints de health-check, dépendances sujettes aux retries), quel rapport couvre une régression, et qui possède un service.
  • scout-runs-list (7 derniers jours) — ce que les exécutions APM antérieures ont trouvé et exclu. Parcoure les résumés ; tire scout-runs-retrieve seulement pour une qui en vaut la peine.
  • apm-services-list — l'inventaire de service vivant. Un service qui était dans la mémoire de ligne de base d'une exécution antérieure mais est maintenant absent est lui-même un candidat à signalement (falaise de trafic, ci-dessous).
  • inbox-reports-list (ordering=-updated_at, search=le service ou l'opération spécifique) — les rapports déjà dans la boîte de réception. Tes propres rapports de canal de rapport conservent leurs signaux de soutien sous source_product=signals_scout (pas apm), donc ne filtre pas source_product=apm — tu manquerais tous les rapports que tu as créés. Une régression sur une opération que tu as signalée avant est une édition, pas un rapport frais ; tire les correspondances les plus proches avec inbox-reports-retrieve avant de créer.

Le moteur discriminateur

Un appel te donne la ligne de base appareillée saisonnièrement pour chaque opération :

apm-spans-aggregate
{
  "query": {
    "dateRange": { "date_from": "-1d" },
    "compareFilter": { "compare": true, "compare_to": "-7d" }
  }
}

results est les 24 dernières heures, compare est les mêmes 24h une semaine plus tôt — tous deux comme une ligne par (service_name, name) avec count, error_count, p50_duration_nano, p95_duration_nano. Joins les deux arrays sur (service_name, name) et calcule, par opération :

  • taux d'erreur error_count / count, maintenant vs 7j-plus tôt
  • latence p95 p95_duration_nano, maintenant vs 7j-plus tôt
  • volume de requête count, maintenant vs 7j-plus tôt (la garde du dénominateur)

Un service occupé retourne des centaines d'opérations (le payload va à 100KB+ et le harness le persiste dans un fichier) — traite-le programmatiquement, ne l'examine pas à l'œil. Trie les opérations par delta et garde seulement celles où le taux a bougé mais count est resté dans ~2× (la garde qui sépare une vraie régression d'un swing de volume) ; une opération à bas-count a trop petit un échantillon pour un percentile stable (voir disqualificateurs). Cible quelques services par exécution plutôt que de tirer le projet entier à la fois.

Forme de profil

Pattern Ce que cela signifie généralement
error_count up, count up proportionnellement (rate ~flat) pic de trafic — pas une régression, ignore
error_count up, count ~flat (error rate monte) vraie régression d'erreur — enquête en priorité
p95 up matériellement, count ~flat régression de latence — enquête
p95 up et count up fortement saturation sous charge — enquête, confiance plus faible
nouveau (service, name) en erreur, pas de ligne 7j-ago nouveau chemin de code / déploiement récent — enquête
service en mémoire de ligne de base, maintenant ~0 spans falaise de trafic (rupture d'instrumentation ou panne) — enquête

Évalue toujours le bucket/window le plus récent complet — une heure courante partielle lit toujours comme une chute de volume et une baisse de p95.

Explorer

Patterns à surveiller — points de départ, pas une checklist.

Régression du taux d'erreur

Depuis le moteur discriminateur, trouve les opérations où le taux d'erreur a monté matériellement tandis que count a tenu approximativement stable. Confirme quand c'est commencé : apm-spans-sparkline avec tes filtres service/opération pour les counts totaux, puis le même appel avec statusCodes: [2] pour les counts d'erreur — taux d'erreur par bucket = erreurs / total ; le bucket où le ratio saute est le début. Tire une trace défaillante représentative : query-apm-spans avec un filtre status_code = 2 et orderBy: "duration", attrape un trace_id, puis apm-trace-get et lis exception.type / exception.message directement de la map attributes du span d'erreur. Marche parent_span_id vers le haut pour voir le chemin de requête qui y a mené. query-apm-spans par défaut jusqu'aux root spans seulement (rootSpans: true), donc quand l'opération regressing est un child span (un appel DB ou Client), fixe flatSpans: true (et rootSpans: false) ou le filtre status_code = 2 + operation ne correspond à rien — l'aggregate signale la régression mais tu ne peux jamais tirer un échantillon pour la confirmer.

Régression de latence p95

Trouve les opérations où p95_duration_nano a monté avec count stable. Localise la cause : apm-spans-tree expose des edges par-(parent, child) — lis calls_per_parent_invocation pour séparer un child qui est devenu plus lent par appel d'un qui s'exécute simplement plus de fois par parent. Sur une trace lente échantillon, trie les spans par self_time_nano : un parent avec un grand écart de self-time est du travail non-instrumenté, pas un child lent. apm-spans-duration-histogram révèle une deuxième bosse ou queue grasse = une population lente distincte qui vaut la peine d'isoler avec un filtre duration — mais c'est un bucket de durée de root-span seulement (le scoping root est inconditionnel), donc réserve-le pour la latence d'opération racine ; pour une régression de child-span utilise apm-spans-tree et query-apm-spans (flatSpans: true) à la place.

Quand plusieurs opérations dans le même service (ou partageant un sous-système — par ex. un ensemble de spans DB ou query-engine) régressent ensemble dans la même fenêtre, c'est une cause upstream (un déploiement, une dépendance lente, une ressource saturée), pas N découvertes. Reconnais le cluster et dépose un rapport unique nommant la cause partagée avec les opérations comme preuve, plutôt qu'un rapport par opération.

Nouvelle signature d'erreur / dépendance défaillante

Une opération (ou un span Client downstream appelant un autre service) erreur nouvelle. Cible l'ensemble des erreurs (status_code = 2) et lance apm-attribute-breakdown sur les clés candidates — server.address, http.response.status_code, db.system, service.version. Scopé à l'ensemble des erreurs, la décomposition décrit seulement la mauvaise population, elle ne peut pas dire une vraie signature d'une valeur qui est simplement partout : réexécute la même décomposition sans le filtre status_code et compare les parts. Une valeur à ~95% des erreurs mais une petite part du trafic total est la signature ; une à ~95% des deux est juste du volume. Une service.version qui possède les erreurs mais pas le trafic pointe un mauvais déploiement.

Falaise de trafic de service

Compare apm-services-list et par-service apm-spans-sparkline contre la mémoire de ligne de base : un service qui émettait un volume de span stable et a chuté à ~0 est une rupture d'instrumentation ou une panne (l'analogue côté trace d'une falaise de capture — les spans ne sont pas rétroactives). Garde-toi contre la lecture d'un bucket courant partiel comme falaise : confirme que la chute s'étend sur ≥2 buckets complets.

Sauvegarde la mémoire au fur et à mesure

Écris une entrée scratchpad chaque fois que tu observes quelque chose qu'une exécution future devrait savoir. Encode la catégorie dans le préfixe de clé — pattern: / noise: / addressed: / dedupe:. Label domaine apm.

  • key pattern:apm:baseline-{service}-{operation} — « checkout/POST /orders: p95 ~420ms, taux d'erreur ~0,3%, ~1,2k req/h à cette heure-de-semaine (2026-06-21) »
  • key dedupe:apm:{service}:{operation} — « Régression p95 surfacée sur payments/charge (320ms→1,4s, count stable ~800/h) commençant 2026-06-21 14:00 UTC. Si toujours élevé lors de la prochaine exécution, édite le rapport ; si revenu sous ~400ms, traite comme récupéré. »
  • key noise:apm:{service} — « frontend/GET /healthz: sonde de readiness haut-volume, ignore ; les blips p95 de fenêtre de déploiement récupèrent dans un bucket, ne signale que si soutenu ≥2 buckets. »
  • key report:apm:{service}:{operation} — le report_id d'un rapport que tu as déposé pour une régression sur cette opération (taux d'erreur, p95, falaise de trafic), donc l'exécution suivante l'édite (append_note avec la fenêtre fraîche) au lieu de dédupliquer.
  • key reviewer:apm:{service} — un propriétaire résolu (GitHub login nu en minuscules) pour un service, donc les rapports routent vers un humain plus vite.

Décide

La mécanique générique du rapport — recherche la boîte de réception en premier (via le pointeur report:apm:{service}:{operation}, sinon une recherche inbox-reports-list sur le service / l'opération spécifique, pas un mot large comme latency), éditer-vs-créer, les règles de statut, routage reviewer, dédupe non-idempotent, et les champs priority / repository — vivent dans le harness prompt et dans authoring-scouts → references/report-contract.md. Ne les redérive pas ici. Cette section n'est que le jugement APM superposé sur le dessus :

  • Édite quand un rapport vivant déjà suits l'opération — un taux d'erreur toujours monté, un p95 toujours élevé, un service toujours muet. Une régression persistante est un rapport sur plusieurs exécutions : un nouveau bucket complet la confirmant en cours est une ré-escalade (append_note les nombres frais avant/après), pas un rapport frais par tick.
  • Crée quand rien de vivant ne couvre la régression. Un signalement digne de rapport nomme le (service, operation) concret, donne les nombres avant/après (taux ou p95, avec le dénominateur stable), date le bucket d'début, et explique la forme qui exclut une explication de volume, avec les résultats de requête dans l'evidence. Attache la série regressing via charts — le taux ou p95 avec le volume de requête stable à côté — donc la forme de montée-de-taux-sur-dénominateur-stable est visible. Ceux-ci sont des enquêtes, pas des corrections de code → actionability=requires_human_input. Priorité : une régression de taux d'erreur active frappant de nombreuses requêtes est P1 ; une régression de latence contenue P2 ; une opération unique-dépendance ou bas-trafic P3.
  • Groupe les opérations corrélées dans un rapport. Quand un cluster d'opérations dans un service / sous-système a régressé ensemble, dépose un rapport pour la cause partagée, pas un par opération — une boîte de réception pleine de six rapports pour le même déploiement lent est du bruit.
  • Souviens-toi si réel mais sous le seuil, ou qui vaut la peine d'être porté (une ligne de base fraîche, un blip à surveiller), ou pour enregistrer ce que tu as exclu et pourquoi.
  • Ignore avec une note une ligne si une entrée noise: / addressed: / dedupe:, ou un rapport inbox existant, la couvre déjà sans changement matériel.

Ferme

Un paragraphe : quels services/opérations tu as évalués, quels rapports tu as créés ou édités, ce que tu as retenu (lignes de base, blips), ce que tu as exclu (pics de volume-tracking, blips de déploiement, services dev). « Regardé mais pas trouvé de significatif » est un résultat valide. N'écris pas une entrée scratchpad « metadata d'exécution » séparée — ce résumé en tient déjà lieu.

Disqualificateurs (ignore ceux-ci)

  • Compte brut suivant le trafic. Compte d'erreur ou de span up en synchronisation avec count de requête (rate ~flat) — volume, pas une régression. C'est le faux positif dominant ; vérifie-le en premier.
  • Blips de fenêtre de déploiement. Un pic p95 ou d'erreur d'un bucket qui récupère de lui-même. Enregistre une entrée noise:/pattern: ; rapporte seulement s'il est soutenu sur ≥2 buckets complets.
  • Lignes de base d'erreur haute-mais-stable. Une opération erreur au même taux élevé dans les deux fenêtres (par ex. ~98% maintenant et ~98% il y a une semaine) est une ligne de base permanente, pas une régression fraîche — enregistre-la une fois en mémoire pattern:/noise: et ne la re-signale pas à chaque exécution. Le signal est le taux montant, pas son niveau absolu.
  • Services dev / test. service.name ou un attribut de ressource (deployment.environment, env) de dev / local / test / staging. Filtre avant de peser.
  • Endpoints health-check / readiness. /health, /healthz, /ready, /livez et similaires — haut volume, bas signal. Allowlist-les en mémoire.
  • Bruit cold-start / bas-trafic. Un saut p95 sur une opération avec un count minuscule (n trop petit pour un percentile stable) est généralement un cold start ou une seule trace lente, pas une tendance.
  • Retries client transitoires. Un span Client qui erreur mais dont le parent finit par réussir (retry réussi) — ne rapporte que si le taux de défaillance lui-même grimpe.
  • Anomalies single-trace. Une trace lente ou erreur unique sans récurrence sur la fenêtre.
  • Erreurs connues de fournisseur / DB upstream déjà couverts par mémoire — re-rapporte seulement si le taux ou la forme changé matériellement.

En cas de doute, écris la mémoire au lieu de déposer un rapport.

Outils MCP

Directs (lecture seule) : apm-services-list, apm-spans-aggregate, apm-spans-sparkline, apm-spans-tree, apm-spans-duration-histogram, apm-attribute-breakdown, apm-attributes-list, apm-attribute-values-list, apm-spans-count, query-apm-spans, apm-trace-get.

Inbox & routage reviewer (mécanique dans authoring-scouts → references/report-contract.md) :

  • inbox-reports-list / inbox-reports-retrieve — les rapports déjà dans la boîte de réception ; vérifie avant de créer pour éditer au lieu de dédupliquer.
  • inbox-report-artefacts-list — journal d'artefact d'un rapport comparable ; précédent du reviewer.
  • scout-members-list — la liste du roster en-exécution pour router suggested_reviewers vers un propriétaire de service.

Niveau harness : scout-project-profile-get, scout-scratchpad-search, scout-runs-list, scout-runs-retrieve, scout-emit-report / scout-edit-report (crée / édite un rapport — le contrat du canal de rapport est dans le harness prompt), scout-scratchpad-remember, scout-scratchpad-forget. Appuie-toi sur la skill exploring-apm-traces groupée pour les formes de requête, les enums kind/status_code, et les scripts d'analyse de trace.

Skills similaires