Skip to content

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 :

  1. Contraintes de sortie — Format attendu
  2. JSON vs texte libre vs code
  3. Schéma de validation (ex: 必须包含 slug, title, content)
  4. Longueur maximale/minimale

  5. Contraintes de comportement — Comment agir

  6. "Toujours utiliser area_id au lieu de entity_id pour les lumières"
  7. "Ne jamais envoyer d'email sans confirmation"
  8. "En cas d'erreur, retenter 3 fois avant d'échouer"

  9. Contraintes de sécurité — Ce qui est interdit

  10. "Pas d'exécution de commandes destructives sans approbation"
  11. "Pas d'accès MEMORY.md dans les groupes publics"
  12. "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 :

# Tout ou rien
try:
    result = call_llm(prompt)
    if not result:
        return "Erreur, veuillez réessayer"

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.

Success Rate = (Tâches réussies sans correction) / (Total des tâches)

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.

Mean Corrections = (Total corrections) / (Tâches réussies)

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.

Temporal Consistency = (Réponses cohérentes avec le passé) / (Total des réponses)

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.

Silent Failure Rate = (Échecs non détectés) / (Total des échecs)

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).

Loop Latency = (Temps total des tâches) / (Nombre de tâches)

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 :

  1. Explicit over Implicit — Les règles sont dans le code, pas cachées dans le prompt
  2. Verification over Trust — On vérifie tout, on ne fait rien confiance
  3. Persistence over Ephemeral - On apprend de chaque interaction
  4. 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