Parcours 3 : Harness
De l'histoire du processus de développement jusqu'à un harness complet, puis dix étapes sur un dépôt jetable, avec les vrais gardes : les sorties affichées sont celles de l'exécution, recalculées à chaque génération du site. La pratique prépare un dépôt jetable pour le plugin, puis lance les gardes à la main : ce sont de simples scripts, les mêmes que ceux que Claude Code appelle. Prérequis : les parcours Culture et Contexte, Python 3 et le plugin Kwa installé (voir Avant de commencer). Les exercices tournent sans agent, sans compte ni clé ; Claude Code ou Codex sert pour les essais en situation, et jq pour l'exemple de hook en bash. Environ quatre heures, à calibrer en séance.
Parcours pas encore éprouvé avec un groupe : les durées et les exercices sont des estimations de conception, à ajuster après une première séance.
- 0 · L'histoire
- 1 · Instructions
- 2 · Un garde
- 3 · Branchement
- 4 · Événements
- 5 · Politique
- 6 · Une skill
- 7 · Mémoire
- 8 · Workflow
- 9 · Le flux
- 10 · Limites
Introduction
D'où vient le harness
Le processus protégeait la qualité. L'agent l'a contourné par vitesse.
Pendant cinquante ans, la qualité d'un logiciel ne dépendait pas que du talent de chacun : elle venait d'un processus (spécifier, tester, relire, intégrer, déployer). Chaque époque a ajouté une couche de vérification. Un agent de code écrit et exécute plus vite que ce processus ne peut suivre : sans cadre, il décide et agit seul. Le harness est ce cadre : tout ce qui entoure le modèle pour qu'il travaille dans le processus, et non à côté.
| Époque | Pratique | Qui écrit | Ce qui protège la qualité |
|---|---|---|---|
| 1970 | Cycle séquentiel | Les personnes, en phases successives | Spécification et recette finales, revues de documents |
| 2001 | Méthodes agiles | Les personnes, par petits lots | Itérations courtes, tests, revue entre pairs |
| 2008 à 2010 | Pull requests, CI/CD, DevOps | Les personnes, avec des machines qui vérifient | Revue de code sur chaque changement, intégration continue, déploiement automatisé, branches protégées |
| 2021 | Complétion dans l'éditeur | La personne, avec des suggestions | Inchangé : la personne relit chaque ligne qu'elle accepte |
| 2022 | Assistant par conversation | La personne, qui copie et colle | Inchangé, mais le volume de texte relu augmente |
| 2024 à 2025 | Agents avec outils (MCP, terminal) | Un agent, qui lit, modifie, lance et commit | Les demandes d'autorisation de l'outil existent, mais elles ignorent votre processus et les règles de votre équipe |
| Aujourd'hui | Harness | Un agent, dans un cadre | Règles, gardes, skills, mémoire et preuve rebranchés autour de l'agent |
Dates : repères généraux de l'histoire du génie logiciel (article de Royce en 1970, Manifeste agile en 2001, GitHub en 2008, GitHub Copilot en 2021, ChatGPT fin 2022, MCP fin 2024). La lecture « le harness rebranche le processus » est celle de Kwa, pas un consensus.
Chaque étape du processus d'équipe a donc son équivalent autour de l'agent :
| Étape du processus | Dans le harness (Kwa) |
|---|---|
| Cadrer le besoin | /kwa:brainstorm, /kwa:plan |
| Ticket et branche dédiée | /kwa:start-dev et kwa start |
| Tests d'abord | /kwa:tdd |
| Revue de code | /kwa:review, retours traités par /kwa:review-feedback |
| Branches protégées, pas de force-push | Gardes guard-git, guard-write |
| Intégration continue | /kwa:verify, vérifications de la politique |
| Pull request avec preuve | /kwa:ship, guard-github |
| Déploiement vérifié | /kwa:deploy |
| Rétrospective | /kwa:learn, mémoire de session |
Étape 1
Instructions et contrainte
Ce que l'agent lit, et ce qui l'oblige.
Préparez un dépôt jetable pour le plugin. kwa init n'écrit que de la configuration : les scripts restent dans le plugin. Regardez ce qui est écrit pour l'agent, puis demandez-vous : quelle ligne l'empêche de committer sur main ?
$ kwa init ./mon-projet --modules core,memory,craft,issue-flow
kwa v0.2.2 (plugin) → mon-projet
modules : core, memory, craft, issue-flow
gabarits copiés :
- .github/pull_request_template.md
- .github/ISSUE_TEMPLATE/bug.md
- .github/ISSUE_TEMPLATE/feature.md
- .github/ISSUE_TEMPLATE/config.yml
AGENTS.md : créé
CLAUDE.md : créé
politique .claude/kwa.policy.json : créée à partir de la détection — à relire et compléter
Dans une session Claude Code, le plugin met kwa dans le PATH. Dans un terminal ordinaire, appelez-le depuis le dossier du plugin, noté $KWA dans ce parcours : ~/.claude/plugins/cache/kwa/kwa/<version>, ou un clone du dépôt Kwa ("$KWA"/bin/kwa init …).
CLAUDE.md contient une ligne, un pointeur vers le fichier commun :
@AGENTS.md
Début du bloc géré de AGENTS.md :
# AGENTS.md <!-- kwa:begin v0.2.2 profile=pr-flow (géré ; toute retouche ici est écrasée à la mise à jour) --> ## Kwa — socle commun Ce bloc est géré par Kwa (`kwa init`) : ne pas l'éditer ici, le modifier dans le pack. Tout ce qui est propre au projet va **au-dessus ou en dessous** des marqueurs, ou dans `.claude/kwa.policy.json`. - `AGENTS.md` est la source unique. `CLAUDE.md` n'est qu'un pointeur `@AGENTS.md`. - Modules actifs : core, memory, craft, issue-flow. - Règles communes : les règles communes de Kwa (rappelées au démarrage de session). Garde-fous automatiques : les hooks du plugin Kwa ; ce sont des filets contre la bévue, pas une frontière de sécurité : ne jamais les contourner ni les éditer sans accord.
Réponse
Aucune ligne ne l'empêche. Un fichier d'instructions est du contexte : l'agent le lit et peut s'en écarter. Ce qui contraint, c'est un hook qui s'exécute avant l'outil. Voici le même commit, tenté sur main, quand le garde est appelé :
{"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "Kwa — git commit sur main refusé : travailler sur une branche dédiée et ouvrir une PR (KWA_ALLOW_MAIN=1 pour une exception assumée)"}}
Étape 2
Un garde, de la commande au verdict
Un hook reçoit du JSON et répond refuse, demande ou laisse passer.
Un garde est un petit programme. C'est Claude Code qui le lance, pas le modèle : il lui envoie ce que l'agent s'apprête à faire, et le garde répond sur la sortie standard. Appelez-le à la main :
$ echo '{"tool_name": "Bash", "tool_input": {"command": "git commit -m wip"}, "cwd": "."}' | python3 "$KWA"/core/hooks/guard-git.py
Pas de sortie veut dire « laisse passer ». Avant de regarder les réponses, faites votre pronostic pour chaque commande, puis ouvrez :
| Commande | Verdict réel |
|---|---|
cat .envbranche de travail | Mon pronosticrefusé commande refusée : touche un fichier de secrets (.env) |
cat .env.examplebranche de travail | Mon pronosticpasse |
git push --force origin feat/12-filtrebranche de travail | Mon pronosticrefusé push forcé ou destructif refusé (--force, -f, +ref, --delete, --all, --mirror). Après un rebase : --force-with-lease, avec confirmation. |
rm -rf buildbranche de travail | Mon pronosticpasse |
git commit -m 'wip'branche main | Mon pronosticrefusé git commit sur main refusé : travailler sur une branche dédiée et ouvrir une PR (KWA_ALLOW_MAIN=1 pour une exception assumée) |
Verdicts calculés en exécutant les gardes à chaque génération de la page.
Étape 3
Le branchement
Un hook relie un événement à un script. Le plugin le déclare, le projet active le plugin.
Le plugin déclare ses hooks dans un fichier hooks.json. Voici l'entrée qui relie le garde git à l'événement PreToolUse pour l'outil Bash ; run.py vérifie d'abord que le projet a une politique Kwa et le module concerné :
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "python3 \"${CLAUDE_PLUGIN_ROOT}/core/hooks/run.py\" guard-git", "timeout": 10 } ] } ] } }
Le projet, lui, active le plugin dans .claude/settings.json :
{ "enabledPlugins": { "kwa@kwa": true } }
Vérifiez l'état de l'installation (ici sur une machine où le plugin n'est pas encore installé) :
$ kwa doctor ./mon-projet• plugin Claude Code sur cette machine : non installé (/plugin install kwa@kwa) ✓ cible conforme (plugin)
Cassez-le : retirez enabledPlugins de settings.json sans toucher au script, puis relancez le diagnostic.
• plugin Claude Code sur cette machine : non installé (/plugin install kwa@kwa) ✗ .claude/settings.json : plugin kwa@kwa non activé pour le projet
Que fait le script maintenant ?
Il refuse toujours quand on l'appelle à la main, mais plus personne ne l'appelle dans ce projet. Un garde qui existe sans être branché ne protège de rien.
{"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "Kwa — git commit sur main refusé : travailler sur une branche dédiée et ouvrir une PR (KWA_ALLOW_MAIN=1 pour une exception assumée)"}}
Étape 4
Les autres événements de hooks
Le garde ne sert qu'un événement sur plus de trente. Le choix de l'événement fait la différence.
Jusqu'ici, un seul événement : PreToolUse, avant un outil. Il en existe plus de trente. Cinq suffisent pour commencer :
| Événement | Quand | Usage typique | Dans Kwa |
|---|---|---|---|
SessionStart | Début, reprise, après effacement ou compaction | Réinjecter le contexte utile | memory-context |
UserPromptSubmit | Avant que l'agent traite votre message | Ajouter du contexte, repérer un signal | signal-prompt |
PreToolUse | Avant un outil | Bloquer ou demander | les gardes |
PostToolUse | Après un outil réussi | Formater, lancer un contrôle rapide | format-after-edit |
Stop | Quand l'agent a fini de répondre | Refuser de s'arrêter tant que ce n'est pas vérifié | verify-stop |
Un hook est une commande, mais aussi, selon le besoin, un appel HTTP, un outil MCP, une évaluation par un modèle, ou un sous-agent de vérification (ces deux derniers sont réservés aux décisions qui demandent du jugement). La règle des codes de sortie est la même partout : 0 laisse faire, 2 bloque et renvoie le message à l'agent.
Pour sentir le principe sans Kwa, voici un garde complet, hors de tout outillage : un script et sa déclaration.
.claude/hooks/protege.sh :
#!/bin/bash # bloque l'édition des fichiers protégés FILE=$(cat | jq -r '.tool_input.file_path // empty') for motif in ".env" "package-lock.json" ".git/"; do if [[ "$FILE" == *"$motif"* ]]; then echo "Bloqué : $FILE correspond à $motif" >&2 exit 2 fi done exit 0
.claude/settings.json :
{ "hooks": { "PreToolUse": [ { "matcher": "Edit|Write", "hooks": [{ "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protege.sh" }] } ] } }
Pronostic : pour chaque besoin, quel événement ?
| Besoin | Événement |
|---|---|
| Formater chaque fichier modifié | Mon pronostic
|
| Rappeler au démarrage où en était la session précédente | Mon pronostic
|
| Empêcher de terminer avec des tests rouges | Mon pronostic
|
Étape 5
La politique du projet
Ce qui change d'un projet à l'autre tient dans un fichier, pas dans un script.
Les gardes sont communs, la politique est locale (.claude/kwa.policy.json). Dans le dépôt jetable, la commande npm run deploy:prod n'est pas gardée : passe
Ajoutez cette règle dans bash.deny :
{ "id": "deploy-prod", "reason": "Le déploiement de production passe par la PR, pas par ce script.", "all": [ "npm run deploy:prod" ] }
Même commande, même garde, nouveau verdict : refusé
[deploy-prod] Le déploiement de production passe par la PR, pas par ce script.
Étape 6
Une skill, et comment la casser
Une procédure que l'agent déclenche ou que vous tapez.
Une skill est un fichier SKILL.md : un en-tête qui dit quand l'appeler, un corps qui dit comment. Celui de /kwa:rephrase :
--- name: rephrase description: Reformuler le dernier message quand l'utilisateur n'a pas compris. À utiliser dès qu'il dit « stop », « je ne comprends pas », « explique autrement », « reformule », « c'est quoi ça ? », « /kwa:rephrase ». disable-model-invocation: true ---
Dans un clone du dépôt Kwa, créez une skill dont la description contient « : » (un piège YAML qui tronque l'en-tête sans bruit), puis lancez la suite de contrôle des skills. Elle échoue deux fois : le premier échec est le piège YAML, le second rappelle qu'une skill doit être déclarée dans manifest.json.
$ python3 -m unittest tests.test_skills.Lint
FAIL: test_description_survives_yaml_parsing (tests.test_skills.Lint) (skill='demo') AssertionError: ': ' unexpectedly found in 'Faire une démonstration. À lancer quand : on le demande.' : « : » dans une description non quotée casse le YAML FAIL: test_manifest_covers_every_skill (tests.test_skills.Lint) FAILED (failures=2)
Pourquoi ce test existe
Une description mal formée ne plante rien : la skill devient simplement introuvable. Un test qui échoue vaut mieux qu'une skill invisible.
Étape 7
La mémoire qui s'enrichit
Noter pendant, trier à la fin, valider soi-même.
Capturez un piège rencontré pendant le travail, sans rien écrire dans la documentation :
$ kwa memory capture gotcha "Les tests d'intégration ont besoin du fichier d'environnement local"
$ kwa memory pending1 note(s) en attente - [2026-10-15] [gotcha] Les tests d'intégration ont besoin du fichier d'environnement local
En fin de session, /kwa:learn propose où chaque note devrait vivre (documentation, politique, mémoire de l'agent). Rien n'est appliqué sans votre accord.
Étape 8
Le workflow complet
Une demande, une branche, une preuve, une PR.
Démarrez un changement : une branche dans son propre worktree, à côté du dépôt, jamais sur main.
$ kwa start feat status-filter "Ajouter un filtre de statut" --no-issue
aucune issue créée (flow.auto_issue: false ou --no-issue) branche : feat/status-filter worktree : mon-projet-wt-status-filter Travailler dans : mon-projet-wt-status-filter
mon-projet abc1234 [main] mon-projet-wt-status-filter abc1234 [feat/status-filter]
La suite se joue avec les skills : /kwa:brainstorm, /kwa:plan, /kwa:tdd, /kwa:verify, /kwa:review, /kwa:ship. La page Les workflows déroule ce workflow sur une demande réelle, et le Terminal montre les commandes.
Étape 9
Le flux d'un développeur aujourd'hui
Tout mis ensemble : une journée où presque tout est automatisé, sauf les décisions.
Les étapes précédentes sont des pièces. Voici l'assemblage tel qu'une équipe qui l'a bien outillé le vit : l'agent code, mais chaque affirmation est adossée à une preuve, et les humains gardent les deux décisions qui engagent (valider ce qu'on construit, accepter ce qui est livré).
Le travail d'un développeur se déplace : moins de frappe, plus de cadrage (critères de réussite précis), de mise en place (hooks, MCP, règles) et de relecture de preuves. Context7 évite l'API périmée, Playwright évite le « ça marche chez moi » sans l'avoir vu, et un agent vérificateur en contexte neuf évite le biais de celui qui relit ce qu'il vient d'écrire.
Deux promesses à ne pas faire : que la vérification attrape tout (un vérificateur invité à trouver des écarts en trouve toujours, d'où la consigne de ne retenir que ceux qui touchent aux exigences), et que le garde-fou remplace la relecture humaine.
À vous
Prenez une tâche récente de votre équipe. Pour chacune des huit étapes, écrivez : ce qui était fait à la main, ce qui peut être automatisé, et le contrôle qui prouverait que c'est bon. Marquez les deux décisions que vous refusez de déléguer.
Étape 10
Les limites, sans maquillage
Un garde est un filet contre la bévue, pas une frontière de sécurité.
Essayez de contourner le garde des secrets. Ces trois commandes lisent le même fichier d'environnement ; seule la forme change :
| Commande | Verdict réel |
|---|---|
cat $(echo .e)nv | passe |
cat .e* | passe |
python3 -c "print(open('.env').read())" | passe |
Le garde lit le texte de la commande, il ne l'exécute pas : un nom calculé, un glob ou un interpréteur lui échappent.
Alors, à quoi ça sert, et que mettre en plus ?
À arrêter l'erreur honnête. Les règles permissions.deny de Claude Code, seules, ne font pas mieux : Read(./.env) bloque l'outil de lecture, pas python3 -c. Contre une attaque, il faut une barrière que le système d'exploitation ou GitHub fait respecter : le bac à sable de Claude Code (/sandbox, désactivé par défaut), qui applique aussi ces règles aux commandes shell ; un conteneur ou une machine isolée ; des identifiants séparés ; et la protection de branche avec la CI obligatoire. Voir Ce qui est imposé, ce qui est suggéré.
Vous avez compris si vous savez
- dire pourquoi un fichier d'instructions ne contraint pas, et nommer ce qui contraint ;
- prédire le verdict d'une commande, puis relier un garde à son entrée dans
settings.json; - ajouter une règle de projet et montrer qu'elle change un verdict ;
- écrire l'en-tête d'une skill et lire l'échec d'un test ;
- dire ce qu'aucun de ces garde-fous ne garantit, et quelle barrière complète.
Pour les consignes de chaque outil, les documentations officielles font foi : mémoire et instructions, hooks, réglages. Les commandes et les noms de réglages évoluent avec les versions : vérifiez avant une séance.