Harness Engineering : Comment Concevoir un Bon Système Autour d'un LLM
Harness Engineering : Comment Concevoir un Bon Système Autour d'un LLM
Introduction : Tu n'as pas besoin d'un meilleur modèle
Si vous lisez ceci, vous avez probablement déjà tenté de construire quelque chose avec un LLM. Peut-être un chatbot, un assistant de code, un outil d'analyse. Et vous avez probablement rencontré le même problème que tout le monde : ça marche parfois, mais pas toujours.
La réaction instinctive ? "Il me faut un meilleur modèle." GPT-4, Claude 3.5, Gemini Ultra — on pense que la solution est dans le moteur. Plus puissant, plus intelligente, plus fiable.
C'est faux.
Le 12 février 2026, un ingénieur démontre qu'en améliorant uniquement le harness — sans changer un seul modèle — il améliore les performances de 15 LLMs différents en coding, en un seul après-midi. Même résultat : la qualité du système dépend plus de l'architecture que du modèle.
"The model is a commodity. The harness is the product."
Cet article vous donne un cadre pratique pour concevoir un bon harness. Pas de théorie abstraite. Des étapes concrètes, des patterns à suivre, des erreurs à éviter, des métriques à mesurer.
Le Framework de Conception en 4 Étapes
Un bon harness ne se construit pas par hasard. Il résulte d'un processus systématique qui répond à quatre questions fondamentales :
Étape 1 : Quel est le domaine de compétence ?
La première erreur : essayer de faire tout avec le même système. Un harness généraliste est un harness médiocre.
Questions à se poser : - Quelles sont les tâches spécifiques que je veux accomplir ? - Quelles sont les limites de mon domaine ? - Qu'est-ce que je refuse de faire ?
Exemple Cursor : Le harness de Cursor est spécialisé dans l'édition de code. Il ne génère pas d'images, ne compose pas d'emails, ne planifie pas de vacances. Il se concentre sur une boucle précise : inspect → edit → run checks → verify → iterate.
Exemple OpenClaw : Le harness est organisé en skills autonomes (email, mealie, home assistant). Chaque skill a son domaine de compétence clairement défini. La skill email-mcp envoie des emails — elle ne fait pas de café.
Checklist : - [ ] J'ai identifié 1-3 tâches principales (pas plus) - [ ] Je sais explicitement ce que je ne fais pas - [ ] J'ai documenté les limites du domaine
Étape 2 : Quelles sont mes contraintes opérationnelles ?
Un LLM sans contraintes est un générateur de chaos. Un bon harness explicite les règles du jeu.
Types de contraintes à définir :
- Contraintes de sortie — Format attendu
- JSON vs texte libre vs code
- Schéma de validation (ex: 必须包含
slug,title,content) -
Longueur maximale/minimale
-
Contraintes de comportement — Comment agir
- "Toujours utiliser
area_idau lieu deentity_idpour les lumières" - "Ne jamais envoyer d'email sans confirmation"
-
"En cas d'erreur, retenter 3 fois avant d'échouer"
-
Contraintes de sécurité — Ce qui est interdit
- "Pas d'exécution de commandes destructives sans approbation"
- "Pas d'accès MEMORY.md dans les groupes publics"
- "Pas de modification de fichiers système"
Exemple concret : Dans OpenClaw, les contraintes ne sont pas implicites. Elles sont documentées dans TOOLS.md :
🚨 RÈGLE IMPORTANTE - Lumières :
TOUJOURS utiliser `area_id` au lieu de `entity_id`
❌ À ne pas faire :
ha_call_service("light", "turn_on", entity_id="light.luminaire_salon")
✅ À faire :
ha_call_service("light", "turn_on", data={"area_id": "salon", "brightness_pct": 70})
Le LLM n'a pas à "deviner" ces règles. Elles lui sont explicitement fournies dans le contexte.
Checklist : - [ ] J'ai documenté le format de sortie attendu (schéma) - [ ] J'ai listé les règles de comportement clés - [ ] J'ai identifié les actions qui nécessitent une approbation - [ ] J'ai écrit les contraintes dans un fichier dédié (pas cachées dans le prompt)
Étape 3 : Comment je vérifie la qualité ?
Un LLM génère. Un harness vérifie. La différence entre un système "qui marche parfois" et un système fiable, c'est la boucle de vérification.
Niveaux de vérification :
Niveau 1 : Validation syntaxique - Le JSON est-il valide ? - Les champs requis sont-ils présents ? - Les types correspondent-ils ?
# Exemple : Vérification après génération
result = json.parse(llm_output)
assert "slug" in result, "Missing required field: slug"
assert isinstance(result["tags"], list), "tags must be a list"
Niveau 2 : Validation sémantique - Le contenu est-il cohérent avec la demande ? - Les valeurs sont-elles dans les bornes acceptables ? - Y a-t-il des contradictions internes ?
# Exemple : Vérification cohérence
assert result["brightness_pct"] >= 0 and result["brightness_pct"] <= 100
assert result["color_temp_kelvin"] in [2700, 3000, 4000, 5000]
Niveau 3 : Validation par exécution - Le code s'exécute-t-il ? - L'API retourne-t-elle une réponse valide ? - Le fichier est-il créé correctement ?
# Exemple : Cursor - Boucle de vérification
for attempt in range(max_attempts):
change = agent.make_edit(code)
result = run_tests(change)
if result.passed:
break
# L'agent voit l'erreur et corrige
Exemple OpenClaw : Quand je génère un article pour IDex, je ne me contente pas de créer le fichier Markdown. Je vérifie :
1. Le frontmatter est valide (title, date, category, tags)
2. Le fichier est au bon endroit (docs/blog/)
3. Le nom suit la convention (YYYY-MM-DD-slug.md)
4. Le podcast est généré avec les bons paramètres
Si une étape échoue, je ne présente pas à l'utilisateur. Je corrige et je réessaie.
Checklist : - [ ] J'ai des tests de validation syntaxique (JSON, schémas) - [ ] J'ai des tests de validation sémantique (cohérence) - [ ] J'ai une boucle de retry en cas d'échec - [ ] Je connais mon taux d'échec actuel (métrique)
Étape 4 : Comment je persiste l'apprentissage ?
Un harness sans mémoire est un outil sans progression. Chaque interaction devrait enrichir le système.
Types de persistence :
1. Persistence structurée — Données organisées
- Configurations (.json, .yaml)
- Logs structurés
- Index et métadonnées
2. Persistence narrative — Mémoire événementielle
- Journaux quotidiens (memory/YYYY-MM-DD.md)
- Décisions importantes
- Erreurs et leçons apprises
3.长久Mémoire longue — Distillation - Faits durables (préférences utilisateur) - Patterns récurrents - Connaissances du domaine
Exemple OpenClaw : Le système de mémoire à deux vitesses
- Court terme : memory/2026-02-19.md — Tout est noté, sans curation
- Long terme : MEMORY.md — Seulement ce qui mérite d'être conservé
La transition se fait via heartbeats réguliers qui relisent les fichiers récents et distillent ce qui compte.
Exemple Cursor : Les .cursorrules persistent les règles du projet. Après des mois de développement, un fichier contient l'accumulation des décisions architecturales, des patterns à suivre, des erreurs à éviter.
Checklist : - [ ] J'ai un système de logs structurés - [ ] J'ai un mécanisme de mémoire à long terme - [ ] Je sais distiller ce qui compte des données brutes - [ ] Les informations persistantes sont exploitables (pas juste du texte)
Patterns à Suivre ✅
Pattern 1 : Tool-First Design
Principe : Commencez par les outils, pas par le prompt.
Mauvaise approche :
# Tout dans le prompt, implicite
prompt = "Tu es un assistant qui peut contrôler les lumières. Quand je dis 'allume', allume-les."
# Comment le modèle sait-il quoi faire ? Magie ?
Bonne approche :
# Schéma d'outil explicite
tools = [{
"name": "ha_call_service",
"description": "Appelle un service Home Assistant",
"parameters": {
"service": {"type": "string"},
"action": {"type": "string"},
"data": {"type": "object"}
}
}]
# Le modèle voit exactement ce qu'il peut faire et comment
Pourquoi c'est mieux : - Le modèle ne peut pas inventer des outils inexistants - Les paramètres sont typés et validés - Le comportement est prévisible et testable
Pattern 2 : Explicit Constraints Over Prompt Engineering
Principe : Ne comptez pas sur le prompt pour imposer des contraintes. Rendez-les explicites dans le système.
Mauvaise approche :
prompt = """
Génère un article en FORMAT MARKDOWN avec un FRONTMATTER qui contient
title, date, category, tags, description. Le nom du fichier doit être
YYYY-MM-DD-slug.md. Ne dépasse pas 2500 mots. Utilise un ton pragmatique.
"""
# Le modèle va oublier la moitié de ces contraintes
Bonne approche :
# Schéma de validation
article_schema = {
"title": {"required": True, "max_length": 100},
"date": {"format": "YYYY-MM-DD"},
"category": {"enum": ["🤖 IA", "💻 Dev", "🎨 Design"]},
"tags": {"type": "array", "max_items": 10},
"content": {"max_words": 2500}
}
# Après génération
validate(result, article_schema)
Pourquoi c'est mieux : - Les contraintes sont vérifiées automatiquement - Le système rejette les sorties invalides (retry) - Le prompt reste simple et focalisé sur le contenu
Pattern 3 : Fail-Gracefully avec Fallbacks
Principe : Prévoyez l'échec. Quand ça échoue, dégradez gracieusement.
Mauvaise approche :
Bonne approche :
# Cascade de fallbacks
models = ["gpt-4", "claude-3.5", "gemini-pro", "local-llm"]
for model in models:
try:
result = call_llm(prompt, model=model)
if validate(result):
return result
except Exception as e:
log_error(e, model)
continue
# Fallback ultime : réponse template
return safe_template_response()
Exemple OpenClaw : 15+ modèles en cascade
- zai/glm-4.7 → Modèle principal
- gemini-2.5-flash → Backup rapide et gratuit
- gpt-5-codex → Spécialisé code
- Et encore...
Si le modèle principal échoue, le système continue automatiquement. L'utilisateur ne voit pas l'erreur, il voit une réponse (peut-être moins bonne, mais une réponse quand même).
Pattern 4 : Verification Loop
Principe : Ne présentez jamais une sortie non vérifiée à l'utilisateur.
Exemple Cursor :
1. Agent génère un changement de code
2. Compilation / tests
3. Si échec → Agent voit l'erreur, corrige
4. Répétez jusqu'à succès
5. Seulement alors, présente à l'utilisateur
Exemple OpenClaw (création d'article) :
1. Génération du contenu Markdown
2. Vérification du frontmatter (title, date, tags...)
3. Vérification du placement (bon dossier, bon nom de fichier)
4. Génération du podcast (Edge TTS)
5. Redémarrage du service podcast-server
6. Confirmation à l'utilisateur : "Article créé + podcast généré"
Si l'étape 4 échoue (TTS down), je ne dis pas "Désolé, erreur". Je dis : "Article créé avec succès. Podcast en attente — le serveur TTS ne répond pas. Je réessaierai plus tard."
Checklist patterns : - [ ] Mes outils sont définis avec des schémas explicites - [ ] Mes contraintes sont vérifiées par du code, pas par le prompt - [ ] J'ai une cascade de fallbacks en cas d'échec - [ ] Je ne présente jamais une sortie non vérifiée
Patterns à Éviter ❌
Anti-Pattern 1 : Implicit Assumptions
Le problème : Supposer que le LLM "comprendra" ou "devinera".
Exemple typique :
prompt = "Analyse ce code et améliore-le."
# Qu'est-ce que "améliore" ?
- Plus rapide ?
- Plus lisible ?
- Plus sécurisé ?
- Tous les trois ?
- Dans quel style ?
- Quelles refactorisations sont acceptables ?
Le modèle va : - Deviner vos intentions (souvent faux) - Appliquer des changements inappropriés - Négatif pour la confiance utilisateur
Solution : Explicitement tout
prompt = """
Analyse ce code Python et applique ces refactorisations :
1. Ajoute des type hints (PEP 484)
2. Remplace les f-strings par .format() pour compatibilité Python 3.5
3. Ajoute de la docstring Google style
4. NE PAS changer la logique métier
5. NE PAS ajouter de nouvelles dépendances
"""
Anti-Pattern 2 : Over-Reliance on Prompt Engineering
Le problème : Essayer de résoudre tous les problèmes avec des prompts plus complexes.
Pourquoi c'est une impasse : - Les prompts ont une limite de taille - Plus c'est long, plus le modèle "oublie" le début - Impossible de maintenir des prompts de 5000 lignes - Chaque changement de prompt nécessite des tests
Solution : Déplacez la complexité vers le code
# Au lieu d'un prompt géant avec toutes les règles
# Créez une validation layer
class ArticleValidator:
def validate(self, article):
assert len(article.title) < 100
assert article.category in ALLOWED_CATEGORIES
assert article.date <= datetime.now()
# ... 50 autres règles
# Le prompt reste simple
prompt = "Génère un article sur [sujet]"
# La validation gère tout le reste
Anti-Pattern 3 : No Verification Layer
Le problème : Faire confiance aveuglément à la sortie du LLM.
Conséquences typiques : - JSON invalide qui fait planter l'application - Code qui ne compile pas - URLs fausses qui renvoient 404 - Incohérences factuelles
Solution : Toujours vérifier
# Minimal verification
try:
result = json.parse(llm_output)
except JSONDecodeError:
return retry_with_stricter_prompt("Format JSON requis")
# Semantic verification
if not is_coherent(result):
return retry_with_context("Cette réponse est incohérente")
# Execution verification
if not execute_safely(result):
return retry_with_error_feedback("L'exécution a échoué")
Anti-Pattern 4 : One-Shot No Loop
Le problème : Une seule génération, pas de correction possible.
Réalité : Même les meilleurs humains font des erreurs. Pourquoi les LLMs seraient parfaits du premier coup ?
Solution : Implémentez une boucle de rétroaction
max_iterations = 3
for i in range(max_iterations):
result = generate(prompt + context)
errors = validate(result)
if not errors:
return result # Succès
# Feedback pour la prochaine itération
context += f"\nErreur: {errors}\nCorrige en gardant le reste."
# Après 3 essais, fallback
return fallback_response()
Exemple réel : Cursor utilise cette approche pour l'édition de code. L'agent génère un changement, les tests échouent, l'agent voit l'erreur, corrige, recommence. Souvent 2-3 itérations avant succès.
Métriques de Qualité d'un Harness
Comment savoir si votre harness est bon ? Mesurez-le.
Métrique 1 : Taux de Succès (Success Rate)
Définition : Pourcentage de tâches complétées sans intervention humaine.
Bonnes valeurs : - < 50% : Harness insuffisant, repensez l'architecture - 50-70% : Moyen, améliorable - 70-90% : Bon, exploitable en production - > 90% : Excellent, rarement atteint sans sur-spécialisation
Comment mesurer :
# Log chaque tâche
log_task({
"task_id": "article-123",
"success": True,
"retries": 1,
"human_intervention": False
})
# Calculer le taux
success_rate = succeeded_without_help / total_tasks
Métrique 2 : Corrections Nécessaires (Mean Corrections)
Définition : Nombre moyen de corrections nécessaires par tâche réussie.
Interprétation : - 0 : Parfait (impossible en pratique) - 0.1-0.5 : Excellent (1 correction toutes les 2-10 tâches) - 0.5-2 : Moyen - > 2 : Problématique (plus de corrections que de tâches)
Comment réduire : - Améliorer les prompts avec exemples (few-shot) - Ajouter des règles explicites dans le système - Implémenter une boucle de vérification/correction automatique
Métrique 3 : Cohérence Temporelle (Temporal Consistency)
Définition : Capacité du système à rester cohérent dans le temps.
Exemple de cohérence : - Journée 1 : "Marc préfère les lumières à 2700K" - Journée 10 : "Allumer le salon" → utilise 2700K - Journée 30 : "Allumer le salon" → utilise toujours 2700K
Exemple d'incohérence : - Journée 1 : Préférence 2700K - Journée 15 : Utilise 5000K (oubli de la préférence) - Journée 20 : Demande confirmation (préférence perdue)
Comment améliorer : - Système de mémoire long terme (MEMORY.md) - Distillation régulière des faits durables - Chargement du contexte historique avant chaque génération
Métrique 4 : Taux d'Échec Silencieux (Silent Failure Rate)
Définition : Pourcentage d'échecs que le système ne détecte pas lui-même.
Pourquoi c'est critique : Un échec détecté = retry = correction possible. Un échec silencieux = mauvaise réponse présentée à l'utilisateur = perte de confiance.
Exemple d'échec détecté :
result = call_api(endpoint)
if result.status_code == 500:
log_error("API down")
return fallback_response() # Échec géré
Exemple d'échec silencieux :
result = call_api(endpoint)
# L'API retourne 200 mais avec des données incorrectes
# Le système ne vérifie pas la cohérence
# L'utilisateur reçoit une mauvaise réponse
Comment réduire : - Validation sémantique des réponses - Checks de cohérence (ex: slug ne doit pas être vide) - Tests de régression réguliers
Métrique 5 : Latence de Boucle (Loop Latency)
Définition : Temps moyen pour une boucle complète (génération + vérification + correction si nécessaire).
Pourquoi c'est important : - Trop rapide = risques d'erreurs non détectées - Trop lent = mauvaise expérience utilisateur - L'équilibre dépend du cas d'usage
Typique : - Chat instantané : < 2 secondes (accepte plus d'erreurs) - Génération de code : 5-30 secondes (vérification rigoureuse) - Analyse de documents : 30-120 secondes (précision avant tout)
Checklist de Conception d'un Harness
Avant de mettre en production, passez en revue cette checklist.
Phase de Conception
Domaine : - [ ] J'ai identifié 1-3 tâches principales - [ ] Je sais clairement ce que je ne fais PAS - [ ] J'ai documenté les limites du domaine
Contraintes : - [ ] Le format de sortie est spécifié (schéma) - [ ] Les règles de comportement sont documentées - [ ] Les actions sensibles nécessitent une approbation - [ ] Les contraintes sont dans un fichier dédié (pas dans le prompt)
Vérification : - [ ] J'ai des tests de validation syntaxique - [ ] J'ai des tests de validation sémantique - [ ] J'ai une boucle de retry (max 3 itérations) - [ ] Je connais mon taux d'échec actuel
Persistence : - [ ] J'ai un système de logs structurés - [ ] J'ai un mécanisme de mémoire long terme - [ ] Je sais distiller ce qui compte - [ ] La mémoire est exploitable (pas du texte brut)
Phase de Test
Fonctionnel : - [ ] Le système fonctionne sur le cas nominal - [ ] Le système gère les erreurs API - [ ] Le système gère les timeouts - [ ] Le système dégrade gracieusement (fallbacks)
Qualité : - [ ] Success rate > 70% - [ ] Mean corrections < 1 - [ ] Silent failure rate < 10% - [ ] Latence acceptable pour le cas d'usage
Robustesse : - [ ] Fonctionne même si le modèle principal est down - [ ] Fonctionne même si une API externe est down - [ ] Ne crash pas sur des entrées invalides - [ ] Ne perd pas la mémoire entre les sessions
Conclusion : L'avantage compétitif se déplace vers l'architecture
En 2023, la question était : "Quel modèle utilises-tu ?" En 2026, la question est : "Quel harness as-tu construit ?"
La course aux modèles géants n'est pas la seule voie. Un système bien conçu avec un modèle plus petit peut surpasser un système mal conçu avec le dernier modèle à la mode.
Cursor l'a démontré. OpenClaw le démontre chaque jour. Des startups comme LangChain, Dust, et fixie le savent.
Ce qui différencie les bons systèmes des médiocres :
- Explicit over Implicit — Les règles sont dans le code, pas cachées dans le prompt
- Verification over Trust — On vérifie tout, on ne fait rien confiance
- Persistence over Ephemeral - On apprend de chaque interaction
- Fail-Gracefully — Quand ça échoue, on dégrade avec élégance
L'avenir de l'IA n'est pas dans des modèles plus grands. Il est dans des systèmes plus intelligents autour des modèles.
Le harness engineering n'est pas juste une pratique technique. C'est un changement de paradigme. Le modèle devient une commodité — un moteur interchangeable. La vraie valeur, c'est l'architecture qui transforme ce moteur en assistant fiable, prévisible, et utile.
Prochaine Étape : Construisez Votre Harness
Maintenant que vous avez le cadre, construisez.
Commencez petit. Un domaine précis. 1-3 tâches. Contraintes explicites. Verification simple. Persistence basique.
Itérez. Mesurez vos métriques. Améliorez.
Rappelez-vous : Un harness parfait n'existe pas. Mais un harness qui s'améliore à chaque interaction ? Ça, c'est possible.
C'est ça, la beauté du harness engineering.
Sources : - Blog.can.ac - I Improved 15 LLMs at Coding in One Afternoon (12 février 2026) - Cursor - Best practices for coding with agents - Blog ByteByteGo - How Cursor Shipped its Coding Agent to Production - OpenClaw - Architecture et code source (accès complet) - Parallel AI - What is an agent harness in the context of LLMs?
Articles de la série : 1. Le Harness de l'IA : Le Vrai Secret de la Performance 2. Autopsie d'un Harness : OpenClaw comme Étude de Cas 3. Harness Engineering : Méthodologie
Photo by Ilya Pavlov on Unsplash