Doc : cahier de la v0.9.0, hooks stricts et contrat de MIAM pour la délégation

- Ajout de `docs/hooks-delegation.md`
- État de MIAM et des hooks, raisons de leur échec
- Treize hooks avec priorités, découpage atomique (`.claude/miam-plan.json`)
- Contrôle automatique des réponses (`controle`, `verdict`) et reprise sans suivi répété
- Section 11 : contrat livré par MIAM (PH2-H11) : paramètres, réponses et états `fin`, commande `reprise`
- Sept bouts à coder dans le plugin pour la v0.9.0, réveil après 11 min vérifié

Aucun code : spécification, seuils à confirmer.
This commit is contained in:
ange committed 2026-11-03 09:09:16 +01:00
1 parent 7841bcd89f
commit 7d3885a735
2 files changed
+412

No files matched your search

+64
View File
@@ -0,0 +1,64 @@
# À faire pour la prochaine version du plugin
Notes issues de la session MIAM du 2026-11-01. Rien n'est encore appliqué.
## 1. Autoriser le repli sur un subagent Haiku (demande de l'utilisateur)
`plugins/miam/skills/deleguer/SKILL.md`, ligne 24, dit « Pas de subagent ». Vérifier aussi le texte équivalent injecté
par les hooks et la documentation (`docs/agent-delegue.md`, `plugins/miam/README.md`).
Nouvelle règle, ordre de délégation :
1. MIAM d'abord, pour les tâches de son champ.
2. Subagent `model: "haiku"`, sans demander, pour une tâche longue et simple si MIAM est arrêté ou mal adapté : lire un
gros document ou une longue sortie pour en tirer peu, recherche large dans le code, tests répétitifs.
3. Claude principal pour ce qui demande le contexte de la conversation, pour la sécurité et pour la dépense.
Pas de subagent pour une petite tâche : un `grep` ou une lecture directe coûte moins cher qu'un agent qui repart de zéro.
À décider : le coût d'un subagent Haiku doit-il entrer dans les événements `pilotage` et dans le cumul, et comment le
distinguer d'un pilotage de MIAM ?
## 2. Vérifier avant de déléguer, dresser le bilan après
À ajouter au skill `deleguer` :
- **Faisable ?** OpenCode ne voit que le code commité et n'a pas de `.venv` utilisable : ne pas lui demander les tests.
- **Le preset tient ?** Appeler `modeles` avant d'imposer un preset.
- **Rentable ?** Pas de délégation si la consigne dépasse un tiers de la sortie attendue, ou si une recherche suffit.
- **Indépendant ?** Ne pas importer un module pas encore livré : MIAM peut tourner depuis le dépôt qu'il modifie.
- **Après un lot :** bilan des délégations, sans attendre la demande : coût, résultat, faute, règle ajoutée.
## 3. Rendre le cumul fiable (PH2-H10 de MIAM)
Le cumul mesure le volume, pas la justesse. Sur la session, 124 625 tokens évités sur 139 707 étaient fictifs.
- Ne compter aucun gain pour une réponse vide ou tronquée.
- Pouvoir marquer une délégation fausse comme rejetée, pour annuler son gain et compter sa vérification.
- Ne pas compter comme évitée une lecture que Claude n'aurait pas faite, comme une relecture imposée au démarrage.
## 4. Imposer les règles par des hooks
La mémoire et les skills sont du texte que Claude peut ignorer : la règle « un seul suivi » était notée et il y a eu plus
de 15 `suivre`. Ne garder en texte que ce qui demande du jugement ; ce qui se mesure passe dans `tool.call`, à côté de
`contrainte.ts`, en fonctions pures testées.
À vérifier d'abord : `tool.call` peut-il refuser un appel avant son exécution ? Les rappels actuels arrivent après coup.
| Contrôle | Déclencheur | Action |
|---|---|---|
| Suivi | `suivre` à moins de N secondes du précédent, ou Nᵉ appel sur la même demande | Refus, avec le délai à respecter |
| OpenCode et tests | `deleguer(moteur="opencode")` dont la consigne demande de lancer pytest | Refus, avec la raison |
| Preset | `deleguer(preset=…)` sans `modeles` dans la session | Refus |
| Rentabilité | Consigne de plus de N caractères | Rappel, le seuil reste à régler |
| Sortie longue | Lecture ou commande non filtrée | Rappel avant l'appel si possible, sinon après |
| Fin de lot | Fin de tour après des délégations | Injection du bilan mesuré : coût, échecs, reprises |
Hors de portée d'un hook, à garder en texte : juger si une réponse est juste, ou si un `grep` suffisait.
Chaque refus doit dire quoi faire à la place et se compter, pour régler les seuils sur des chiffres réels.
## 5. Relecture de démarrage
Si elle vient du plugin, la relecture de `plan/Plan.md` et `plan/tasks-P2.md` à chaque session coûte 62 000 tokens lus et
a donné une réponse vide puis une réponse fausse. La remplacer par une consigne ciblée sur la demande en cours, ou par une
recherche.
+348
View File
@@ -0,0 +1,348 @@
# Forcer la délégation par les hooks
État au 2026-11-02, base de travail du plugin. But : que Claude pilote et que MIAM, l'API ou Haiku écrivent.
Claude découpe, confie, contrôle par des machines, et n'écrit lui-même que ce qui ne peut pas être confié.
## 1. Constat
**MIAM (v2.5.2-dev, phase 2)** sait déjà router : modèle local (27B, 15,5 Go, un seul à la fois), API OpenRouter
(plafond 2 $/jour, ~0,0135 $ dépensés le 2026-11-02), OpenCode, et Haiku côté Claude. `moteur="auto"` choisit entre
local et API. Les moyens existent ; c'est Claude qui ne les utilise pas spontanément.
**Journal** : 185 délégations (165 ok, 10 refusées, 3 erreurs, 3 annulées, 4 rejetées), dont 9 API et 5 OpenCode.
Par jour : 103 (30/10), 52, 19, 12 (02/11, journée en cours). La baisse n'est pas rapportée au volume de travail : elle
alerte, elle ne prouve rien.
**Session du 2026-11-02, tâche PH2-D1** : seule la lecture imposée par le hook de démarrage a été déléguée. 8 tests
répétitifs écrits à la main alors que leur modèle était dans le fichier. Les délégations suivantes ont toutes suivi un
rappel de l'utilisateur.
### Pourquoi les hooks actuels n'ont pas suffi
| Mécanisme actuel | Ce qui a échappé |
|---|---|
| Refus de `Read` si fichier ≥ 20 000 caractères sans `limit` ≤ 400 | Contourné légalement : plusieurs plages de 75 à 240 lignes sur `instances.py`, `allow_large` sur des lignes géantes de `tasks-P2.md` |
| Retenue des sorties shell ≥ 20 000 caractères | Ne voit que la taille d'un appel, pas le cumul |
| Rappel texte (1 toutes les 10 min), consigne `prompt.compose`, mémoire | Coût nul pour Claude au moment de décider : ignorés |
| `Edit`, `Write`, `NotebookEdit` | **Aucun contrôle** : seule la comptabilité des corrections |
| Suivi « ce tour a délégué » | **Non présent** pour l'agent principal |
| Hooks de fin de tour et de soumission | **Non présents** |
Conclusion : une consigne perd face à la facilité. Il faut qu'écrire soi-même soit plus lent que déléguer, à l'instant
où l'écriture est tentée.
### Frictions côté délégation
- `deleguer` sans `moteur` échoue si aucun modèle n'est chargé ; il faut penser à `moteur="auto"`.
- Le bench occupe le GPU ; `auto` local peut le préempter (corrigé pour les refus, `093f110`, pas pour le reste).
- `plugin-claude-miam` n'est pas autorisé pour l'API, et la config affiche `projets: null` pour OpenRouter (à éclaircir).
- Un fichier hors projet (scratchpad) fait échouer le routage API.
- Rien n'oriente vers Haiku quand local et API sont indisponibles.
- Pas de contrôle automatique du résultat : Claude relit tout, ce qui annule une partie du gain.
## 2. Principes
1. **Refuser, pas rappeler.** Un `deny` au moment de l'écriture, avec l'appel prêt à copier dans le message.
2. **Budget cumulé, pas seuil par appel.** Compter lignes écrites et caractères lus par tour et par fichier.
3. **Découper avant d'écrire.** Pas de code sans liste de bouts.
4. **La machine juge, Claude arbitre.** Tests, ruff et contrat décident ; Claude ne lit qu'en cas d'échec.
5. **Une sortie de secours chère et visible.** Exception motivée, journalisée, affichée.
6. **Ne jamais bloquer le travail.** Si aucun exécutant ne répond, la règle s'ouvre et le signale.
## 3. Pistes de hooks
Seuils proposés, à régler sur les mesures.
| # | Hook | Déclencheur | Action | Priorité |
|---|---|---|---|---|
| 1 | **Portillon tests** | `Write`/`Edit` de l'agent principal sur `tests/**`, `*.test.*` ajoutant > 10 lignes | `deny` + appel `deleguer` prêt | P0 |
| 2 | **Budget d'écriture** | Cumul > 30 lignes de code ajoutées par l'agent principal depuis la dernière délégation réussie | `deny` jusqu'à une délégation ou une exception | P0 |
| 3 | **Portillon commit** | `git commit` sans message issu de `deleguer` dans le tour | `deny` | P0 |
| 4 | **Écritures par shell** | `Bash`/`PowerShell` avec `Set-Content`, `Out-File`, `>`, `sed -i`, Python écrivant un fichier | Comptés comme le hook 2 ; sinon le contournement est immédiat | P0 |
| 5 | **Budget de lecture** | Cumul par tour > 40 000 caractères, ou > 400 lignes par fichier, `allow_large` et `Grep` compris | `deny` + « confie la question » | P1 |
| 6 | **Portillon découpage** | Premier `Edit`/`Write` de code d'un tour sans plan | `deny` : écrire `.claude/miam-plan.json` (section 4) | P1 |
| 7 | **Sélecteur d'exécutant** | Tout `deny` ci-dessus | Message calculé : local si prêt et sans bench ; API si projet autorisé ; sinon subagent Haiku | P1 |
| 8 | **Bilan de fin de tour** | Fin de tour de l'agent principal | Injecte lignes écrites par Claude et par délégation ; bloque la fin si un bout du plan reste « délégable » non traité | P2 |
| 9 | **Compteur visible** | Ligne d'état | `délégué 82 % · exceptions 1` pour le tour et la session | P2 |
| 10 | **Docs et docstrings** | `*.md` hors `plan/`, docstrings ajoutées en lot | Comme le hook 1 | P2 |
| 11 | **Attente unique** | `deleguer` en file (`ok: null` avec `reprise`) ; commande `/attendre` lancée sans `run_in_background` | Injecte « lance `reprise` en arrière-plan » ; `deny` sur l'appel au premier plan | P0 |
| 12 | **Pas de suivi pendant l'attente** | `suivre` sur une demande attendue, `sleep` ou boucle d'attente en `Bash` | `deny` : « tu seras réveillé à la fin » ; permis si `fin` vaut `besoin_claude` | P0 |
| 13 | **Confiance au verdict** | `Read` complet du fichier `sortie` après `fin: "valide"` | `deny` hors chemins sensibles : aperçu ou `offset`/`limit` | P1 |
**Hors comptage** : fichiers écrits par MIAM (déjà mesurés en corrections), `plan/**` et journal de suivi,
modifications ≤ 3 lignes, subagents (`agentId` défini), config locale.
**Exception** : outil `exception(raison, fichiers, lignes)` avec motifs fermés (`concurrence`, `securite`,
`debogage-multi-fichiers`, `secret`). Elle ouvre un budget limité aux fichiers cités, est journalisée et apparaît dans le
bilan. Pour la concurrence, liste de chemins sensibles à définir plutôt qu'un jugement de Claude.
**Repli** : si aucun des trois exécutants ne répond, règle ouverte pour le tour et toast. Réutiliser `joignable`.
**Options** : garder `imposer` et `incitation`, ajouter `budget_ecriture`, `budget_lecture`, `chemins_sensibles`.
### À vérifier avant de coder
- Existence d'un événement de fin de tour de l'agent principal capable de relancer Claude (`turn.complete` n'est
utilisé que pour les subagents).
- Capacité de `tool.call` à lire le contenu de `Write`/`Edit` avant exécution pour compter les lignes.
- Possibilité de déclarer `exception` : outil MCP côté MIAM, ou fichier lu par le hook.
## 4. Découper en bouts atomiques
Plan attendu par le hook 6 :
```json
{ "tache": "PH2-D1", "bouts": [
{ "id": 1, "quoi": "tests start_all", "fichier": "tests/x.py", "moteur": "auto", "modele": "tests/x.py:613-637",
"contrat": "4 instances, ordre d, b, c, erreurs par nom", "controle": "pytest -k start_all" },
{ "id": 2, "quoi": "ordre des réservations dans start_instance", "moteur": "claude", "motif": "concurrence" }
] }
```
Un bout délégable : **une** fonction ou un groupe de tests, **un** fichier de sortie, ≤ 80 lignes, signature donnée,
exemple à imiter en plage de lignes, contrôle exécutable. Consigne en anglais, langue de sortie précisée. Modules
joints, car le 27B invente champs et valeurs.
Relecture de D1 avec ce découpage :
| Bout | Exécutant possible |
|---|---|
| Choix du port réservé, messages d'erreur, `start_all`, `stop_all` à partir de leur contrat | Délégable, contrôlé par tests |
| 8 tests, tests du view model | Délégable, modèles présents |
| Boutons du dashboard, branchement du tray | Délégable, patron existant |
| Lignes de suivi, message de commit | Délégable |
| Absence d'`await` entre contrôle et réservation dans `start_instance` | Claude (concurrence) |
| Inventaire des fichiers à modifier | Délégable ; Claude lit le résultat |
Claude n'aurait gardé que le découpage, le point de concurrence et l'intégration.
## 5. Faire confiance aux retours
Contrôle par niveaux, du moins cher au plus cher :
1. **Automatique** : `format="code"`, `sortie=`, `retour="apercu"` ; pytest, ruff, `ruff format --check`.
2. **Test rouge d'abord** : le test échoue sans le code, passe avec.
3. **Taille du diff** : conforme au bout annoncé.
4. **Lecture par Claude** : seulement si un contrôle échoue, ou en zone sensible.
En cas d'échec, renvoyer la sortie du test à l'exécutant (2 essais) avant toute reprise par Claude.
**Pièges connus du 27B à garder en contrôle** : docstrings omises, valeurs inventées ; ajouter docstrings et champs
aux contrôles plutôt qu'à la relecture.
**Risque accepté** : des erreurs passeront les tests. La parade est de meilleurs contrats, avec la revue par Claude
réservée aux zones sensibles et au diff final.
## 5 bis. Contrôle automatique des réponses par MIAM
Livré dans MIAM (PH2-H11), sauf `commande` et `perimetre` ; contrat exact en section 11. **But** : Claude reçoit un verdict plutôt que le code ; MIAM juge, retente, escalade.
**Contrat de la demande** : nouveau paramètre optionnel `controle` sur `deleguer`.
| contrôle | s'applique à | ce qu'il vérifie |
|---|---|---|
| `format` | tous | JSON valide, schéma si fourni ; Python via `ast.parse`, TypeScript via transpile uniquement |
| `complet` | tous | non vide, non tronqué, symboles demandés présents (fonctions, nombre de tests) |
| `symboles` | code | chaque identifiant importé ou appelé existe dans les modules joints, pour attraper les champs inventés (faiblesse connue du 27B) |
| `docstrings` | code | fonctions publiques documentées (faiblesse connue du 27B) |
| `lint` | code | `ruff check` et `ruff format --check`, ou l'équivalent du projet |
| `commande` | code | commande de test lancée dans un clone à l'intérieur du bac à sable AppContainer existant, sans réseau, commandes limitées à celles déclarées par projet |
| `perimetre` | code | seuls les fichiers déclarés changent |
| `citations` | extraction, résumé | toute valeur, nombre ou identifiant cité doit se retrouver dans les fichiers source |
| `traduction` | traduction | placeholders, code et nombres conservés, ratio de longueur plausible |
**Défauts par type** : `format` + `complet` toujours ; code ajoute `symboles`, `docstrings`, `lint` ; extraction ajoute `citations` ; `commande` seulement si fourni.
**Boucle** : en cas d'échec, MIAM renvoie l'échec exact au modèle, 2 tentatives maximum, puis escalade local → API → rend la main à Claude avec statut `echec_controle`, sans accepter silencieusement.
**Réponse à Claude** : champs `verdict` (`valide`, `echec_controle`, `non_controle`), `controles` (nom, résultat, message court), `essais`, `moteur_final`, `sortie` (chemin de fichier), stats de diff ; pas de corps de code quand `valide` et `retour="apercu"`.
**Application** : rien n'est écrit dans l'arbre de travail avant que tous les contrôles passent ; le résultat reste dans le clone ou un fichier temporaire ; puis écrit comme `sortie` déclarée.
**Sécurité** : la sortie du modèle est non fiable ; elle n'est jamais exécutée hors du bac à sable ; seules les commandes déclarées ; aucun secret dans le clone.
**Mesure** : le journal reçoit `controles`, `essais`, `verdict` ; `echec_controle` ne compte aucun gain ; le taux de réussite au premier essai par modèle alimente le choix de modèle (`type`/`seuil`) et le taux de réparation ; le temps passé dans les contrôles est suivi.
**Limites** : des tests écrits par le même modèle peuvent valider un comportement faux : exiger d'abord un test rouge (le test doit échouer sans le code) ou un test écrit séparément ; `citations` prouve la présence, pas le sens correct ; `commande` coûte du temps ; passer les contrôles ne remplace pas la revue de Claude sur les zones sensibles (concurrence, sécurité).
**Découpage en bouts** :
1. Contrôles purs (`format`, `complet`, `symboles`, `docstrings`, `citations`, `traduction`) avec leurs tests.
2. Contrôles outillés (`lint`, `perimetre`) avec leurs tests.
3. Boucle de retentative et escalade local → API → `echec_controle`.
4. Contrôle `commande` dans le bac à sable, sur clone, sans réseau.
5. Champs de réponse (`verdict`, `controles`, `essais`, `moteur_final`, `sortie`, stats de diff) et écriture différée.
6. Journal (`controles`, `essais`, `verdict`, temps) et plugin (le hook lit `verdict` et saute la lecture quand `valide`).
### Reprise de la main sans suivi répété
Livré dans MIAM (PH2-H11), sauf le chien de garde. `miam-attendre` est devenu la commande `curl` donnée dans `reprise` : aucun exécutable à installer. Contrat exact en section 11.
Problème : `suivre` plafonne chaque attente à `call_wait_s` (45 s), donc une tâche longue fait revenir Claude toutes les 45 s, et chaque retour coûte des tokens.
Principe : le temps n'est pas une limite, le seul coût est les tours de Claude. MIAM exécute contrôles, corrections et escalade seul. Claude est réveillé une fois, à un état terminal.
**Attente unique** : Claude lance une fois, en arrière-plan via `Bash` avec `run_in_background`, une petite commande `miam-attendre <demande_id>`. Elle bloque sur une route HTTP locale authentifiée de MIAM (long polling), imprime une ligne JSON, sort. Claude Code réveille Claude quand une commande d'arrière-plan se termine : aucun token pendant l'attente. Claude termine son tour ou continue autre chose.
**États terminaux** :
| état | sens | ce que fait Claude |
|---|---|---|
| `valide` | tous les contrôles passés, fichier écrit | intègre, sans lecture |
| `echec_controle` | tentatives et escalade épuisées ; le rapport donne le dernier échec | reprend la main ou re-découpe |
| `besoin_claude` | décision réservée à Claude : permission, plafond de dépense API atteint, instruction ambiguë | répond puis attend à nouveau |
| `annule` | demande annulée | rien |
| `perdue` | MIAM redémarré ou injoignable | renvoie ou fait lui-même |
**Pas de limite de durée, mais pas d'attente sans fin** : pas de timeout total. Un chien de garde détecte l'absence de progrès : aucun token généré, aucun contrôle terminé et aucun changement de tentative pendant `mcp.sans_progres_s` (proposé 600 s) donne `besoin_claude` avec l'étape bloquée. Les tentatives sont bornées (2 par moteur, puis escalade), donc la boucle se termine toujours. `miam-attendre` sort en `perdue` après plusieurs échecs de contact avec MIAM.
**Hook du plugin** : tant que `miam-attendre` tourne pour une demande, `suivre` sur cette demande est refusé avec le message « tu seras réveillé à la fin » ; idem pour les boucles de `sleep` en `Bash`. Le hook part du `demande_id` renvoyé en « en_file ».
**Réponse de `deleguer`** : si non terminé dans `call_wait_s`, la réponse donne `demande_id`, `etat` et `reprise` : la commande exacte à lancer.
**Autres clients** : un client sans commandes d'arrière-plan (OpenCode) garde `suivre`, avec un `attente_s` long unique, plus plafonné à 45 s, dans la limite du timeout du client MCP, à vérifier.
**Coût attendu** : un lancement et un réveil par délégation, indépendamment de la durée.
**Bouts** :
1. États terminaux et chien de garde en logique pure, avec tests.
2. Route de long polling authentifiée.
3. Commande `miam-attendre`, avec tests.
4. Champ `reprise` dans `deleguer`.
5. Hook du plugin refusant `suivre`.
## 6. Évolutions côté MIAM
| Évolution | Effet |
|---|---|
| `moteur="auto"` par défaut, repli explicite si tout est indisponible | Supprime l'erreur « aucun modèle chargé » |
| `routage` qui peut répondre « subagent Haiku » | Échelle complète local → API → Haiku |
| Délégation courte pendant un bench : API plutôt que préemption | Ne coupe plus les tests |
| Projet du plugin autorisé pour l'API ; clarifier `projets: null` | Lève le refus rencontré |
| `deleguer(..., controle="pytest -k x", essais=2)` : exécute et renvoie l'échec au modèle | Niveau 1 sans Claude, détail en 5 bis |
| `lot=[...]` : plusieurs bouts en un appel | Moins de tours de pilotage |
| Écriture directe dans le dépôt avec diff court en retour | Claude ne reçoit pas le code |
## 7. Limites
- Un hook contrôle des appels, pas le raisonnement : Claude peut toujours rédiger puis découper artificiellement.
- Chaque délégation coûte une consigne et un suivi : sous ~10 lignes, écrire peut être moins cher, d'où les seuils.
- Des seuils trop stricts multiplieront les exceptions : les suivre dès le début.
- Le hook 6 ajoute un tour au départ de chaque tâche ; il doit rester sans objet pour une correction minime.
## 8. Ordre de travail
Chaque lot est découpé en bouts confiés à MIAM : le plugin sert de premier terrain d'essai.
1. **Lot 1 (P0)** : compteurs par tour, hooks 1, 2, 3, 4, exception, repli. Vérifier d'abord les points de la section 3.
2. **Lot 2 (MIAM)** : `auto` par défaut, API pendant le bench, projet du plugin.
3. **Lot 3 (P1)** : budget de lecture, plan, sélecteur d'exécutant.
4. **Lot 4** : `controle=`, `lot=`, écriture directe.
5. **Lot 5 (P2)** : bilan, compteur, documentation.
**Mesures** par session : part des lignes écrites par délégation, net de tokens, taux de reprise, nombre d'exceptions par
motif, refus jugés à tort. Objectif proposé après le lot 1 : ≥ 70 % des lignes hors zones sensibles.
## 9. Décisions attendues
- Seuils de départ : 10 lignes pour les tests, 30 pour le code, ou plus strict.
- Mode : refus immédiat, ou une semaine en mesure seule pour régler les seuils (recommandé : refus immédiat sur les
tests et le commit, mesure sur le reste).
- Liste des chemins sensibles exemptés.
- Part acceptable de Haiku dans l'échelle, à comparer au coût de l'API.
## 10. Version cible : plugin v0.9.0
**But** : une version dont les gains de tokens sont réels et mesurés, parce que Claude délègue par défaut.
**Valeurs retenues par défaut** (issues de la section 9, modifiables) : 10 lignes pour les tests, 30 lignes pour le code, refus immédiat sur tous les hooks P0, budget de lecture de 40 000 caractères par tour et 400 lignes par fichier ; chemins sensibles : `src/miam/services/mcp.py`, `src/miam/services/instances.py`, `src/miam/core/aio.py` plus tout ce qui correspond à `*sandbox*`, liste à confirmer.
**Périmètre plugin** (checklist) : compteurs de tour (lignes écrites par l'agent principal, caractères lus, dernière délégation réussie) ; hooks 1, 2, 3, 4 (section 3) ; hook 5 budget de lecture ; hook 6 plan de découpage ; hook 7 sélecteur d'exécutant ; `exception` avec raisons fermées ; repli quand aucun exécutant ne répond ; refus de `suivre` pendant `miam-attendre` ; rapport de fin de tour et compteur visible (hooks 8, 9) ; skill `deleguer` mis à jour pour enseigner le découpage, `moteur="auto"` et `reprise`.
**Prérequis MIAM** (PH2-H11, même livraison) : `moteur="auto"` par défaut ; API au lieu de la préemption pendant un benchmark ; projet plugin autorisé pour l'API ; `controle` avec contrôles purs et `lint` ; boucle de correction et escalade ; états terminaux, `miam-attendre`, `reprise`, chien de garde `mcp.sans_progres_s` ; `verdict`. `lot=`, écritures directes et `commande` en sandbox peuvent suivre plus tard sans bloquer la v0.9.0.
**Vérifications préalables** (bloquantes) : les trois points de « À vérifier avant de coder » en section 3 ; durée maximale de blocage tolérée par un client MCP. Fait le 03/11/2026 : une commande en arrière-plan de 11 min réveille bien Claude à sa sortie, sans token pendant l'attente.
**Critères d'acceptation** (checklist, chacun testable) : une édition de test de l'agent principal de plus de 10 lignes est refusée avec un appel prêt ; une écriture via le shell est comptée ; un `git commit` sans message délégué est refusé ; une tâche découpée en plusieurs morceaux lisant le même fichier dépasse le budget de lecture et est refusée ; tous les exécutants étant indisponibles, le travail continue et un toast le signale ; une délégation longue coûte un lancement et un réveil, sans appel à `suivre` ; un `echec_controle` rend la main avec le dernier échec ; les résultats `valide` sont intégrés sans que Claude lise le corps du code ; les exceptions apparaissent dans le rapport ; les sous-agents ne sont pas soumis aux budgets ; tous les tests du plugin passent.
**Mesure du gain** : gain réel = tokens Claude effectivement consommés, lus depuis `session.usage`, pas les `evites` estimés. Protocole : rejouer des tâches comparables (une fonction pure avec tests, une tâche UI multi-fichiers, une extraction de document) sur v0.8.0 et v0.9.0, mêmes prompts ; enregistrer tokens Claude totaux, coût cloud, durée, corrections, exceptions, actions refusées à tort, bugs trouvés ensuite. Comparer aussi `net` (`evites - pilotage - corrections`) à la différence mesurée pour calibrer l'estimation. Version valide seulement si les tokens Claude mesurés baissent sans hausse des bugs post-merge ; la cible est fixée après la première comparaison, aucun chiffre décidé maintenant.
**Ordre des bouts** :
1. vérifications ;
2. compteurs avec tests ;
3. hooks P0 et exception ;
4. prérequis MIAM ;
5. budget de lecture, plan, sélecteur ;
6. rapport de fin de tour et compteur ;
7. skill et docs ;
8. mesure puis release v0.9.0.
Chaque bout suit la section 4 et est délégué.
## 11. Contrat livré par MIAM (PH2-H11) et travail restant côté plugin
État au 03/11/2026, code de MIAM non commité, tests verts. Le MIAM installé doit être reconstruit pour l'exposer.
### Ce que MIAM accepte
`deleguer(..., controle=[...], attendus=[...])` :
- `controle` : liste parmi `syntaxe`, `complet`, `symboles`, `docstrings`, `citations`, `traduction`, `lint`. `[]` désactive les contrôles.
- Par défaut : `format="code"` avec une `sortie` en `.py` donne `syntaxe`, `complet`, `symboles`, `docstrings`, `lint` ; tout le reste donne `complet`.
- `attendus` : noms qui doivent figurer dans la réponse (fonctions, classes), vérifiés par `complet`.
- `lint` : `ruff check --select F,E9` seulement (vraies erreurs, pas le style) ; ignoré si ruff est introuvable (cas de l'exe).
- `symboles` et `citations` comparent la réponse aux fichiers joints : joindre les modules utilisés.
- Tentatives : `mcp.controle_essais` (2) par moteur ; une demande locale passe ensuite à l'API.
Écarts avec la section 5 bis : `format` s'appelle `syntaxe` (Python seulement) ; `commande` et `perimetre` ne sont pas faits ; il n'y a ni `moteur_final` ni statistiques de diff.
### Ce que MIAM renvoie
| cas | réponse | `fin` (route `/attendre`) |
|---|---|---|
| terminé, contrôles passés | `ok: true`, `verdict: "valide"`, `controles: [{nom, ok, message?}]`, `essais`, `sortie` si demandée | `valide` |
| terminé sans contrôle | `ok: true`, `verdict: "non_controle"` | `termine` |
| contrôles échoués partout | `ok: false`, `erreur: "echec_controle : nom : message ; …"` | `echec_controle` |
| annulé | `ok: false`, `etat: "annulee"` | `annule` |
| demande inconnue (MIAM redémarré) | `ok: false`, sans `etat` | `perdue` |
| permission OpenCode, autre cas en attente | `ok: null` | `besoin_claude` |
| pas fini dans `call_wait_s` (45 s) | `ok: null`, `demande_id`, `etat`, `reprise`, `message` | (sans objet) |
`sortie` n'est écrite qu'après le verdict `valide`. Les tentatives rejetées sont journalisées dans `delegations.jsonl` avec `statut: "rejete"`, sans gain.
### Attente unique
`reprise` vaut exactement :
```
curl -s -H @"<logs>/mcp/attente.hdr" "http://127.0.0.1:<port>/attendre?demande_id=<id>"
```
- `attente.hdr` contient l'en-tête `Authorization`, écrit par MIAM au démarrage du serveur MCP : aucun jeton dans la commande ni dans la conversation.
- `GET /attendre` répond une seule fois, à l'état final : la même réponse que `suivre`, plus `fin`. Chaque attente interne dure au plus `ATTENTE_ROUTE_S` (3600 s) et reprend en boucle tant que la demande n'est pas finie.
- À lancer avec `Bash` et `run_in_background: true` (Git Bash sous Windows : la commande est en syntaxe POSIX). Vérifié le 03/11/2026 sur le vrai serveur HTTP.
### Encore manquant côté MIAM
- Chien de garde `mcp.sans_progres_s` : pas encore fait, car le modèle répond sans flux et rien ne mesure l'avancement. La seule limite reste `timeout_execution_s`, multiplié par le nombre de tentatives. Une très longue tâche peut donc finir en expiration (`ok: false`, `fin: "besoin_claude"` ou `perdue`).
- Contrôles `commande` (tests en bac à sable) et `perimetre`.
- `moteur="auto"` par défaut ; API au lieu de la préemption pendant un benchmark.
### À coder dans le plugin (v0.9.0), par bouts
1. **Hook PostToolUse sur `deleguer`** : si `ok` est `null` et qu'il y a une `reprise`, ajouter au contexte « lance `reprise` une fois avec `run_in_background`, puis termine ton tour ou fais autre chose », et noter `demande_id` comme en attente dans l'état du tour.
2. **Hook PreToolUse sur `Bash`** : reconnaître une commande qui contient `/attendre?demande_id=` ; sans `run_in_background`, la refuser (« lance-la en arrière-plan ») ; avec, passer la demande à « attendue ».
3. **Hook PreToolUse sur `suivre`** : refuser si la demande est « attendue », avec « tu seras réveillé à la fin ». Refuser aussi les `sleep` et les boucles d'attente en `Bash` pendant une attente. Laisser passer `suivre` si `fin` vaut `besoin_claude` (permission à donner).
4. **Hook sur la sortie de la commande d'arrière-plan** (ou PostToolUse sur la notification) : lire `fin` et injecter la conduite :
- `valide` : intégrer sans relire le corps, ne pas `Read` la `sortie` en entier ;
- `termine` : relire ;
- `echec_controle` : reprendre la main ou redécouper, avec le dernier motif ;
- `besoin_claude` : répondre (`autoriser`), puis relancer `reprise` ;
- `annule` : rien ;
- `perdue` : renvoyer la demande ou faire soi-même.
5. **Hook PreToolUse sur `Read`** : après `valide`, refuser une lecture complète du fichier `sortie` (aperçu ou `offset`/`limit` seulement), sauf chemin sensible.
6. **Skill `deleguer`** : enseigner `controle` et `attendus`, joindre les modules pour `symboles`, lancer `reprise` en arrière-plan, et le tableau `fin` ci-dessus.
7. **Compteurs** : `essais`, nombre de `echec_controle` et attentes uniques par session, pour la mesure de la section 10.
Tests de chaque hook sur des réponses figées tirées du tableau ci-dessus ; les autres hooks (sections 3 et 10) suivent l'ordre de la section 10.