Chapitres
Sur cette page
DOC-05 / Référence technique · Chapitre 05
Automates, crons & runs
Décrit la couche d'exécution non-conversationnelle de Synedre OS : les 209 façades Python, leur enrobage cron, le registre un composant interne et la frontière entre doctrine run et unité agent.
Automates, crons & runs
Cette page décrit la couche d'exécution non-conversationnelle de Synedre OS : les façades Python, leur enrobage cron, le registre des automates, le double système d'ordonnancement (scheduler Nitro + filet crontab Linux) et la frontière entre la doctrine run (unité de travail pilotée) et l'unité d'exécution agent (tâche déléguée). Public visé : ingénieur reprenant le harness.
Note de sécurité : le fichier crontab de production peut contenir des secrets en clair (mots de passe base de données, jetons d'authentification). Ils ne sont pas reproduits ici. Cette dette de hardening — secrets inline dans le crontab — est signalée en fin de page.
1. Modèle mental : agent (pense) vs automate (exécute)
Le harness sépare deux registres :
| Agent | Automate | |
|---|---|---|
| Rôle | Pense, décide, délègue (boucle ReAct) | Exécute une routine déterministe |
| Support | Persona LLM (Claude / Mistral) | Script Python dans le dépôt |
| Stockage | Table des agents (référentiel personas) | Registre des automates (table dédiée) |
| Déclenchement | Spawn orchestrateur, outil de tâche, unité de tâche agent | Cron, CLI, scheduler Nitro |
| Lien entre les deux | Une table de liaison relie chaque automate à son agent propriétaire ; un agent peut engendrer un automate ou une tâche déléguée. | |
Un automate ne « réfléchit » pas : il peut appeler un LLM (génération de contenu, classification) mais son flux de contrôle est codé en dur. La pensée vit dans les personas et l'orchestrateur Atlas ; l'exécution répétable vit dans les automates.
ALEX / EMAIL / CRON
│ trigger
▼
┌──────────────┐ délègue ┌──────────────┐ spawn ┌──────────────┐
│ Atlas (run) │ ─────────► │ tâche agent │ ───────► │ LLM CLI / │
│ (run piloté)│ │ (déléguée) │ │ script Python│
└──────────────┘ └──────────────┘ └──────────────┘
▲ │
│ cron/Nitro ────────┘
│ (automate planifié)
/hub/runs registre automates + logs automates
2. Les façades Python
Le dépôt contient plusieurs centaines de fichiers Python. Ce ne sont pas tous des automates planifiés : la majorité sont des façades (point d'entrée unique pour une capacité), certaines étant des bibliothèques partagées, des outils manuels ou des scripts à usage unique.
Le registre canonique de classification vit en base de données dans le registre des automates, pas dans les fichiers eux-mêmes. Deux axes structurent chaque entrée :
kind— nature technique :recurring— planifié (cron / Nitro)oneshot— déclenché à la demandetool— outil invoqué par un agent ou un skilllib— bibliothèque partagée, pas exécutable seulemeta— méta-outillage (ex. le wrapper cron lui-même)
caste— groupe d'agents propriétaire. Casernes observées : Vigies (audits/QA), Scribes (rédaction), Oracles (veille/reporting), Horlogers (infra/session/backup), Bâtisseurs (build/provision), Tisserands (SEO/maillage), plus des castes techniques (execution,library,veille). ⚠ Dette : la casse est incohérente entre entrées — normalisation à planifier.
Taxonomie fonctionnelle des familles de façades
| Famille | Rôle | Notes |
|---|---|---|
| Orchestrateur Atlas | Pipeline inbox → intention → spawn ; santé et monitoring de l'orchestrateur | |
| Audits | Détection de dérives (schéma, SEO, accessibilité, sécurité, cannibalisation de mots-clés) | Exit non-zéro = findings d'audit, pas un crash (voir SOFT_FAIL_SCRIPTS §3) |
| Sauvegardes | Dump base de données et fichiers vers stockage objet ; test de restauration mensuel | |
| Brainstorm | Jobs asynchrones de génération d'idées (worker boucle + filet cron ponctuel) | |
| Blog / SEO | Génération et hygiène de contenu, SEO technique | Le moteur de maillage interne est archivé, plus actif |
| Email / inbox | Lecture et écriture mail ; façade unique d'envoi client | Envoi client = façade only (pas d'accès SMTP direct depuis les agents) |
| Navigateur | Automatisation navigateur (worker headful résidentiel, captures d'écran, QA) | |
| Banque / facturation | Synchronisation bancaire, facturation récurrente, relances | |
| Veille marque | Surveillance marque, veille technologique, avis Google | Le moteur de newsletters est archivé, plus actif |
| Mémoire / apprentissage | Indexation vectorielle (RAG pgvector), consolidation, post-mortems, détection de patterns | |
| SRE / garde-fous | Surveillance coûts, détection de runaway, alerting, garde-fou écriture production | |
| Auto-maintenance documentation | Miroir doc↔code, couverture des angles morts, réparation déterministe des chemins morts, régénération profonde, publication vers synedre.com | Voir §7 |
| Autonomie | Seeder quotidien de chantiers en mode automatique ; amorce le pipeline de tâches agents | Voir §9 |
| Bibliothèques / infra | Briques partagées (connexion DB, logger, variables d'environnement, fournisseur IA, rotation de logs, wrapper cron) — non planifiées |
3. Le wrapper cron — watchdog d'enrobage
Tout automate planifié via crontab est lancé à travers un wrapper de supervision. Ce composant est le gardien de la stabilité de la couche cron : il protège chaque script contre les boucles d'erreur, tente des réparations automatiques et journalise chaque incident en base.
Le déclenchement typique ressemble à :
*/2 * * * * <chemin-wrapper> <nom-du-script> >> <fichier-log> 2>&1
Les responsabilités du wrapper sont les suivantes :
- Chargement des variables d'environnement : le wrapper lit les fichiers
.envet.env.hostau démarrage et injecte les variables dans l'environnement du sous-processus sans écraser les valeurs déjà présentes. - Disjoncteur : avant chaque lancement, le wrapper consulte le journal des erreurs en base. Si un script dépasse 10 échecs consécutifs, il est désactivé automatiquement (le wrapper s'arrête sans exécuter le script). Une seule ligne est loggée au moment du franchissement du seuil — anti-spam qui évite des centaines de lignes répétitives par jour.
- Exécution supervisée : le script est lancé en sous-processus avec un timeout par défaut de 300 secondes. Une liste de scripts à longue durée d'exécution (spawns LLM complexes, etc.) bénéficie d'un timeout étendu jusqu'à 1 800 secondes.
- Auto-réparation : en cas d'erreur, le wrapper analyse la sortie d'erreur standard, classe l'incident et tente un correctif automatique avant de relancer :
- Import manquant → injection de l'import depuis une liste connue
- Permission sur les logs → redirection vers un dossier accessible
- Dossier introuvable → création du dossier (sandboxé sous la racine du dépôt)
- Module Python absent → installation via
pipdans l'environnement virtuel - Conflit de nom de variable log → renommage automatique de l'alias conflictuel
- Erreur réseau → 3 tentatives avec backoff exponentiel (2 s, 5 s, 10 s)
- Soft-fail pour les audits : les scripts d'audit sont déclarés dans une liste spéciale. Pour eux, un code de sortie non-nul signifie des findings ont été détectés, pas un crash. Le compteur d'erreurs consécutives est remis à zéro et le code de sortie est propagé aux consommateurs en aval.
- Journalisation en base : chaque incident est enregistré dans la table de journal des erreurs cron (table append-only) avec les champs : type d'erreur, trace, indicateur de correction automatique, description du correctif appliqué, succès du retry, nombre d'échecs consécutifs, statut de désactivation.
Point de vigilance — absence de lock : le wrapper lui-même n'implémente pas de verrou exclusif (
flock). Quelques crons ajoutent un verrou à la main (notamment les workers brainstorm et certains extracteurs ponctuels). La majorité des entrées wrapper n'ont pas de protection contre le recouvrement : la sécurité repose uniquement sur le timeout et la fréquence de planification. C'est une dette d'architecture à traiter.
La connexion à la base de données utilisée par le wrapper passe par la façade de connexion partagée du dépôt, qui pointe vers la base principale du vaisseau-mère. Les constantes de connexion à une ancienne base MariaDB présentes dans le code sont du legacy mort (supprimé lors d'un chantier d'infrastructure antérieur) et peuvent être ignorées.
4. Le système de runs
4.1 Le run — unité d'exécution scopée
Un run désigne une exécution pilotée par l'orchestrateur central sur un périmètre défini (vaisseau-mère ou client). Au démarrage, le périmètre charge automatiquement son contexte depuis la base de données : infrastructure, interlocuteur, boîte de réception.
La table des runs comporte les colonnes suivantes : identifiant, source, déclencheur, périmètre, titre, statut, type et identifiant de référence, horodatages de début et de fin.
Deux déclencheurs sont actifs en production :
| Déclencheur | Source | Porte d'entrée |
|---|---|---|
email |
Boîte de réception Atlas | Transfert vers l'adresse Atlas de l'orchestrateur, classifié par intention (run, question, chantier, bruit) |
chat |
Console | Console scopée accessible depuis le cadran des runs (sélection de périmètre + conversation Atlas) |
Un troisième déclencheur planifié (cron) est documenté en doctrine mais n'est pas encore actif en base de données.
L'interface de pilotage des runs est accessible depuis le cadran principal de l'orchestrateur, qui expose la liste des runs et une vue détaillée par item.
4.2 La tâche d'exécution agent — unité déléguée
La tâche d'exécution agent n'est pas un run. C'est l'unité de travail déléguée à un agent par l'orchestrateur. Elle vit sous le cadran des tâches et sous les chantiers. Un run peut engendrer une tâche d'exécution agent lorsque l'orchestrateur délègue.
La table des tâches d'exécution (environ 209 lignes) comporte les colonnes clés suivantes : identifiant agent, titre, prompt, gabarit, périmètre, critères de sortie, statut, créateur, journal de sortie, code de sortie, tokens consommés, coût en USD, modèle utilisé.
Statuts observés :
- Terminé : 182 tâches
- Échoué : 23 tâches
- Annulé : 4 tâches
- États transitoires : en attente, en cours
Gabarits d'exécution : code (208 tâches), audit (1 tâche).
Origines de création observées : chaîne automatique de l'exécuteur de tâches (121), déclenchement manuel par opérateur (37), API de lancement automatique de chantier (29), chaîne automatique de travail (22).
Exécuteur : un worker planifié à la minute consomme les tâches en attente. Sa logique : il sélectionne les tâches dont le statut est en attente par ordre d'ancienneté, les marque en cours, lance le sous-processus selon le gabarit, diffuse la sortie standard vers le journal, puis marque terminé ou échoué.
Deux modes d'exécution :
- Mode simulation (défaut) : aucun spawn réel, placeholder enregistré.
- Mode live : spawn d'un processus d'inférence. Les permissions accordées varient selon le gabarit :
- Gabarit research : lecture seule (fichiers, recherche, historique de révisions).
- Gabarit audit : lecture + écriture du rapport.
- Gabarit code : permissions étendues, sandboxées au répertoire de périmètre déclaré.
4.3 Le journal d'exécution des automates
Le journal d'exécution des automates (environ 124 000 lignes, croissance continue, mode append-only) trace chaque exécution d'automate de façon verbeuse. Colonnes : nom de l'automate, environnement, résultat, durée en secondes, nombre d'étapes, nombre d'erreurs, nombre d'avertissements, compteurs, détail des étapes, erreurs, avertissements, détail du résultat, contexte.
Ce journal est distinct du registre des crashs du wrapper, qui ne consigne que les échecs du niveau enveloppe (wrapper) — non les exécutions nominales.
Relation entre les composants :
Run orchestrateur (scopé)
└─ engendre ─► Tâche d'exécution agent ──log──► journal de sortie (output_log)
Registre automates ──exécution planifiée──► Journal verbeux automates
└─► Registre crashs wrapper (crashs uniquement)
5. Ordonnancement : deux schedulers en parallèle
5.1 Scheduler applicatif Nitro (couche vaisseau-mère)
L'application vaisseau-mère expose un scheduler de tâches intégré au framework applicatif (fonctionnalité expérimentale activée dans la configuration). Les tâches de la famille audit sont définies dans un module dédié fusionné automatiquement par le framework — elles ne résident pas dans le répertoire de tâches racine.
Historique : ce scheduler a été inactif pendant environ trois jours en mai 2026, puis restauré dans le cadre d'un chantier de remise en service. Les tâches sont actives depuis lors, sous réserve qu'un rebuild du container soit déclenché après toute modification de la configuration (le container doit être reconstruit pour refléter les changements).
Tâches actives :
| Rôle | Fréquence cron | Guard |
|---|---|---|
| Traitement file d'attente e-mail | Toutes les 2 min | — |
| Surveillance de disponibilité | Toutes les 15 min | Environnement interne uniquement |
| Veille dictionnaire | Toutes les 30 min | Environnement interne uniquement |
| Veille dépendances | Quotidienne à 2h | Environnement interne uniquement |
| Synthèse quotidienne | Quotidienne à 8h | Environnement interne uniquement |
| Surveillance SSL | Quotidienne à 9h | Environnement interne uniquement |
| Veille marque | Quotidienne à 12h | Environnement interne uniquement |
Le guard environnement interne uniquement court-circuite la tâche si une variable d'activation n'est pas positionnée dans l'environnement du container (transmise via le fichier d'environnement de l'hôte).
Tâches toujours désactivées : la synchronisation de boîte de réception et la synchronisation e-mail client. Le client IMAP actuel monopolise la boucle événementielle Node et provoque des timeouts en cascade sur la base de données ; la réactivation est conditionnée à la livraison d'un client IMAP non-bloquant (chantier en cours, phase B).
Tâches présentes dans la codebase mais non planifiées intentionnellement : hygiène de blog, audit de page, veille sitemap, veille Synedre, vérification de faits, veille schéma.
5.2 Filet crontab système
Le crontab système du serveur porte l'essentiel de la charge d'ordonnancement et constitue un filet indépendant du scheduler applicatif. Sur 175 lignes totales (état au 7 juin 2026), 55 sont actives. Les lignes commentées portent des marqueurs d'archéologie (gel temporaire, désactivation explicite) — le crontab fait office de journal de bord opérationnel.
Familles actives :
- Via enveloppe de surveillance (wrapper) : monitoring (toutes les 2 min), sauvegarde (4h), audit des automates (3h), audit des sauvegardes (4h30), synchronisation bancaire (6h15), facturation récurrente (6h30), écoute de la boîte Atlas (toutes les min), orchestration de spawn (toutes les 5 min avec délai), synchronisation de boîte de réception (toutes les min), boucle nocturne de maintenance documentaire (4h), détection de dérive documentaire (toutes les 6h), correction automatique de documentation (toutes les 6h, +10 min), boucle de maintenance de nuit (5h), boucle d'autonomie (5h), synchronisation des embeddings (5h40).
Ces scripts bénéficient du logging centralisé des erreurs wrapper et du mécanisme d'auto-réparation. - Hors wrapper, modules Python : indexation mémoire (toutes les 15 min), worker de tâches d'exécution agent en mode live (toutes les min), indexation des compétences (toutes les 15 min), détecteur de blocages (toutes les 15 min), alertes SRE (toutes les 30 min), alertes de coût (toutes les 6 min), détecteur de dérive (toutes les 15 min), extraction d'événements de négociation avec verrouillage flock (toutes les 2 min), analyse de leads (7h30 et 19h30), rafraîchissement de KPIs (5h15), synchronisation des avis Google (1h20), traitement des revues documentaires externes (toutes les 30 min).
- Hors wrapper, scripts Python directs : publication documentaire avec limite (3h30), test de restauration de sauvegarde (5h le 1er du mois), rotation des logs (6h), métriques mémoire (6h), indexation de sessions (toutes les heures), surveillance de propositions de compétences (toutes les heures), détection de propositions de compétences (4h le dimanche), détection de patterns (3h le dimanche), synthèse quotidienne de réactions (3h30).
⚠️ Attention : ces scripts s'exécutent sans enveloppe de surveillance. Ils ne bénéficient pas du logging centralisé des erreurs wrapper ni du mécanisme d'auto-réparation.
- Worker de réflexion (brainstorm) : lancé au démarrage du serveur en mode boucle persistante (processus détaché) ; un filet toutes les 2 min relance le processus s'il est absent ; un filet toutes les 3 min avec verrouillage flock rattrape les exécutions ponctuelles manquées.
- Scripts shell et Node : scan de flotte (4h30), analyse des dépendances TypeScript (4h05), sauvegarde base de données principale vers stockage objet S3 (3h30), sauvegarde fichiers vers S3 (3h), sauvegardes des bases de données des VPS clients vers S3 à des horaires décalés — tous avec fallback de notification en cas d'échec. Test de restauration mensuel (1h le 1er du mois), synchronisation mémoire (toutes les 30 min), nettoyage des verrous de chantier (toutes les 5 min), chien de garde des verrous d'orchestrateur (toutes les 15 min).
- Appels directs HTTP : drain de la file d'attente mail toutes les minutes en POST vers l'API locale (double sécurité avec la tâche Nitro correspondante) ; synchronisation de rejeu de session toutes les 5 minutes.
⚠️ Dette P0 : une de ces lignes crontab expose un jeton d'authentification en clair dans la commande. Ce token doit être sorti de la ligne crontab et chargé depuis une variable d'environnement ou un fichier sécurisé — action corrective à planifier.
- Consolidation mémoire : script de consolidation mensuel (minuit le 1er du mois, avec verrouillage flock de 600 s).
- Quarantaine OSS : purge des paquets en quarantaine selon une fenêtre calendaire définie.
Scripts existants mais retirés du crontab (façades actives, non planifiées) : notification fondateur, veille sectorielle — aucune ligne active ni commentée dans l'état courant du crontab.
⚠️ Réconciliation registre ↔ ordonnancement (audit à produire) : le registre canonique des automates est la source de vérité de classification, mais aucun mapping automatisé ne relie aujourd'hui chaque automate de type récurrent à sa ligne crontab ou à sa tâche Nitro. Il est impossible de savoir combien d'automates sont orphelins (enregistrés comme récurrents mais non planifiés). Un audit est nécessaire : jointure entre le registre des automates (filtrés sur le type récurrent), les lignes actives du crontab système, et les tâches déclarées dans la configuration du scheduler applicatif.
Le browser-worker : automate distribué hors datacenter
Le sous-système de navigation web est le seul composant de la plateforme dont l'exécution sort physiquement du datacenter. Deux raisons distinctes justifient cette sortie, et donc deux topologies d'egress qu'il ne faut pas confondre.
| Topologie | Composant | Où tourne le navigateur | Ce qu'elle traite | Mode |
|---|---|---|---|---|
| Proxy résidentiel | Agent navigateur (côté VPS) | Navigateur headless sur le VPS du vaisseau-mère | La réputation IP | Headless + profil furtif |
| Worker headful distant | Worker navigateur (côté machine résidentielle) | Chrome headful sur la machine hôte résidentielle | Le fingerprint navigateur | Fenêtre visible, IP résidentielle directe |
6.1 Pourquoi un navigateur headful résidentiel plutôt qu'un navigateur headless sur VPS
Un tunnel proxy résidentiel fait sortir le trafic du VPS via une adresse IP résidentielle. Cela suffit pour les sites qui discriminent uniquement sur la réputation IP datacenter. Un mécanisme de garde vérifie avant chaque lancement que le proxy est actif et que l'adresse IP de sortie effective n'est pas celle du datacenter — toute sortie accidentelle par le datacenter est bloquée.
Cependant, certains systèmes de protection tiers (notamment les challenges gérés de type Turnstile) ne jugent pas l'IP : ils analysent le fingerprint du navigateur — signaux JavaScript révélant l'automatisation (navigator.webdriver, absence de plugins, marqueurs headless, WebRTC, etc.). Un navigateur headless conserve un fingerprint de bot même derrière une IP résidentielle propre.
Conséquence doctrinale : classifier le type de protection avant de coder le flow. Si la protection cible le fingerprint navigateur, un navigateur headful sur machine résidentielle est obligatoire ; le mode headless furtif ne passe pas.
D'où le worker headful : un vrai Chrome (fenêtre visible, mode non-headless) tourne en permanence sur une machine hôte résidentielle toujours allumée, avec IP résidentielle directe. Aucun proxy n'est nécessaire côté worker : on est déjà sur la bonne IP, et les challenges de type Turnstile passent naturellement.
6.2 File de jobs et cycle de vie d'un job navigateur
La file de travaux navigateur est stockée en base de données selon le même pattern enqueue / claim / finish / get que les autres workers de la plateforme. Chaque entrée porte : un identifiant de job, un type (kind), une charge utile JSON, un statut (queued, running, done, failed), un résultat JSON, un message d'erreur, l'identifiant de la machine exécutante, le nombre de tentatives, et les horodatages de début et fin. Des index garantissent la performance du claim ordonné.
L'inversion de contrôle est le principe central : le VPS du vaisseau-mère n'a aucun accès entrant vers la machine résidentielle (NAT, IP dynamique). C'est le worker distant qui interroge activement le VPS par connexion sortante. Le cycle complet est le suivant :
- Enqueue (VPS) — Un agent ou un skill dépose un job dans la file. Seuls les types de job figurant dans une liste blanche stricte sont acceptés ; la charge utile JSON est validée avant insertion. L'injection SQL est prévenue par un mécanisme de dollar-quoting aléatoire.
- Claim atomique (worker → VPS) — Toutes les 5 secondes, le worker interroge le VPS via SSH sortant pour réclamer le job le plus ancien en statut
queued. Le claim utilise un verrou transactionnel (FOR UPDATE SKIP LOCKED) : deux workers concurrents ne peuvent pas s'approprier le même job. - Dispatch (worker, headful) — Le worker route le job vers le handler métier correspondant selon son type. La charge utile ne contient que des paramètres métier (limites, quantités) ; aucune évaluation dynamique de code n'est effectuée.
- Récupération de code 2FA (worker → VPS → worker) — Pour certains flows nécessitant une authentification à deux facteurs, le code n'arrive jamais directement sur la machine résidentielle. Le worker rappelle le VPS via SSH pour que celui-ci lise le code depuis la boîte mail côté serveur et le retourne en clair sur la sortie standard. Le code 2FA n'est jamais écrit dans les journaux.
- Finish (worker → VPS) — Une fois le job terminé, le worker transmet le résultat et l'éventuelle erreur en un unique objet JSON passé sur l'entrée standard de la commande SSH (jamais en argument shell, pour éviter les ruptures sur caractères spéciaux). Le VPS met à jour le statut, le résultat et l'horodatage de fin.
- Auto-mise à jour du worker — En mode boucle, lorsque le worker est inactif (jamais en cours de job), il vérifie périodiquement si le dépôt a évolué. En cas de mise à jour, il se redémarre automatiquement via remplacement de processus (
os.execv), garantissant que la machine résidentielle tourne toujours sur le code le plus récent sans intervention manuelle.
6.3 Le portail de commande SSH (défense en profondeur si la clé venait à fuiter)
La clé SSH de la machine résidentielle est enregistrée côté VPS avec une restriction stricte : elle n'ouvre pas un shell libre. À la place, chaque connexion SSH est interceptée par un portail de commande dédié, configuré directement dans le fichier d'autorisation SSH.
Ce portail lit la commande SSH demandée, la découpe en tokens de façon sûre (sans jamais passer par un interpréteur shell), puis n'autorise l'exécution que si :
- le préfixe de la commande correspond exactement au module de gestion de la file de jobs ;
- le sous-commande appartient à l'ensemble autorisé (
--claim,--finish,--get,--enqueue, récupération de code 2FA) ; - chaque valeur de paramètre correspond à un motif strict prédéfini (identifiant numérique, statut
done|failed, type de job alphanumérique borné, etc.).
Tout token inconnu ou valeur non conforme entraîne un refus immédiat avec journalisation. Le worker ne préfixe jamais ses commandes SSH d'un changement de répertoire : le portail gère lui-même le répertoire de travail. Dans le pire cas d'une clé compromise, l'attaquant peut au plus polluer la file de jobs — il ne peut pas exécuter de code arbitraire sur le VPS.
Données personnelles et captures d'écran. Les résultats de certains jobs (noms, extraits de messages) sont stockés dans la base de données privée en JSON structuré et ne sont jamais écrits en clair dans les journaux — seul un compteur est tracé. Les captures d'écran de débogage sont purgées en fin de run par le module métier concerné, pas par le worker lui-même. Un mode conservation des captures existe mais est réservé au débogage local. Le rapatriement structuré des captures fait l'objet d'un chantier en cours.
⚠️ Bug latent connu : le délai d'expiration des jobs navigateur n'est actuellement pas appliqué — un job dont le navigateur se bloque peut rester en statut
runningindéfiniment. Un correctif est à confirmer.
Boucle d'auto-maintenance de la documentation
Depuis un chantier récent, le harness dispose d'un sous-système cohérent qui mesure l'écart entre sa propre documentation et son code, puis le corrige de façon autonome avec des portes de validation (gates). Ce sous-système constitue la couche d'automates la plus récente et la plus intriquée.
Architecture du pipeline
La boucle nocturne s'articule autour d'un orchestrateur central qui se déclenche chaque matin à 5 h et enchaîne huit étapes dans l'ordre :
- Percevoir — le détecteur de dérive inspecte l'écart entre la documentation et le code.
- Couvrir — le détecteur d'angles morts vérifie la complétude de la documentation.
- Réparer — le correcteur déterministe résout les références mortes.
- Soigner — le bilan de santé agrège toutes les dimensions.
- Régénérer — le régénérateur profond réécrit en profondeur (avec gate).
- Proposer — le module de staging prépare les chapitres et produit un brief.
- Publier-doc — le module de publication valide et active les chapitres.
- Publier — le module de synchronisation publique pousse un snapshot assaini vers le site public.
Par ailleurs, le détecteur de dérive et le correcteur mécanique tournent chacun sur leur propre rythme indépendant (toutes les six heures), sans dépendre de l'orchestrateur. Un module de revue externe s'exécute quant à lui toutes les trente minutes.
Le détecteur de dérive (Phase 0 — percevoir)
Ce composant s'exécute toutes les six heures en mode lecture seule stricte : il n'écrit jamais le code ni la documentation, mais enregistre uniquement ses observations dans la base de données.
Il mesure trois types de dérive :
- Doc périmée — le fichier source référencé a été modifié après la rédaction du document : la documentation décrit un corps obsolète.
- Référence morte — le document cite un fichier qui n'existe plus dans le dépôt.
- Publication en retard — le chapitre public diffère du document interne (divergence de contenu détectée par empreinte).
Le composant est idempotent : il ne produit qu'une seule observation par chapitre et par jour. Il retourne un signal d'absence de dérive ou un signal de dérive détectée.
Le détecteur d'angles morts (Phase 0bis — couverture)
Lancé juste après le détecteur de dérive, ce composant vérifie la complétude de la documentation — là où le détecteur de dérive vérifie la fidélité. La question posée est : « Existe-t-il une partie du harness que la documentation ne mentionne jamais ? »
La méthode repose sur une différence d'ensembles :
- Corps documentable — l'ensemble des automates et des structures de données du harness.
- Couverture actuelle — ceux mentionnés dans au moins un chapitre de documentation interne.
- Angles morts — la différence : composants présents mais jamais documentés, regroupés par famille.
Un orphelin isolé n'est pas préoccupant. En revanche, plusieurs orphelins d'une même famille constituent un candidat à une nouvelle section ou un nouveau chapitre, créé par le régénérateur profond en mode création. Ce composant est lui aussi lecture seule et sa dimension « couverture » est consolidée dans le bilan de santé.
Le correcteur déterministe de références mortes (Phase 1bis — réparer)
Ce composant comble le trou entre le détecteur de dérive — qui diagnostique les références mortes — et le régénérateur LLM — qui corrige la prose mais ignore qu'un fichier a été déplacé. La résolution est déterministe, sans hallucination possible.
Pour chaque référence morte trouvée dans la documentation interne, le composant recherche le vrai emplacement par le nom de base du fichier :
- Un seul résultat (certitude) → la référence est corrigée en place.
- Aucun résultat (composant supprimé) → signalement, rapport, notification au fondateur pour avis humain.
- Plusieurs résultats (ambiguïté) → signalement et rapport, jamais de réécriture automatique.
Garde-fous : ce composant ne touche qu'aux fichiers de documentation interne, jamais au code ni aux chapitres publiés. Il est idempotent et produit un commit unique et réversible par run.
Distinction importante : ce correcteur traite uniquement les chemins de façon déterministe et est déclenché par l'orchestrateur. Le correcteur mécanique décrit ci-après tente d'autres types de corrections structurelles et tourne sur son propre rythme indépendant.
Le bilan de santé (Phase 2 — soigner)
Ce composant agrège quatre dimensions sans en modifier aucune :
| Dimension | Ce qui est mesuré |
|---|---|
| Proprioception | Écart documentation ↔ code (drift détecté par le détecteur de dérive) |
| Dette | Backlog de tâches prioritaires ouvertes |
| Apprentissage | Signal issu des cicatrices accumulées |
| Automates | Santé des crons (erreurs détectées) |
Le bilan produit un snapshot idempotent par dimension et par jour, ainsi qu'un item pour le compte-rendu quotidien. Il retourne un signal : OK, avertissement, ou état critique.
Le correcteur mécanique (indépendant de l'orchestrateur)
Ce composant tourne toutes les six heures sur son propre rythme, décalé de dix minutes par rapport au détecteur de dérive. Il ne touche qu'aux fichiers de documentation interne — jamais au code, jamais aux chapitres publiés.
Règle de sûreté : une référence morte n'est corrigée que si son nom de base correspond à exactement un fichier suivi par le dépôt. Zéro candidat (composant supprimé) ou deux candidats ou plus (ambiguïté) → proposition soumise à validation humaine, sans aucune écriture automatique.
Le composant est réversible (un seul commit par run, annulable en une opération), plafonné par un maximum configurable, et doté d'un coupe-circuit global. Il fonctionne en mode simulation par défaut ; le mode actif doit être explicitement demandé.
Le régénérateur profond (Phase 4 — régénérer)
Ce composant est appelé par l'orchestrateur avec un budget-temps maximum (défaut : 4 500 secondes). Il lance un processus Claude en mode sans interface qui : (a) réécrit le document interne en lisant le code réel, (b) produit directement la version publique bilingue FR+EN avec ses métadonnées dans un espace temporaire.
Le document interne est commité, puis une porte anti-fuite (vérification regex multi-couches) est appliquée avant tout staging public. Cette porte est indépendante de l'orchestrateur.
Le module de staging et de brief (Phase 5 — proposer)
Lancé par l'orchestrateur après la régénération profonde, ce composant re-stage les chapitres dont le document interne devance la version publiée, déclenche la publication unitaire, et place les chapitres assainis et bilingues dans la file d'attente de publication. Une porte anti-fuite indépendante bloque tout chapitre contenant une fuite détectée. Le composant produit également un brief de régénération pour les cas ambigus nécessitant une attention humaine.
L'orchestrateur nocturne (cron 5 h)
L'orchestrateur déclenche les huit étapes dans l'ordre, sous plusieurs garde-fous globaux :
- Verrou anti-recouvrement — une seule instance à la fois.
- Coupe-circuit base de données — si le kill-switch est activé pour cet automate, le tick s'arrête immédiatement.
- Budget-temps configurable pour le régénérateur profond ; un timeout externe plus large englobe l'ensemble.
- Isolation des étapes — si une étape échoue, le tick continue et note l'échec.
En fin de run, l'orchestrateur appelle le module de synchronisation publique pour pousser un snapshot assaini vers le site public.
Invocation typique (avec simulation ou sans régénération profonde) :
python3 [orchestrateur] [--dry-run] [--no-deep] [--deep-budget-sec N]
La synchronisation publique
Appelé en fin de tick, ce composant lit les données de la boucle en lecture seule, les assainit (zéro nom de client, adresse IP, secret, chemin interne), puis pousse un snapshot JSON vers la base de données du site public par upsert idempotent. Ce snapshot alimente le cadran de maintenance accessible depuis la section documentation du site.
La publication des chapitres vers le site public
Ce composant est invoqué selon deux chemins :
- Planification autonome chaque nuit à 3 h 30, avec activation automatique.
- Invocation par l'orchestrateur à l'étape « publier-doc », avec les mêmes paramètres — rendant l'orchestrateur auto-suffisant sans dépendre du cron séparé.
Le composant détecte les chapitres dont l'empreinte du document interne diverge de celle du chapitre publié, les re-stage dans la file d'attente de publication (transport sécurisé vers la base du site public), avec une pré-vérification anti-fuite regex multi-couches.
Logique d'activation : un geste humain délibéré (--activate) ou l'activation automatique (--auto-activate) peut publier un chapitre en attente. En mode automatique, les chapitres passant tous les gates machine — y compris la vérification anti-gourou définie dans le registre public §VII — sont publiés sans intervention humaine. Les chapitres refusés restent en file d'attente et génèrent une notification au fondateur.
Le regard des autres (revue externe — toutes les 30 min)
Ce composant s'exécute toutes les trente minutes indépendamment de l'orchestrateur. Il dépile les revues soumises par un opérateur humain (réponse d'un modèle externe), les évalue via un processus Claude sandboxé avec délimiteurs anti-injection, puis parse sélectivement le verdict : validité globale et détection de tentative d'injection.
Si la revue est valide, le composant émet un signal uniquement (note dans le compte-rendu quotidien) sans déclencher de régénération en ligne. La régénération est consommée lors du prochain cycle de l'orchestrateur, selon un couplage lâche qui évite les timeouts et la perte de calcul.
Le tick d'autonomie — amorçage du pipeline
Ce composant s'exécute quotidiennement à 5 h (simultanément mais indépendamment de l'orchestrateur de documentation). Sa doctrine : simulation par défaut — il observe sans rien semer ; le mode actif doit être explicitement demandé.
Son rôle est de combler le cold-start du pipeline d'autonomie. Le moteur de tâches se chaîne lui-même à l'intérieur d'un travail en cours, mais rien n'amorce le pipeline : un travail en mode automatique dont toutes les tâches sont en attente et dont aucun run n'est actif reste dormant. Ce tick crée un run en attente pour la prochaine tâche éligible de chaque travail actif sans run en cours.
Garde-fous :
- Plafond de coût — si le coût cumulé d'un chantier atteint 100 % de son budget, il est gelé et aucun run n'est semé.
- Double coupe-circuit — deux variables d'environnement permettent d'interrompre totalement le composant sans aucune action.
- Déploiement — signalé uniquement, jamais exécuté dans le tick ; le déploiement en production reste un geste humain délibéré.
Règle constitutionnelle : le tick peut aller jusqu'à la préproduction (
./deploy) mais n'appelle jamais la mise en production (./ship). La décision de mise en production reste un geste humain explicite.
Garde-fous & opérations
Cette section recense les mécanismes de protection, de surveillance et de maintenance opérationnelle qui encadrent l'exécution des automates sur le vaisseau-mère.
Mécanismes de protection actifs
- Disjoncteur automatique : un composant de supervision enveloppe chaque automate planifié. Après dix crashs consécutifs, il désactive automatiquement l'automate concerné et consigne l'état dans la table de suivi des erreurs de planification, évitant ainsi toute boucle d'échec infinie.
- Audit quotidien : chaque nuit, un automate d'audit relit les journaux d'erreurs de planification ainsi que les journaux d'exécution des automates afin de détecter toute anomalie persistante.
- Surveillance des coûts et des emballements : trois automates de garde tournent en continu — l'un toutes les six heures pour alerter sur les dérives de coût, un autre toutes les quinze minutes pour détecter les exécutions en emballement, un troisième toutes les trente minutes pour les alertes de fiabilité de service.
- Sauvegardes et test de restauration : des dumps nightly sont poussés vers le stockage objet distant (base de données du vaisseau-mère, fichiers, bases distantes des clients). Un automate mensuel rejoue un test de restauration complet pour valider la cohérence des sauvegardes.
- Déblocage automatique : toutes les quinze minutes, un automate détecte les exécutions de tâches bloquées et les soumet à nouveau, dans la limite de quatre tentatives. Un interrupteur d'urgence permet de désactiver ce mécanisme sans modifier le planificateur.
- Boucle de maintenance documentaire : un ensemble de trois automates assure la surveillance de la dérive documentaire (toutes les six heures), la correction automatique des anomalies détectées (décalé de dix minutes sur le même cycle), et un cadran de maintenance nocturne (chaque jour à 5 h). Ces mécanismes sont décrits en détail dans la section dédiée à la régénération de la documentation.
Dette technique signalée
- ✅ Automate d'audit supprimé sans remplaçant (résolu le 2026-06-06) : un automate d'audit de fuites d'URL avait été retiré du dépôt sans qu'un successeur soit mis en place. La ligne de planification correspondante a été supprimée du planificateur. Seul subsiste un dossier de journaux périmés. Aucune façade d'audit de fuite d'URL n'existe à ce jour ; elle devra être recréée via l'enveloppe de planification standard si le besoin réapparaît.
-
⚠️ Jeton d'authentification en clair dans le planificateur : une entrée du planificateur embarque un bearer token directement dans la ligne de commande, sans passer par le fichier d'environnement sécurisé. La migration vers une lecture par variable d'environnement est à réaliser — le patron propre est déjà appliqué sur les autres entrées du planificateur (lecture du mot de passe base de données depuis le fichier
.env). -
⚠️ Double exécution de la file d'e-mails : deux entrées de planification déclenchent indépendamment le traitement de la file d'envoi d'e-mails chaque minute. Le mécanisme est idempotent grâce à une clause
LIMITcôté file, mais la redondance reste à nettoyer pour éviter toute confusion opérationnelle. -
⚠️ Incohérence de casse sur le champ de classification des automates : le champ désignant le type d'exécution présente une capitalisation non uniforme (
executionvsExecution) dans la table des automates. Une normalisation est à planifier. - ⚠️ Absence de verrou générique dans l'enveloppe de planification : l'enveloppe qui encapsule les automates planifiés ne pose pas de verrou système par défaut. Des recouvrements d'exécution restent possibles sur les jobs lents qui ne gèrent pas eux-mêmes leur exclusion mutuelle.
- ⚠️ Automate d'autonomie toujours en mode observation : l'automate de seeding autonome tourne actuellement en mode dry-run — il observe et journalise sans effectuer d'actions réelles. Le mode actif n'est pas encore câblé dans le planificateur. Par ailleurs, un délai de garde-fou sur les jobs navigateur est défini dans le code mais non appliqué à l'exécution : un job navigateur peut théoriquement rester suspendu indéfiniment. Un correctif est à confirmer.