L3.1c : acte le contrat de troncature en D24, retire le lot 3 de la ROADMAP

Les deux bornages (L3.1a pagination, L3.1b plafond de volume) partagent
le même vocabulaire de signal — truncated présent uniquement quand la
réponse est coupée, hint actionnable, returned vs total avant coupe —
mais gardent des implémentations locales : paginer et plafonner un
volume sont deux mécanismes distincts, un helper commun forcerait une
abstraction qu'ils n'ont pas. D24 consigne ce contrat, les garde-fous
de cadrage (défauts inchangés, mesure protocolaire qui fait foi) et le
changement de sens de totalParameters.

ROADMAP : L3.1 livré, le lot 3 devenait vide — section retirée ; la
référence à L3.1 dans L4.5 (QueryExecuteStream) renvoie désormais à
D24. read_recent_logs est laissé tel quel : sans helper partagé, rien
de gratuit à lui apporter (L3.1c).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Arthur Ria
2026-08-25 10:36:37 +02:00
parent d0a6cc1a0b
commit b37c2ac251
2 changed files with 41 additions and 17 deletions
+40
View File
@@ -440,3 +440,43 @@ silence, pas de réimplémenter JSON Schema.
Le renommage des paramètres (`entity_type`/`query` uniformisés) a été **écarté**
au profit de cette validation — voir ROADMAP « Écarté ».
---
## D24 — Contrat de troncature : borné + signalé, jamais un rejet silencieux
**Piège mesuré (24-25/08/2026).** Une réponse d'outil de ~70 000 caractères
(`get_system_parameters` sans filtre ; `search_logs` atteignait 52-56 000 avec
les seuls défauts) est **rejetée par le client MCP** — l'utilisateur voit un
échec opaque au lieu d'un résultat partiel.
**Décision.** Tout outil susceptible de produire une sortie volumineuse borne
sa réponse et **signale** la coupe. Le signal est commun :
| Champ | Sémantique |
|---|---|
| `truncated: true` | présent **uniquement** quand la réponse a été coupée — jamais `truncated: false` |
| `hint` | présent ssi `truncated` ; actionnable : dit comment continuer (`offset` suivant) ou réduire (filtres, `context_lines`…) |
| `returned` | nombre d'éléments effectivement renvoyés |
| total (`totalParameters`, `totalResults`) | total **avant** la coupe — `truncated` se vérifie donc depuis la réponse elle-même |
Les mécanismes restent **volontairement locaux**, car ils diffèrent :
`get_system_parameters` pagine (`limit`/`offset` au schéma — rien n'est perdu,
on continue avec l'offset suivant) ; `search_logs` plafonne le volume
(`MAX_LOG_SEARCH_CHARS`, défaut 25 000 caractères) en écartant des résultats
**entiers** — jamais coupés au milieu de leurs lignes de contexte — et annonce
en plus `omitted`, le compte écarté. Pas de helper partagé : le factoriser
forcerait une abstraction commune à deux mécanismes qui n'en ont pas.
Deux garde-fous de cadrage :
- **Ne pas réduire les défauts existants** (`max_results` 50, `context_lines` 2)
pour passer sous le plafond : le correctif est le bornage signalé, pas un
changement silencieux de comportement.
- La taille qui fait foi est celle de `content[0].text` **mesurée via le
protocole**, pas une estimation. Ordre de grandeur cible : ~20-25 000
caractères par réponse.
Au passage, `totalParameters` a changé de sens : c'était le nombre brut
d'entités `Parameter` chargées, c'est désormais le total correspondant aux
filtres avant pagination (identique sans filtre).