Files
plugin-claude-miam/docs/token-summary-mod.md
T
ange 9e66af7bf9 v0.7.0 : délégation directe par l'outil deleguer, subagent mis de côté
- consignes, rappel et refus renvoient vers l'outil MCP deleguer
- skill deleguer réécrite pour l'appel direct
- subagent déplacé dans docs/agent-delegue.md, réglage modele_subagent retiré
2026-10-31 10:57:24 +01:00

14 KiB
Raw Permalink Blame History

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 d'un subagent qui délègue ; le subagent miam:delegue est mis de côté, voir agent-delegue.md). 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 : Si retour === 'apercu', alors Math.ceil(longueur_reponse / 4), sinon ecrits.
  • evites : lus + ecrits - (consigne + rendus) (si pas de subagent) ou lus + 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

  1. demande_id dans delegations.jsonl (voir « Prérequis »).
  2. Route Starlette POST /economies montée sur la même application que /mcp (McpService.http_app), derrière BearerAuth (401 sans jeton valide, comme /mcp). Bind 127.0.0.1 inchangé.
  3. Validation stricte, en cœur pur (core/, sans I/O) : evenements liste de 1 à 500 ; pour chaque événement schema == 1, type connu, champs requis présents et du bon type (entiers ≥ 0 pour les compteurs, sauf estimation.evites qui peut être négatif ; agent_id chaîne ou null pour delegation) ; 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.
  4. Stockage : ajout dans logs/mcp/economies.jsonl (même dossier que delegations.jsonl), une ligne par événement nouveau : l'événement tel que reçu, plus recu_le (ISO). Ensemble des id déjà vus en mémoire, rechargé depuis le fichier au démarrage du service ; un doublon n'est pas réécrit.
  5. Outil bilan enrichi d'une section economies, par client : delegations (événements delegation), evites (somme de estimation.evites), pilotage (somme des cout de pilotage), corrections (somme des cout de correction), net = evites - pilotage - corrections, et taux_reprise = corrections / evites (null si evites ≤ 0). Fichier absent : section à zéro. La jointure avec delegations.jsonl par demande_id permet ensuite le détail par preset ou par format (utile au choix du modèle par MIAM).
  6. Tests : unitaires sur la validation (chaque champ requis, types, lot vide, 501 événements, evites né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 avec recus/doublons, renvoi du même lot → tout en doublons, 422, 413).
  7. Taille maximale du corps : 1 Mo → 413.
  8. 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).