Applique docs/protocole-agents.md de MIAM (PH2-E3) : - banc/ et /miam:banc supprimés : MIAM teste les presets lui-même - miam:delegue passe type + seuil, ou preset ; MIAM peut donc remplacer le modèle chargé s'il est sous le seuil - modeles en repli si le contexte est dépassé ; preset_defaut seulement sans test ni modèle chargé - MIAM 2.5.1 minimum Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
14 KiB
Mod compteur de tokens : contrat avec MIAM (POST /economies)
Destinataire : l'agent Claude Code qui travaille dans le dépôt MIAM. Le côté plugin est fait et testé (dépôt
plugin-claude-miam, schéma 1) ; ce document décrit ce que MIAM doit recevoir, stocker et restituer.
But
Le mod estime, sans aucun coût en tokens, ce que chaque délégation évite à Claude et le coût du pilotage (consommation du subagent miam:delegue). MIAM connaît les tokens du modèle local mais pas ce qui se passe côté Claude : ces événements complètent son bilan.
Prérequis dans MIAM : demande_id dans le journal
delegations.jsonl ne contient pas aujourd'hui l'identifiant de la demande (core/delegation.py, journal_entry) :
la jointure est impossible sans lui. Ajouter un paramètre demande_id à journal_entry et le passer depuis
McpService.deleguer (réussites) et _journal_failure / _on_request_finished (refus, échecs, annulations,
expirations ; None pour un refus avant mise en file, qui n'a pas d'id). C'est le req.id de la file (dem-…), celui
que deleguer et suivre renvoient au client.
Transport
POST http://127.0.0.1:/economies (port = celui du serveur MCP, 8090 par défaut ; réglage port du plugin).
En-têtes : Content-Type: application/json et Authorization: Bearer <jeton> (le même jeton que /mcp).
Corps : { "evenements": [ ... ] }, lot de 1 à 500 événements (ATTENTE_MAX).
Le mod ne poste jamais depuis un hook : chaque événement est d'abord mis en attente dans le stockage du plugin
($.store, clé a_envoyer), puis une minuterie (toutes les 5 s, lancée au démarrage de la session) poste toute
l'attente en un lot. Une délégation n'attend donc jamais MIAM. Une seule requête à la fois.
Sans réponse ou statut à garder, le lot reste en attente et repart au tour suivant, avec ce qui est arrivé depuis (les
plus anciens d'abord, doublons d'id retirés, 500 au plus : au-delà, les plus anciens sont perdus). Conséquence : MIAM
reçoit souvent plusieurs fois le même événement, et doit répondre vite (pas de travail lourd dans la requête).
Réponses attendues
| Statut | Quand | Effet côté mod |
|---|---|---|
| 200 (ou 204) | Lot reçu | Le mod vide son attente. |
| 400 ou 422 | Lot invalide | Le mod l'abandonne (pas de renvoi). |
| 401 | Jeton refusé | Gardé. |
| 404 | Route absente (MIAM ancien) | Gardé. |
| 413 | Corps trop gros | Gardé (500 événements font environ 300 Ko : rester au-dessus de 1 Mo). |
| 5xx / Connexion refusée | Erreur réseau | Gardé. |
Corps de réponse recommandé pour 200 : { "ok": true, "recus": n, "doublons": m } (le mod ne le lit pas ; utile aux
tests et au débogage). Un lot est accepté ou refusé en entier : pas de 200 partiel avec des événements rejetés, sinon
le mod les croirait reçus.
Modèle de données
Événement delegation
| Champ | Type | Description |
|---|---|---|
schema |
number |
Version du schéma (fixé à 1). |
type |
string |
Valeur "delegation". |
id |
string |
Identifiant unique stable : claude-code:<demande_id>. |
date |
string |
ISO 8601 de la génération de l'événement. |
client |
string |
Valeur "claude-code". |
session_id |
string |
Identifiant de la session Claude Code. |
demande_id |
string |
Id MIAM de la demande (dem-…), tel que deleguer ou suivre l'a renvoyé. |
agent_id |
string | null |
Boucle Claude Code qui a appelé deleguer : id du subagent, null pour l'agent principal. |
par_subagent |
boolean |
agent_id !== null. |
retour |
string |
"complet" ou "apercu" (argument retour de deleguer). |
tokens_entree |
integer |
Tokens lus par le modèle local, tels que deleguer les a renvoyés (= journal). |
tokens_sortie |
integer |
Tokens écrits par le modèle local, idem. |
estimation |
object |
lus, ecrits, consigne, rendus, evites (entiers, en tokens Claude) et methode ("caracteres/4"). |
Seules les délégations réussies (ok: true) produisent un événement ; une délégation « en file » n'en produit qu'un,
quand suivre rend son résultat.
Calcul des estimations (fonction estimer) :
consigne:Math.ceil(longueur_consigne / 4).lus:Math.max(0, tokens_entree - consigne).ecrits:tokens_sortie.rendus: Siretour === 'apercu', alorsMath.ceil(longueur_reponse / 4), sinonecrits.evites:lus + ecrits - (consigne + rendus)(si pas de subagent) oulus + ecrits(si subagent : la consigne et la lecture de la réponse sont alors dans le coût du pilotage). Peut être négatif (consigne longue, petite réponse relue en entier) : ne pas le borner.
Événement pilotage
| Champ | Type | Description |
|---|---|---|
schema |
number |
Version du schéma (fixé à 1). |
type |
string |
Valeur "pilotage". |
id |
string |
Identifiant unique stable : claude-code:<session_id>:<agent_id>:<turn_id>. |
date |
string |
ISO 8601 de la génération de l'événement. |
client |
string |
Valeur "claude-code". |
session_id |
string |
Identifiant de la session Claude Code. |
agent_id |
string |
Id du subagent (toujours présent : l'agent principal n'a pas de pilotage). |
turn_id |
string |
Id du tour du subagent (un tour = une exécution du subagent). |
demande_ids |
string[] |
Les demande_id des délégations réussies de ce tour (au moins un). |
modele |
string |
Modèle Claude du subagent, tel que l'API le nomme (ex. claude-haiku-5-5). |
usage |
object |
Les 4 compteurs de l'API pour ce tour (entiers) : input_tokens, output_tokens, cache_read_input_tokens, cache_creation_input_tokens. |
cout |
integer |
Coût du tour en tokens Claude, à déduire des evites des délégations couvertes. |
Le pilotage arrive à la fin du tour du subagent, donc après ses événements delegation (sauf renvoi : l'ordre
n'est pas garanti).
Événement correction
Pondère le gain par le travail de reprise : quand l'agent principal relit ou retouche un fichier écrit par une
délégation (champ sortie du résultat de deleguer), le coût de cet appel d'outil est déduit. Les reprises faites
par le subagent ne produisent pas d'événement : elles sont déjà dans son pilotage. Une délégation sans sortie
(réponse rendue dans la conversation) n'a pas de correction mesurable.
| Champ | Type | Description |
|---|---|---|
schema |
number |
1. |
type |
string |
"correction". |
id |
string |
claude-code:<session_id>:<tool_use_id> (unique par appel d'outil). |
date |
string |
ISO 8601 de l'appel. |
client |
string |
"claude-code". |
session_id |
string |
Session Claude Code. |
demande_id |
string |
Délégation dont la sortie est reprise. |
outil |
string |
Read, Edit, Write, NotebookEdit ou Bash. |
tool_use_id |
string |
Id de l'appel d'outil (reprise-<n> si Claude Code n'en a pas donné). |
tokens_lus |
integer |
Ce que Claude lit : résultat d'un Read (ou d'une commande Bash). |
tokens_ecrits |
integer |
Ce que Claude écrit : old_string + new_string d'un Edit, contenu d'un Write, new_source d'un NotebookEdit, commande Bash. |
cout |
integer |
tokens_lus + tokens_ecrits, à déduire des evites de demande_id. |
Rattachement (fonction cible) : chemin du fichier de l'appel égal à la sortie, après normalisation (\ → /,
minuscules) ; pour Bash, la commande contient le chemin, ou le nom du fichier s'il fait au moins 6 caractères
(heuristique : une commande qui cite ce nom sans toucher au fichier est comptée aussi). Un appel qui ne lit ni n'écrit
rien (coût 0) n'est pas envoyé.
Gain net = somme des estimation.evites − somme des cout de pilotage − somme des cout de correction.
Le taux de reprise d'une délégation (cout de ses corrections / ses evites) mesure la qualité réelle du modèle
local sur la tâche : c'est la donnée la plus utile pour classer les presets avec les tests de MIAM.
Calcul du coût (fonction cout) :
input_tokens + cache_creation_input_tokens + output_tokens + Math.round(cache_read_input_tokens / 10).
Exemple : une délégation du subagent a1 (consigne de 400 caractères, réponse complète), puis la fin de son
tour. Ce sont les valeurs du test hooks/register.test.ts, produites par le code du mod.
{
"evenements": [
{
"schema": 1,
"type": "delegation",
"id": "claude-code:dem-9c8a4d0189",
"date": "2026-10-30T09:12:04.512Z",
"client": "claude-code",
"session_id": "c009a419-e17b-4036-9531-ac29eaa481d4",
"demande_id": "dem-9c8a4d0189",
"agent_id": "a1",
"par_subagent": true,
"retour": "complet",
"tokens_entree": 4100,
"tokens_sortie": 900,
"estimation": { "lus": 4000, "ecrits": 900, "consigne": 100, "rendus": 900, "evites": 4900,
"methode": "caracteres/4" }
},
{
"schema": 1,
"type": "pilotage",
"id": "claude-code:c009a419-e17b-4036-9531-ac29eaa481d4:a1:t",
"date": "2026-10-30T09:12:09.031Z",
"client": "claude-code",
"session_id": "c009a419-e17b-4036-9531-ac29eaa481d4",
"agent_id": "a1",
"turn_id": "t",
"demande_ids": ["dem-9c8a4d0189"],
"modele": "claude-haiku-5-5",
"usage": { "input_tokens": 800, "output_tokens": 200, "cache_read_input_tokens": 10000,
"cache_creation_input_tokens": 0 },
"cout": 2000
}
]
}
Lecture : 4 900 tokens évités, 2 000 de pilotage, gain net 2 900.
Idempotence et jointure
id est unique et stable (delegation : claude-code:<demande_id> ; pilotage : claude-code:<session_id>:<agent_id>:<turn_id> ; correction : claude-code:<session_id>:<tool_use_id>).
Un même événement peut arriver plusieurs fois → MIAM doit ignorer un id déjà reçu et le compter dans « doublons ».
Jointure avec delegations.jsonl par demande_id ; un pilotage couvre la liste demande_ids, une correction
porte un seul demande_id (une délégation peut en avoir plusieurs).
Un pilotage ou une correction peut arriver sans que sa délégation ait été reçue (ou l'inverse) : accepter dans
tous les ordres.
À implémenter dans MIAM
demande_iddansdelegations.jsonl(voir « Prérequis »).- Route Starlette POST
/economiesmontée sur la même application que/mcp(McpService.http_app), derrièreBearerAuth(401 sans jeton valide, comme/mcp). Bind127.0.0.1inchangé. - Validation stricte, en cœur pur (
core/, sans I/O) :evenementsliste de 1 à 500 ; pour chaque événementschema == 1,typeconnu, champs requis présents et du bon type (entiers ≥ 0 pour les compteurs, saufestimation.evitesqui peut être négatif ;agent_idchaîne ounullpourdelegation) ; champs inconnus ignorés (le mod pourra en ajouter sans changer de schéma). Un seul événement invalide → 422 pour tout le lot, avec l'index et le champ fautifs dans le corps. - Stockage : ajout dans
logs/mcp/economies.jsonl(même dossier quedelegations.jsonl), une ligne par événement nouveau : l'événement tel que reçu, plusrecu_le(ISO). Ensemble desiddéjà vus en mémoire, rechargé depuis le fichier au démarrage du service ; un doublon n'est pas réécrit. - Outil
bilanenrichi d'une sectioneconomies, parclient:delegations(événementsdelegation),evites(somme deestimation.evites),pilotage(somme descoutdepilotage),corrections(somme descoutdecorrection),net = evites - pilotage - corrections, ettaux_reprise = corrections / evites(nullsievites≤ 0). Fichier absent : section à zéro. La jointure avecdelegations.jsonlpardemande_idpermet ensuite le détail par preset ou par format (utile au choix du modèle par MIAM). - Tests : unitaires sur la validation (chaque champ requis, types, lot vide, 501 événements,
evitesnégatif accepté) et sur le calcul du bilan (doublons, pilotage reçu avant sa délégation) ; intégration HTTP réelle comme les tests MCP existants (401 sans jeton, 200 avecrecus/doublons, renvoi du même lot → tout en doublons, 422, 413). - Taille maximale du corps : 1 Mo → 413.
- Optionnel : afficher le net du jour sur la carte MCP du dashboard.
Hors périmètre : rien n'est à changer dans les outils MCP existants (le mod lit leurs réponses telles quelles).
Limites de l'estimation
Brief de l'agent principal et lecture du compte rendu non comptés.
Coût d'un token Haiku ≠ Opus, non pondéré ; un token écrit coûte plus qu'un token lu, non pondéré non plus (les
événements gardent lus et écrits séparés : MIAM peut pondérer).
Estimation à 4 caractères par token.
Corrections : seulement sur les fichiers sortie, et seulement pendant la session qui a délégué (la table des sorties
n'est pas gardée d'une session à l'autre) ; une reprise ailleurs (texte de la réponse recopié dans un autre fichier)
n'est pas vue ; l'heuristique Bash peut compter une commande qui ne fait que citer le fichier.
Côté plugin
Fichiers plugins/miam/hooks/economies.ts (calcul et format, testés) et register.ts (hooks : tool.call sur deleguer et suivre puis sur les reprises Read/Edit/Write/NotebookEdit/Bash, turn.complete, commande /miam-economies).
L'envoi passe par $.http.fetch de Claude Code, sans modèle, donc sans token ; le jeton et le port viennent des réglages du plugin.
Format et calcul : economies.ts fait foi (EvenementDelegation, EvenementPilotage, EvenementCorrection,
estimer, cout, cible, mesurerCorrection) ; toute évolution du format change SCHEMA et ce document.
Tests : claude plugin test plugins/miam (35 tests).