Architecture d'intelligence évolutive
Phase 21.5–40.12 — Système de skills auto-améliorant pour Arc OS. Statut : Prêt pour la production. Sage Worker (amélioration autonome) + benchmarks (tests A/B) + Marketplace Discovery + recherche sémantique NotebookLM.
Vue d'ensemble
Arc OS opère une architecture de bots fédérés où les child bots proxient les messages utilisateur vers Claude CLI. La Phase 21.1 a introduit le suivi qualité (logs d'exécution, boutons de feedback). La Phase 21.5 boucle la boucle : le système valide désormais ses propres sorties, mémorise les corrections, concentre le contexte et propose des améliorations de manière autonome.
Sept piliers forment la couche intelligence :
Message utilisateur
│
├─► Context Router ──► SKILLS_HINT (top 5 skills pertinents)
│
├─► Learnings ──► bloc LEARNINGS (corrections passées)
│
▼
buildGsdPrompt() ──► Claude CLI ──► Réponse
│
├─► Binary Evals ──► Notes de bas de page d'avertissement
│
└─► Quality Tracker ──► Métriques
│
Nightly Improve ──► Approbation CEO
Pilier 1 : Moteur d'évaluation binaire
Module : shared/evals.ts
Fichiers de spec : skills/<name>/<name>.evals.json
Ce qu'il fait
Chaque skill peut déclarer des règles de validation dans un fichier JSON. Après que Claude génère une réponse, le moteur d'évaluation la vérifie par rapport à toutes les règles applicables. Les échecs produisent des notes de bas de page d'avertissement non bloquantes ajoutées au message Telegram.
Types de règles
| Type | Vérifie | Exemple d'usage |
|---|---|---|
string_contains |
La réponse inclut une chaîne littérale | Vérifier que la sortie JSON a un champ "verdict" |
string_not_contains |
La réponse n'inclut PAS une chaîne | Bloquer --force dans les instructions git |
regex_match |
La réponse correspond à un pattern regex | S'assurer que l'audit système mentionne disque/RAM/CPU |
regex_not_match |
La réponse ne correspond PAS à un regex | Prévenir les patterns de fuite de credentials |
max_length |
Longueur de réponse <= N chars | Garder la sortie concise |
min_length |
Longueur de réponse >= N chars | Garantir des réponses substantielles |
Niveaux de sévérité
- warning : Affiché comme
⚠️en note de bas de page. Indique un problème de qualité. - info : Affiché comme
ℹ️en note de bas de page. Consultatif, non critique.
Impact des évaluations sur les métriques
Les résultats des évaluations sont enregistrés avec les événements qualité. Quand la boucle d'amélioration nocturne détecte un skill avec un faible taux de succès, les patterns d'échec d'évaluation aident à identifier la cause racine sans nécessiter d'analyse IA.
Exemple de fichier d'évaluation
{
"version": 1,
"skill": "code-review",
"rules": [
{ "id": "cr-001", "name": "Must return JSON verdict", "type": "string_contains", "value": "\"verdict\"", "severity": "warning" },
{ "id": "cr-002", "name": "No console.log debug", "type": "regex_not_match", "pattern": "console\\.log\\(", "severity": "warning" }
]
}
Principe de conception
Inspiré du framework d'évaluation Skill Creator d'Anthropic : assertions déclaratives sur les sorties, sans IA dans la boucle pour la validation. Le binaire pass/fail élimine l'ambiguïté.
Pilier 2 : Context Router
Module : shared/context-router.ts
Source de données : skills/_registry.json (champs triggers + keywords)
Ce qu'il fait
Avant de construire le prompt GSD, le routeur évalue chaque skill enregistré par rapport au message de l'utilisateur. Les 5 meilleures correspondances sont injectées comme bloc SKILLS_HINT, préparant Claude à se concentrer sur les capacités pertinentes.
Algorithme de scoring
Pour chaque skill :
score = 0
pour chaque trigger : si le message contient le trigger → score += 2
pour chaque keyword : si le message contient le keyword → score += 1
Trier par score DESC → prendre les 5 premiers
Les correspondances de trigger ont un score plus élevé car les triggers sont des signaux d'invocation explicites (ex. "review", "deploy"). Les keywords fournissent une correspondance sémantique plus large (ex. "OWASP", "Docker").
Pourquoi consultatif, pas filtrant
Le routeur est uniquement consultatif. Claude CLI charge toujours tous les skills via CLAUDE.md. Raisons :
- Sécurité : Le filtrage dur via
--allowedToolsou les mutations de symlinks risque de casser mi-session si le routeur classe mal un message. - Stabilité : Pas de mutations système de fichiers sur un processus en cours.
- Dégradation gracieuse : Si le routeur échoue, Claude a toujours accès à tous les skills.
Économie de fenêtre de contexte
Sans le routeur, les 23 skills se disputent l'attention dans le prompt. Le bloc SKILLS_HINT dit à Claude "concentre-toi sur ceux-ci 3-5" — réduisant l'activation de skills non pertinents et gardant les réponses sur le sujet. C'est du priming de contexte, pas une réduction de contexte.
Pilier 3 : Reflect Loop (Learnings persistants)
Module : shared/learnings.ts
Stockage : {PROJECT_CWD}/learnings.md
Ce qu'il fait
Quand le CEO appuie sur "Fix It" ou "👎", le système capture une règle d'apprentissage et la persiste dans un fichier markdown. À chaque message suivant, les learnings accumulés sont injectés dans le prompt GSD comme bloc LEARNINGS.
Pipeline Événement → Apprentissage
CEO appuie sur 🛠️ Fix It
│
├─► addLearning(source: "fixit", rule: "Fix requested for: <context>")
│
└─► projectLearnings rechargé depuis le disque
CEO appuie sur 👎
│
├─► addLearning(source: "negative", rule: "Negative feedback on: <context>")
│
└─► qualityTracker.logFeedback(positive: false)
Format du fichier learnings
# Learnings
> Auto-généré. Injecté dans le prompt GSD au début de la session.
## Règles
- [2026-04-03T14:22:00Z] [fixit] Always use t-call for translations in Odoo QWeb
- [2026-04-03T15:10:00Z] [negative] Avoid sudo in deployment scripts
Injection dans le prompt
La fonction formatForPrompt() prend les learnings les plus récents (jusqu'à 2000 chars), les inverse (les plus récents en premier) et les formate ainsi :
LEARNINGS (corrections passées — suivre ces règles) :
- Avoid sudo in deployment scripts
- Always use t-call for translations in Odoo QWeb
Principe de conception
Inspiré du Reflect System de Claude : les corrections ne corrigent pas seulement la réponse actuelle — elles deviennent des règles persistantes qui préviennent les régressions. Le système construit une "mémoire immunitaire" au fil du temps.
Pilier 4 : Karpathy Loop (Auto-amélioration nocturne)
Module : scripts/nightly-improve.ts
Planning : Quotidien à 03:00 UTC via cron
État : mcp-server/state/improvement-proposals.json
Ce qu'il fait
- Lit
config/bot_registry.jsonpour énumérer tous les child bots - Pour chaque enfant : lit
quality-metrics.jsondepuis son répertoire d'état - Appelle
findUnderperformingSkills()— filtre les skills où :applied_count >= 3(taille minimale d'échantillon)- ET soit
success_rate < 80%SOITfeedback_negative > feedback_positive
- Lit
learnings.mdpour les patterns de correction liés - Génère des propositions basées sur des templates (déterministe, sans IA)
- Envoie un rapport résumé au Telegram du CEO
- Envoie des cartes de proposition individuelles avec des boutons inline Approve/Reject
Flux d'approbation CEO
Script nocturne ──► Telegram : Carte de proposition
│
┌────────────┼────────────┐
▼ ▼
✅ Approuver ❌ Rejeter
│ │
Backup skill.md Marquer rejeté
comme skill.v1.md dans proposals.json
(max 3 versions)
│
Marquer approuvé
dans proposals.json
Versioning des skills
À l'approbation, le master bot crée des sauvegardes versionnées :
skill.md→skill.v1.md(actuel → sauvegarde)skill.v1.md→skill.v2.md(rotation)- Maximum 3 versions de sauvegarde par skill
Principe de conception
Inspiré de la boucle AutoResearch de Karpathy : modifier → vérifier → garder/abandonner → répéter. La différence critique : les propositions sont basées sur des templates et requièrent une approbation humaine. Pas de réécriture autonome de skills — le CEO reste l'autorité finale.
Phase 36+ — Couche sémantique NotebookLM
La Phase 36.3 a ajouté un cinquième canal de feedback : Google NotebookLM comme mémoire sémantique long terme.
Événements CRM (fermeture ticket, mise à jour wiki)
│
└─► POST fire-and-forget vers bridge /sync
│
▼
NotebookLM Bridge (:19213)
├── SyncWorker (asyncio.Queue, 3 essais)
└── notebooklm-py → Google NotebookLM
│
▼
Notebook mis à jour avec nouvelle source
│
┌─────────────────────┤
▼ ▼
outil ask_notebooklm Neural Skill Generator
(chat Cloud PM, 15s) (POST /skills/generate, 30s)
│ │
▼ ▼
Réponse sémantique en Skill Markdown pour
contexte projet revue + sauvegarde (Phase 36.6)
La Phase 36.6 a ajouté le Neural Skill Generator : le CEO définit un objectif d'extraction, le bridge interroge NotebookLM avec un prompt structuré d'AI Architect, retourne un Rulebook Markdown strict qui peut être sauvegardé comme skill.
La Phase 36.7 a exposé les notebooks dans l'UI sidebar (point de statut vert/rouge, nombre de sources, lien externe).
Pilier 6 : Sage Worker (Phase 40.11c)
Module : shared/sage.ts
Déclencheur : POST /api/crm/sage/analyze ou scripts/nightly-improve.ts
Ce qu'il fait
Sage est un moteur autonome d'amélioration de skills. Il analyse les skills en utilisant les métriques qualité + le feedback utilisateur (learnings), puis génère du contenu amélioré concret via Anthropic API (modèle Haiku pour la rentabilité). Les résultats sont stockés comme skill_update_requests (PRs) dans la base de données pour revue CEO.
Flux
Requête Sage Analyze
│
├─► Charger les skills actifs depuis DB (skillQueries)
├─► Croiser avec les métriques qualité (readMetrics)
├─► Prioriser les sous-performants (findUnderperformingSkills)
│
▼ Pour chaque skill cible (max 5) :
├─► Charger contenu skill + fork (si project-scoped)
├─► Charger métriques qualité (taux succès, comptes feedback)
├─► Charger learnings liés (corrections utilisateur)
├─► Passer si PR en attente existe déjà
├─► Construire le prompt (contenu + métriques + learnings)
├─► Appeler Anthropic API (Haiku, timeout 30s)
├─► Parser la réponse (NO_CHANGE = passer)
├─► Insérer skill_update_request (proposed_by: 'sage')
└─► Lancer benchmark automatique (Pilier 7) sur nouvelle PR
Pourquoi un endpoint CRM, pas un worker
Sage a besoin d'un accès direct à la DB (skillQueries, benchmarkQueries) — les workers s'exécutent comme sous-processus sans connexion DB. Sage ne reçoit pas de messages utilisateur ; c'est un analyseur d'arrière-plan dont la sortie = lignes DB, pas du texte de chat.
Pilier 7 : Approbations basées sur les données — Benchmarks (Phase 40.11d)
Module : shared/sage.ts (runBenchmark())
DB : shared/migrations/005_skill_benchmarks.ts
API : POST /api/crm/sage/benchmark, GET /api/crm/skill-updates/:id/benchmarks
Ce qu'il fait
Avant que le CEO approuve une PR Sage, le système prouve que le changement fonctionne. Génère 3 scénarios de test, exécute l'ancien et le nouveau contenu de skill à travers eux (test A/B à l'aveugle), et utilise un juge LLM pour déterminer lequel est meilleur. Score combiné : règles d'évaluation déterministes (60%) + juge LLM (40%).
Flux de benchmark
Lancer Benchmark (requestId)
│
├─► Charger skill_update_request (current_content + proposed_content)
├─► Charger eval_rules depuis DB
│
▼ Générer 3 scénarios de test (Haiku)
│
▼ Pour chaque scénario :
├─► Exécuter ancien contenu skill → old_output
├─► Exécuter nouveau contenu skill → new_output
├─► Randomiser l'ordre A/B (prévention du biais de position)
├─► Juge LLM évalue les deux (1-10) + raison
├─► Score déterministe eval_rules (si disponible)
├─► Score combiné : eval 60% + LLM 40%
└─► Sauvegarder dans la table skill_benchmarks
│
▼ Mettre à jour les métadonnées PR
├─► verdict : PASSED / FAILED / TIE
├─► improvement_pct : ((new_avg - old_avg) / old_avg * 100)
└─► BenchmarkBadge visible sur l'en-tête PR frontend
Frontend : Mode Battle
SkillEvolution.jsx → composant BenchmarkReport :
- Chargement paresseux par PR (récupération à l'expansion)
- Résumé : victoires/défaites/égalités/verdict/improvement_pct
- Par scénario : Ancien vs Nouveau côte à côte, étoile gagnant, score/10, raison du jugement
- Bouton "Run Benchmark" sur l'onglet Content (bleu ciel)
- Bouton d'ancrage par PR pour relancer les benchmarks
Carte des fichiers
shared/
├── evals.ts ← Pilier 1 : Moteur d'évaluation binaire
├── context-router.ts ← Pilier 2 : Context Router (routeContextFromDb)
├── learnings.ts ← Pilier 3 : Reflect Loop
├── quality.ts ← Étendu : findUnderperformingSkills()
├── sage.ts ← Piliers 6+7 : Sage Worker + Benchmarks (runSageAnalysis, runBenchmark)
├── db.ts ← SQLite SSOT : skillQueries, benchmarkQueries, chatQueries
├── migrations/
│ ├── 004_skill_system.ts ← skills_global, forks, evolution_logs, update_requests
│ └── 005_skill_benchmarks.ts ← skill_benchmarks (résultats tests A/B)
├── crm-routes.ts ← CRM API : 55+ endpoints (Sage + benchmarks + évolution skill)
└── ui_templates.ts ← Étendu : improvementProposal()
scripts/
├── nightly-improve.ts ← Pilier 4 : Karpathy Loop (utilise maintenant Sage + DB)
└── migrate-skills-to-db.ts ← Migration nucléaire : fichiers → DB
clients/
├── arc-cli.ts ← ARC CLI (Phase 31.5) : login, start, projects
└── knowledge-mcp.ts ← Déprécié (Phase 38 → sous-commandes CLI)
services/
└── notebooklm-bridge/ ← Pilier 5 : Recherche sémantique NotebookLM (Phase 36.3)
├── main.py ← FastAPI (:19213), /query, /sync, /notebooks/init, /health
└── seed_knowledge.py ← Import bulk des connaissances existantes
frontend/src/crm/pages/
└── SkillEvolution.jsx ← UI deux panneaux : explorateur + détail (Content/Evals/Evolution/PRs)
BenchmarkBadge, BenchmarkReport, Mode Battle, bouton analyze Sage
child-bot/bot.ts ← Intégration : démarrage + prompt GSD + notes eval + /learnings
child-bot/ingest-watcher.ts ← Auto-ingest : fs.watch sur raw/ → inbox CRM (Phase 28)
master-bot/bot.ts ← Intégration : déclencheur cron nocturne Sage