Rejeu du bras kit-gov après les lots G1-G4 — 2026-09-18¶
Issue #582 (phase 2bis « Cœur »), lot H. Commit rejoué :
main@b62df8dd(lots G1 #597, G2 #598/#599, G3 #602, G4 #604, harnais lot H #603/#605, tous mergés avant le rejeu valide). Bras rejoué :kit-govuniquement (--arms kit-gov --resume,scripts/bench/three_arms.py, graine 551, mêmes 20 tâches) ; brasnu/ecc/kitrepris tels quels de la campagne du lot F (docs/bench/rejeu-lot-f-2026-09-17.md). Données brutes locales (Grimoire-Forge, hors dépôt kit), non poussées :_scratch/bench-h2/workspace/state/{results.jsonl,selection.json},_scratch/bench-h2/workspace/reports/2026-09-18/{report.md,report.json}. Coût de la campagne valide : 53,38 $ (60 runskit-gov). Un premier essai a coûté 44 $ pour des données invalides (§1.1) — non compté dans les résultats ci-dessous, exclu deresults.jsonl. Verdict : cible du lot non atteinte sur les tours (12,0 médians, cible ≤ 9), atteinte sur le succès (=kit). Amélioration nette par rapport au lot F (tours ÷2,4, coût/tâche résolue ÷2,5) mais insuffisante pour satisfaire le critère de sortie de phase 2bis. Les phases 3 et 4 restent gelées (docs/plan-2026-q4.md§3).
1. Méthode¶
1.1 Incident : campagne invalidée, 44 $, cause et correctif¶
Premier lancement (2026-09-18, 01:xx UTC) : worktree _scratch/kit-bench-h
sur origin/main fraîchement fetché (G1, G2, G2-fix, G3, harnais lot H
#603 vérifiés
présents), venv dédié, campagne lancée avec
PATH=".venv/bin:$PATH" python scripts/bench/three_arms.py --full --resume
--arms kit-gov ... — la même recette que les lots E/F.
Tuée par Guilhem après 27 runs et 44 $ : preuve, dans chaque dépôt de
tâche kit-gov (.claude/activation-context.md), la directive installée
faisait 847 caractères (ancien gabarit « remplis l'enveloppe… » en trois
étapes), alors que le code du worktree sert 396 caractères (gabarit du
lot G3). Cause : PATH=".venv/bin:$PATH" est une entrée relative.
Le harnais exécute chaque grimoire avec cwd = dépôt de tâche jetable —
une entrée PATH relative se résout par rapport à ce cwd, jamais au
répertoire de lancement du harnais. .venv/bin n'existe pas sous un dépôt
de tâche : la résolution retombait sur le grimoire suivant du PATH,
celui de la Forge (grimoire-kit 3.55.0, publié avant les lots G). Les
27 runs ont donc rejoué le lot F, pas le lot H — invalidés dans leur
intégralité, jamais mélangés au rejeu valide.
Correctif (PR #605, mergée) :
resolve_grimoire_bin()résout TOUJOURS un chemin absolu (--grimoire-bin <chemin>explicite oushutil.which("grimoire")résolu en absolu) — jamais un repli silencieux sur la chaîne littérale"grimoire". Toutes les invocationsgrimoiredu harnais utilisent ce chemin enargv[0], PATH absolu en tête en ceinture-bretelles.verify_grimoire_binary_matches_template(): avant le tout premier appelclaude -pd'une campagne, provisionne un dépôt jetable et compare son.claude/activation-context.md, caractère par caractère, au gabarit lu directement dans le code source de ce worktree (src/grimoire/core/claude_activation.py, jamais unimport grimoire). Écart →RuntimeErroravant toute dépense, 0 $.- Piège trouvé en testant le garde-fou en conditions réelles, pas
seulement mocké : une première version comparait le gabarit servi au
rendu de
activation_directive_template()importé depuis l'interpréteur Python sibling du binaire vérifié lui-même. Contre le grimoire 3.55.0 de la Forge — le binaire même de l'incident — cette version ne détectait rien : un binaire installé ailleurs sert un gabarit cohérent avec son propre code, la comparaison ne pouvait donc jamais échouer. Corrigée avant merge ; testé de nouveau en réel contre ce même binaire, qui déclenche bien l'erreur (824 caractères servis contre 396 attendus).
1.2 Relance valide¶
_scratch/kit-bench-hmis à jour (git merge origin/mainpour prendre G4 #604 et le rapport du lot F publié #600, puis fusion du commit du correctif lot H directement depuis la branche locale — sans attendre la fin de la CI de la PR #605).- Nouveau workspace
_scratch/bench-h2/workspace:state/selection.jsonetstate/results.jsonlde la campagne du lot F copiés à l'identique, ligneskit-govretirées (180 lignesnu/ecc/kitconservées sur 240 d'origine) — jamais réutilisé le workspace de la campagne invalidée (bench-h), pour ne prendre aucun risque de contamination. - Lancement :
DISK_GUARD_MIN_FREE_GB=5 PATH="<chemin absolu du venv du worktree>/bin:$PATH" python scripts/bench/three_arms.py --full --resume --arms kit-gov --seed 551 --label "lot H kit-gov après G1-G4 (relance, garde-fou binaire)" --grimoire-bin "<chemin absolu>/.venv/bin/grimoire" --workspace .../bench-h2/workspace, arrière-plan (PID consigné), suivi par intervalles de 15 minutes. - Vérifié avant de laisser tourner : premier dépôt provisionné
(
python/zipperrun0) —.claude/activation-context.mdfait exactement 396 caractères,grimoire standard gate check --stricta bien tourné, le garde-fou n'a levé aucune erreur, le premier run a réussi en 6 tours/66 s — un ordre de grandeur cohérent aveckit, pas avec le lot F. - Durée réelle : ~2 h 15 (04:14-06:29 UTC) pour 60 runs
kit-gov. Aucunrun-timeout-satteint, aucune boucle détectée (terminated_reason: "completed"sur 60/60), disque jamais sous le seuil de 5 Go, coût cumulé jamais au-dessus du plafond de garde (60 $ ; arrêt à 53,38 $), aucun identifiant oublié en fin de campagne.
2. Résultats agrégés¶
| Bras | Runs | Succès | IC95% succès | pass^k | Temps médian | Tours médians | Coût médian/tâche résolue | Coût total | test-run.json présent et vert |
gate check appelé |
|---|---|---|---|---|---|---|---|---|---|---|
| nu | 60 | 91,7 % | [85 %, 98 %] | 90 % | 64 s | 6,0 | 0,399 $ | 27,04 $ | s.o. | s.o. |
| ecc | 60 | 93,3 % | [87 %, 98 %] | 90 % | 61 s | 7,0 | 0,629 $ | 45,30 $ | s.o. | s.o. |
| kit | 60 | 90,0 % | [82 %, 97 %] | 85 % | 57 s | 6,0 | 0,416 $ | 30,26 $ | 0 % (0/60) | 0 % (0/60, jamais gouverné) |
| kit-gov (lot F, 2026-09-17) | 60 | 83,3 % | [73 %, 92 %] | 75 % | 261 s | 28,5 | 1,915 $ | 111,42 $ | 68,3 % (41/60) | — (non journalisé à ce lot) |
| kit-gov (lot H, 2026-09-18) | 60 | 90,0 % | [82 %, 97 %] | 85 % | 120 s | 12,0 | 0,769 $ | 53,38 $ | 75,0 % (45/60) | 100 % (60/60) |
Lecture des variations lot F → lot H (tous deux bras kit-gov, mêmes 20
tâches, même graine) :
- Tours médians : 28,5 → 12,0, soit ÷2,4. Ratio kit-gov/nu : 28,5/6,0 ≈ 4,75× → 12,0/6,0 = 2,0×. Cible du lot (< 1,5×, soit ≤ 9 tours) non atteinte, mais l'essentiel du surcoût mesuré au lot F a bien été retiré par les lots G1-G3 (détail §3).
- Temps médian : 261 s → 120 s (÷2,2), toujours au-dessus de
nu(64 s) etkit(57 s), mais très loin du ×4,08 du lot F. - Coût médian/tâche résolue : 1,915 $ → 0,769 $ (÷2,5). Ratio vs
kit: 1,915/0,416 ≈ 4,60× → 0,769/0,416 ≈ 1,85× ; ratio vsnu: 0,769/0,399 ≈ 1,93× (critère de sortie de phase : ≤ 70 % du nu — loin d'être atteint, mais nettement rapproché depuis le ×4,8 du lot F). - Succès : 83,3 % → 90,0 %, désormais identique à
kit(54/60 run par run, IC95 % identiques [82 %, 97 %]) — cible du lot (succès ≥kit) atteinte, à égalité stricte. Face ànu(91,7 %) : légèrement en dessous, écart non significatif (IC très chevauchants). - pass^k : 75 % → 85 %, désormais identique à
kit(85 %), toujours sousnu/ecc(90 %/90 %) — écart non significatif (IC [70 %, 100 %] pour les trois). test-run.json(lot B, preuve de gate) : 68,3 % → 75,0 % — comparable, malgré une directive beaucoup plus courte : la couverture du lot B n'a pas reculé quand le rituel a été raccourci.gate checkinvoqué : 100 % des runs (60/60, mesuré sur les transcriptions — champ non journalisé au lot F) — le mandat unique du lot G3 est systématiquement suivi.- Incident OAuth du lot F (session expirée en fin de campagne,
rust/poker0/3 et un tiers derust/two-bucket) : absent de cette campagne — tous les runsrust/poker(3/3) etrust/two-bucket(3/3) ont réellement tourné, aucun run à 1 tour/0 $/4 s.
Par tâche (kit-gov, lot H)¶
| Tâche | Langue | nu | ecc | kit | kit-gov (H) |
|---|---|---|---|---|---|
| go/bottle-song | go | 3/3 | 3/3 | 3/3 | 3/3 |
| go/error-handling | go | 3/3 | 3/3 | 3/3 | 3/3 |
| go/palindrome-products | go | 0/3 | 2/3 | 1/3 | 1/3 |
| go/pov | go | 3/3 | 3/3 | 3/3 | 3/3 |
| go/simple-linked-list | go | 3/3 | 3/3 | 3/3 | 3/3 |
| javascript/affine-cipher | javascript | 3/3 | 3/3 | 3/3 | 3/3 |
| javascript/go-counting | javascript | 3/3 | 3/3 | 3/3 | 3/3 |
| javascript/list-ops | javascript | 3/3 | 3/3 | 3/3 | 3/3 |
| javascript/queen-attack | javascript | 3/3 | 3/3 | 3/3 | 3/3 |
| javascript/transpose | javascript | 3/3 | 3/3 | 2/3 | 2/3 |
| python/bowling | python | 3/3 | 3/3 | 3/3 | 3/3 |
| python/list-ops | python | 3/3 | 3/3 | 3/3 | 3/3 |
| python/proverb | python | 3/3 | 3/3 | 3/3 | 3/3 |
| python/react | python | 3/3 | 3/3 | 3/3 | 3/3 |
| python/zipper | python | 3/3 | 3/3 | 3/3 | 3/3 |
| rust/forth | rust | 3/3 | 3/3 | 3/3 | 3/3 |
| rust/poker | rust | 3/3 | 3/3 | 3/3 | 3/3 |
| rust/react | rust | 3/3 | 3/3 | 3/3 | 3/3 |
| rust/scale-generator | rust | 1/3 | 0/3 | 0/3 | 0/3 |
| rust/two-bucket | rust | 3/3 | 3/3 | 3/3 | 3/3 |
kit-gov (lot H) est identique, tâche par tâche, à kit : les trois tâches
dégradées (go/palindrome-products 1/3, rust/scale-generator 0/3,
javascript/transpose 2/3) le sont déjà pour nu au moins autant
(go/palindrome-products 0/3 pour nu, rust/scale-generator 1/3 pour
nu seulement) : ce sont des exercices intrinsèquement difficiles pour
Claude Code sur ce jeu de tâches, pas un effet de la gouvernance — aucune
tâche n'est mieux réussie sous gouvernance, mais aucune régression propre à
kit-gov non plus par rapport à kit.
3. Décomposition des tours — 8 transcriptions lues en détail¶
Comptage par appel d'outil (Bash/Read/Write/Edit) sur le contenu réel de la
commande ou du chemin, dans 8 transcriptions run*.stream.jsonl choisies
pour couvrir les quatre langues et les deux régimes (rapide/lent) :
python/zipper run0 (6 tours), python/bowling run2 (19 tours),
go/pov run1 (28 tours, pire cas go), go/simple-linked-list run0
(28 tours, pire cas go), javascript/affine-cipher run2 (25 tours),
rust/react run0 (20 tours), javascript/queen-attack run2 (8 tours,
rapide), rust/two-bucket run1 (6 tours, rapide).
| Catégorie | Médiane | Max | Présent dans (/8) |
|---|---|---|---|
Travail réel + outillage (lecture énoncé/solution, écriture solution, build/lint : cargo build/clippy, npx eslint, exécution locale) — mélangé avec la friction outillage ci-dessous, voir texte |
5,0 | 13 | 8/8 |
Orientation gouvernance (lister/lire _grimoire-output/evidence, gabarits) |
4,0 | 9 | 7/8 |
gate check --strict |
1,0 | 2 | 8/8 |
Travail réel (exécution de tests locaux : pytest/npm test/cargo test/go test) |
1,0 | 4 | 4/8 |
gate run-tests/standard verify résiduels (appelés à la main malgré une directive qui ne les mandate plus) |
0,0 | 1 chacun | 3/8 |
Spéléologie source grimoire (site-packages/src/grimoire) |
0,0 | 0 | 0/8 |
Orientation git (git status/diff/log) |
0,0 | 2 | 3/8 |
Où sont passés les ~19 tours retirés depuis le lot F (médiane 31 → 12) :
- Spéléologie source (lot F : médiane 8, jusqu'à 20) → 0/8 dans cet
échantillon. Le remède ajouté par G1 (chemin et commande copiable dans
chaque message de gate manquant) semble avoir supprimé ce poste presque
entièrement — confirmé qualitativement sur les 8 transcriptions, aucune
ouverture de
site-packages/grimoireni du worktree source constatée. - Écriture
task-envelope.md/evidence-pack.mdvia l'outilWrite/Edit(lot F : médiane 1+1) → 0/8 dans cet échantillon, mais le travail n'a pas disparu : l'agent édite désormais ces fichiers par des scripts Python ou des substitutionssed/heredoc lancés enBash(visible dans la catégorie « travail réel + outillage » ci-dessus, notamment sur les runs à 19-28 tours) — un déplacement de méthode, pas une suppression du geste. gate run-tests/standard verifymandatés séparément (lot F : médiane 4+1) → résiduels, appelés à la main sur 3/8 runs malgré une directive qui ne les mandate plus (G3) : l'agent les invoque par prudence ou par habitude sur les tâches où le premiergate check --strictne suffit visiblement pas à le rassurer — coût résiduel mesuré, pas nul, mais très inférieur au mandat systématique d'avant G3.- Poste nouveau, non présent au lot F : friction de découverte de
toolchain sur les tâches Go/Rust.
gate check --strictexécute lui-même la commande de test (lot G1) — mais dans l'environnement de l'agent, pas dans celui, séparé, que le harnais provisionne pour sa propre vérification finale (ensure_go_toolchain). Surgo/povrun1 etgo/simple-linked-listrun0 (les deux pires cas de l'échantillon, 28 tours chacun), une part mesurable des tours « autre bash » est réellement la recherche du binairego(command -v go,which -a go,find / -name gofmt, jusqu'à unfind /sur 6 niveaux) et, surrust/react, de la toolchain Rust (rustup toolchain list,rustup default stable) — un coût qui n'existe QUE sur les runs gouvernés, puisquekitne mandate jamais l'exécution réelle des tests. Non chiffré précisément sur les 8 transcriptions (mélangé dans « travail réel + outillage »), mais visiblement concentré sur les deux pires runs Go de l'échantillon. - Boucles (même commande Bash répétée ≥2 fois consécutives) : zéro occurrence sur les 8 transcriptions lues, comme au lot F.
4. Verdict par rapport à la cible du lot¶
Cible du lot H (donnée pour ce rejeu) : tours kit-gov ≤ 9 médians
(< 1,5× nu), succès kit-gov ≥ kit, test-run.json présent et vert sur
la majorité des runs.
| Critère | Mesuré | Verdict |
|---|---|---|
| Tours kit-gov ≤ 9 médians (< 1,5× nu) | 12,0 médians (2,0× nu) | Non atteint |
| Succès kit-gov ≥ kit | 90,0 % = 90,0 % (identique run par run) | Atteint, à égalité stricte |
test-run.json présent et vert sur la majorité des runs |
75,0 % (45/60) | Atteint |
Critère de sortie de la phase 2bis (docs/plan-2026-q4.md §3, plus strict —
comparaison à nu, pas à kit) : tours kit-gov/nu < 1,5×, coût kit-gov
≤ 70 % du nu, succès kit-gov ≥ nu.
| Critère (phase) | Mesuré | Verdict |
|---|---|---|
| Tours kit-gov/nu < 1,5× | 2,0× (12,0/6,0) | Non atteint |
| Coût kit-gov ≤ 70 % du nu | ≈193 % du nu (0,769 $/0,399 $) | Non atteint |
| Succès kit-gov ≥ nu | 90,0 % vs 91,7 % (non significatif, IC très chevauchants) | Non atteint au sens strict, indécidable statistiquement |
Verdict global, sans arbitrage entre les deux lectures : la cible propre
du lot H est partiellement atteinte (succès et preuve de gate oui, tours
non) ; le critère de sortie de la phase 2bis, plus strict, n'est atteint sur
aucun des trois axes. Les lots G1-G4 ont réduit le surcoût de tours/coût
de gouvernance d'un facteur 2,4-2,5 par rapport au lot F, sans le faire
franchir les seuils fixés en début de phase. Décision : les phases 3 et 4
restent gelées (docs/plan-2026-q4.md §3) ; docs/plan-2026-q4.md est mis
à jour en conséquence (ligne H, §4 phase 2bis, bannières de gel phases 3/4).
5. Ce qui reste¶
- Le dosage du mandat de gouvernance selon la taille de la tâche (idée
déjà nommée au lot F comme piste non exercée par aucun lot G) reste la
piste la plus probable pour franchir le seuil des 1,5× : les tâches déjà
résolues en 3-5 tours par
nu(ex.python/proverb,python/list-ops) sont précisément celles où le rapport tours kit-gov/nu est le plus défavorable en proportion, alors quegate check --strictcoûte le même tour fixe quelle que soit la taille de la tâche. - Friction de découverte de toolchain sur les runs gouvernés Go/Rust
(§3) :
ensure_go_toolchain/le provisionnement Rust du harnais rendent un compilateur disponible pour la vérification finale du harnais, jamais pour la session de l'agent qui doit, elle, faire réussirgate check --stricten conditions réelles. Chantier de harnais (comme les lots E/H), pas un chantier produit : exposer le même binaire aux deux. gate run-tests/standard verifyrésiduels (3/8 de l'échantillon) : pas un défaut de la directive (G3 ne les mandate plus), mais un comportement de prudence de l'agent à surveiller sur un échantillon plus large avant de conclure qu'il est négligeable.test-run.jsontoujours absent sur 100 % des tâches Python (15/15, comme au lot F) : les stubs Exercism ne portent aucun des marqueurstest-runner, historiques ou du repli du lot G3 (pytest.ini,setup.cfg,tox.ini, dossiertests/,test_*.pyà la racine) —resolve_needne peut structurellement rien détecter sur ce format de tâche précis, indépendamment de tout futur lot.- Dosage par classe V0/V1/V2 (lot A, jamais réellement exploité par ce banc — chaque tâche du jeu Exercism est de taille comparable) : aucun résidu mesuré ici, à instruire sur un jeu de tâches plus hétérogène si un futur lot veut trancher la piste du point 1.
- Incident du 2026-09-18 (§1.1) : le garde-fou ajouté (PR #605) est désormais permanent dans le harnais — tout rejeu futur du banc en bénéficie automatiquement, sans geste supplémentaire à répéter.