Aller au contenu

0079 — Boucle fermée dérive → réentraînement, active par défaut (citation)

L’ADR 0062 a porté citation au MLOps niveau 1→2 : suivi MLflow, mesure de dérive (Evidently, asset check non bloquant evidently_embedding_drift), et entraînement continu (CT) orchestré par un @schedule et un @sensor — mais STOPPED par défaut : « le code permet la cadence ; activer le schedule relève du déployeur ». La dérive mesurée informe (score journalisé MLflow) et nourrit la décision humaine d’ajuster la cadence ; aucun seuil ne déclenche le réentraînement.

La page Normes et pratiques appliquées classe en conséquence le « Continuous training autonome » dans « Ce qui n’est pas encore appliqué » : la boucle fermée « seuil de dérive → réentraînement automatique » n’existe pas. C’est précisément cette brique — le cœur d’un MLOps niveau 2 autonome — que le présent ADR ajoute.

Fermer la boucle rompt une posture tenue partout : « le code permet, le déployeur arme » (sensors STOPPED par défaut, ADR 0031 — capacité vs décision d’instance). Un réentraînement déclenché sans geste humain est exactement le « re-training silencieux » que les commentaires de code disaient vouloir éviter. Cette rupture doit donc être actée explicitement — c’est l’objet de cet ADR.

Un piège de conception doit être traité de front. La dérive est mesurée N vs N-1 en aval de researcher_embeddings, dans transform_job. Réentraîner sur les mêmes données ne fait pas disparaître la dérive : elle vient de l’arrivée de données neuves (snapshot OpenAlex), pas du modèle. Une boucle naïve « dérive détectée → relance » s’emballerait (relance perpétuelle sur la même donnée). La boucle n’a de sens que conditionnée à l’avancée de l’ingestion.

citation ferme la boucle MLOps : la dérive mesurée déclenche AUTOMATIQUEMENT un réentraînement (transform_job), et ce sensor est ACTIF PAR DÉFAUT (RUNNING). Le déclenchement n’a lieu que si la dérive est confirmée ET que le watermark d’ingestion a AVANCÉ depuis le dernier réentraînement — garantissant la terminaison. Le déployeur peut DÉSARMER la boucle (opt-out via une variable d’environnement), au lieu de devoir l’armer. C’est une autonomie réelle, assumée et bornée.

Le verdict de dérive (drift_detected, drift_score, et l’état complet du watermark d’ingestion vu au moment de la mesure) est persisté en S3 par l’asset check (drift/researcher_embeddings/_drift_verdict.json) — le journal MLflow étant best-effort et illisible d’un sensor. Un sensor retrain_on_drift lit ce verdict et déclenche transform_job si et seulement si : (a) la dérive est détectée, (b) le watermark vu diffère de celui du dernier réentraînement (donnée neuve), (c) un cooldown est écoulé.

Terminaison prouvée. Le réentraînement ne ré-ingère pas : il ne fait pas avancer le watermark. Le run post-retrain re-mesure donc une dérive sur le même watermark → condition (b) fausse → SKIP. La boucle atteint un point fixe en une itération ; elle ne peut pas s’emballer. S’y ajoutent une déduplication par identifiant du verdict (un même verdict ne déclenche qu’un retrain) et un cooldown (anti-flapping du test statistique). Le détail vit dans la fonction pure evaluate_drift_retrain (testée), pas dans cet ADR.

Active par défaut, opt-out — la rupture assumée

Section intitulée « Active par défaut, opt-out — la rupture assumée »

Contrairement au @schedule et au @sensor watermark de l’ADR 0062 (STOPPED, le déployeur arme), le sensor retrain_on_drift est RUNNING par défaut : l’autonomie est la posture par défaut. Le déployeur opt-out via CITATION_RETRAIN_AUTO=off (la boucle est alors absente, pas un sensor STOPPED). La cadence fine reste une valeur d’instance (CITATION_RETRAIN_COOLDOWN_S). C’est un renversement ciblé de la charge : armer devient le défaut, désarmer devient le geste — pour cette brique précise, sur ce pipeline précis.

mediawatch v1 (« articles seulement ») n’a ni embeddings ni Evidently : aucun signal de dérive à fermer. La boucle ne le concerne pas ; son écart « CT autonome » subsiste, le jour où il aura une mesure de dérive.

Le réentraînement automatique re-dérive depuis curated filtré sur le registre d’opposition exactement comme un run manuel : transform_job ré-exécute les modèles dbt (anti-join opposition_pairs) et researcher_embeddings (exclusion des couples opposés). La boucle déclenche le même chemin re-dérivé, sans contournement du filtre d’opposition de l’ADR 0030 et de l’ADR 0029. Voir Ré-dérivabilité du mart et de l’index.

  • Boucle « dérive seule → retrain » (sans condition de donnée neuve). Écartée : emballement garanti — réentraîner sur les mêmes données ne dissipe pas la dérive, le sensor relancerait en continu. Un cooldown seul ne fait que ralentir la boucle, il ne la borne pas.
  • Garder la posture STOPPED (le déployeur arme). Écartée : c’est l’état de l’ADR 0062, qui ne ferme pas la boucle — l’autonomie demandée n’existe pas si le déclenchement reste un geste humain.
  • Déclencher depuis MLflow (lire le verdict journalisé). Écartée : MLflow est best-effort, non atomique, et un sensor (process séparé) ne lit pas fiablement son état. On persiste un verdict S3 dédié, à la manière du watermark.
  • Étendre la boucle à mediawatch. Écartée à ce stade : pas de signal de dérive (ni embeddings ni Evidently). À rouvrir si une mesure de dérive y apparaît.

Accepted (2026-06-26). Amende l’ADR 0062 sur le seul point du déclenchement par dérive : le CT par dérive passe de STOPPED à RUNNING (actif par défaut) ; le @schedule calendaire et le @sensor watermark de 0062 restent STOPPED, inchangés. Amende l’ADR 0031 sur ce seul point : l’autonomie du réentraînement est une capacité exercée par défaut (opt-out), là où 0031 posait « capacité fournie, décision au déployeur » (opt-in) — le reste de 0031 (neutralité, généricité, multi-tenant) demeure. S’appuie sur l’ADR 0030 (RGPD honoré par construction, aucun contournement du filtre d’opposition). Porte citation au MLOps niveau 2 autonome.

Bénéfices. La boucle MLOps est fermée : la dérive ne se contente plus d’informer, elle agit. Le « Continuous training autonome » passe de « pas encore appliqué » à appliqué pour citation. L’autonomie est réelle (active par défaut) tout en restant sûre (terminaison prouvée) et maîtrisable (kill-switch d’instance).

Prix à payer. Une rupture de posture assumée (« le déployeur arme » → « le déployeur désarme »), limitée à cette brique. Un garde-fou anti-emballement obligatoire (la boucle est conditionnée à la donnée neuve — une boucle « dérive seule » serait un bug). Un verdict S3 de plus à maintenir (signal de contrôle écrasable, pas une donnée).

Garde-fous. Terminaison prouvée (point fixe en une itération : le retrain n’avance pas le watermark). Déduplication par identifiant de verdict + cooldown d’instance. Opt-out CITATION_RETRAIN_AUTO=off = kill-switch immédiat, sans redéploiement de code. Observabilité : chaque réentraînement automatique est tracé (MLflow + lineage OpenLineage). RGPD : re-dérivation depuis curated filtré, aucun contournement de l’opposition. Réversibilité : git revert ; le verdict S3 est un signal écrasable, aucune donnée détruite.