blog.vfg.lol

Le plafond à 572 tokens : ce que le code disait sans le savoir

Un chiffre bizarre avait un vrai coupable : slice(0, 2000). Deux lots pour déterrer la racine — un plafond mémoire silencieux et des chemins qui vivaient hors du dépôt.

Méthode : chaque constat vient d'un fichier, d'une ligne ou d'une commande. Les deux lots décrits sont fusionnés sur main, tests verts.

TL;DR

Quatre mois plus tôt, un corpus d'audit avait produit un chiffre étrange : la mémoire des agents locaux plafonnait à 572 tokens. Un nombre qui ne ressemblait à rien. En creusant jusqu'au code, il avait une cause exacte, à une ligne près. Corriger le symptôme n'aurait servi à rien — il fallait couper la racine.

Deux lots de suite : fin du plafond mémoire et chemins dérivés du dépôt. Et à la fin, un chiffre de tests qui monte de 82 à 102.

1. Un chiffre qui ne ressemblait à rien

L'audit d'un vieux corpus d'agents locaux (deux modèles, une mémoire de session par agent) avait relevé un plafond suspect : la mémoire estimée ne dépassait jamais 572 tokens, alors que le budget annoncé était de 1500. Le chiffre était trop précis pour être un hasard, trop rond pour être une mesure.

La règle : ne pas déduire, aller voir le code.

2. La ligne exacte

const turn = {
  role,
  content: clean.slice(0, 2000),
  ts: new Date().toISOString(),
  tokensEst: estimateTokens(clean.slice(0, 2000)),
};

content.slice(0, 2000). Le contenu de chaque tour était coupé à 2000 caractères — silencieusement, sans marqueur. Et l'estimation de tokens était calculée sur le texte déjà coupé.

L'arithmétique tombe juste : 2000 ÷ 3.5 ≈ 571. Le plafond de 572 tokens n'était pas un réglage, c'était 2000 caractères déguisés.

3. Le premier lot : couper le plafond

Trois changements, un seul principe.

  1. Le cap devient configurable (AGENT_MEMORY_MAX_CHARS, défaut 8000) au lieu d'un nombre magique dans le code.
  2. Quand on tronque, on le dit : un marqueur [… tronqué N car.] est ajouté, et le tour porte truncated: true. Plus de perte silencieuse.
  3. tokensEst se calcule sur le contenu réellement stocké, marqueur inclus — pour rester cohérent avec ce qui sera réinjecté dans le contexte.

Trois tests nouveaux couvrent ça, dont un qui vérifie explicitement qu'un contenu de 5000 caractères n'est plus coupé à 2000.

4. Le deuxième lot : sortir les données du disque système

En lisant les chemins de la mémoire, une deuxième racine apparaît :

function memoryDir() {
  return (process.env.AGENT_MEMORY_DIR || 'C:\\agents\\memory').trim();
}

Le défaut pointe vers C:\agents — à la racine du disque système, hors du dépôt, donc invisible à git. Le corpus produit par le code n'était pas versionnable par construction.

Un résolveur unique remplace les chemins en dur :

function agentsRoot() {
  return envDir('AGENT_DATA_DIR') || path.join(REPO_ROOT, 'agents');
}

Le défaut devient <repo>/agents, et tout reste surchargeable par variable d'environnement. Le store de traces, la mémoire et les journaux d'audit partagent désormais la même racine résolue.

5. La preuve

CommandeRésultat
npm test avant les deux lots82 / 82
après le premier lot91 / 91
après le second96 / 96
après un troisième (quêtes, cf. article lié)102 / 102

Et un contrôle de surface : après ces lots, Select-String 'C:\agents' sur le périmètre mémoire / audit / traces ne renvoie plus rien. Les occurrences qui restent sont ailleurs et fonctionnelles (une liste de racines de lecture autorisées) — les casser aurait cassé la lecture.

6. Ce qu'on n'a pas fait, et pourquoi

Deux occurrences d'un slice(0, 2000) ont été laissées de côté. La première, dans un extracteur de contexte, traite des résumés — sujet distinct, à traiter à part. La seconde, DEFAULT_ROOTS dans le lecteur de fichiers, est fonctionnelle : c'est une allowlist qui permet à un outil de lire un dossier précis. La dériver hâtivement aurait supprimé une capacité réelle.

C'est aussi une discipline : un lot ferme sa racine, il n'emporte pas les voisines par effet d'entraînement.

7. La leçon

Un nombre bizarre cache souvent une ligne précise. 572 tokens n'était pas un mystère : c'était 2000 ÷ 3.5. Tant qu'on traite le symptôme (relever le plafond), la cause reste : le contenu est coupé sans le dire.

Et un défaut par défaut est invisible. C:\agents ne gênait personne au quotidien — sauf que la donnée vivait hors de tout dépôt. Un || en fin de ligne suffisait à rendre un corpus invisible à git. Le corriger n'a pas changé une réponse : ça a rendu la donnée versionnable.

source markdown

Concepts de cet article

dérive

Partage le plus de concepts avec