Reprendre une session compactée affiche tout en double : dans le summary carrier


Vous compactez une longue conversation, vous quittez, et vous revenez le lendemain matin avec --resume. La première moitié paraît correcte. Quelque part autour du point de compaction, une réponse du modèle se met à se répéter — pas une phrase, tout le passage, mot pour mot, deux fois. Le scrollback double de longueur et vous commencez à vous demander si le contexte n’a pas explosé et si le message n’est pas réellement stocké en double. Puis vous comptez les lignes dans state.db et il y en a moins que prévu.

Ce doublon a été corrigé le 20 octobre 2026 (PR #142187). Il mérite son propre billet, parce qu’il expose une structure qu’on ne voit normalement jamais : le summary carrier.

La compaction enveloppe cette réponse une fois

La compaction en place ne supprime pas les anciens messages. Elle conserve une queue protégée — le tour le plus récent, intact — et l’associe à un résumé de compaction dans une nouvelle ligne. Cette nouvelle ligne est le carrier. Elle garde le message_uid de l’original mais pas son contenu : son texte est « le tour précédent, puis le résumé ».

Le design tient debout. L’uid stabilise la question « quel message est-ce ? » à travers la compaction, et le rewind, l’export et la déduplication s’appuient tous dessus. Le problème était dans la couche d’affichage.

Pourquoi elle s’affichait deux fois

Depuis la #140971, reprendre une conversation compactée affiche tout l’historique — exactement ce que les utilisateurs demandaient, au lieu du seul résumé. La lecture d’affichage renvoie donc deux jeux de lignes : les originaux archivés d’avant la compaction, et le nouveau carrier. Le carrier partage le message_uid mais enveloppe le résumé dans son corps, et la couche d’affichage ne savait pas qu’il était un conteneur pour le même passage. Elle rendait les deux lignes : le texte d’origine apparaissait une fois, puis sa copie à l’intérieur du carrier une seconde fois.

Le test E2E officiel de la TUI a mis un chiffre dessus : le E2E resume/compress rapportait 200 words rendered more than once sur /resume, --resume et /compress. Ce genre d’assertion vaut plus qu’une capture d’écran — elle dit noir sur blanc que 200 mots ont été dessinés plus d’une fois.

Le correctif : le carrier emprunte l’identité d’affichage de l’original

Le patch ne touche pas au stockage. Il unifie le calcul de l’identité d’affichage : _dedupe_display_generations dans hermes_state_messages.py gagne une règle — un summary carrier partage l’identité d’affichage de la ligne antérieure portant le même message_uid, et la copie la plus récente l’emporte, si bien que l’ancienne cesse d’être rendue séparément.

La docstring de la fonction est explicite : réduire les générations de compaction pour que chaque message logique n’apparaisse qu’une fois (la queue protégée est copiée dans chaque génération — même rôle, même contenu, même horodatage, mais un flag active et un id différents) ; préférer la copie vivante, puis la plus récente. Et c’est une définition partagée par toutes les projections d’affichage, pas une implémentation distincte par surface.

Le correctif est volontairement étroit : seulement le carrier. Un stub d’outil élagué garde sa sortie complète archivée visible, et test_display_dedupe_pruned_tool_payload verrouille ce comportement — une correspondance d’uid plus précoce et non délimitée l’avait cassé.

Un commit compagnon (c8aa61b7883) a appliqué la même règle aux exports et à la liste des transcripts, de sorte que hermes sessions export n’émet plus non plus une réponse portée deux fois.

Des données ont-elles disparu ?

Non. Tout le bug concernait l’affichage. La validation a utilisé le state.db du E2E en échec : avant le correctif, la lecture d’affichage renvoyait 15 lignes, doublons compris ; après, 14 lignes sans aucun. Moins de lignes est la preuve que la copie supplémentaire était un artefact de rendu, pas une donnée stockée.

Un auto-contrôle utile : quand vous soupçonnez une duplication, comparez le nombre de lignes en base avec le nombre de messages affichés par l’interface. Si la base est normale mais que la vue double, c’est du rendu. Si les deux doublent, quelque chose a réellement écrit deux fois. La campagne de tests plus large derrière le patch (tests/hermes_state, les RPC de reprise et de récupération du gateway, tests/tui_gateway) rapporte 4328 tests passés, 0 échec.

Un problème voisin subsiste

Un ticket du même secteur est toujours ouvert : quand la compaction fusionne un message utilisateur avec un avis de « model switch » qui le précède, ce message utilisateur disparaît du transcript. Ce n’est pas le même bug — l’un affiche une copie de trop, l’autre un message de trop peu — et ce correctif ne l’a pas absorbé. Si un message utilisateur s’évapore après une compaction, n’appliquez pas la conclusion ci-dessus.

Pour bien comprendre la compaction et les budgets de contexte, lisez comment la compaction s’achève en un seul appel et la reconstruction du prompt de compaction. Les commandes d’historique de session, d’export et de reprise sont dans la référence hermes sessions, et si votre objectif est de garder de longues sessions dans un budget, c’est l’optimisation des tokens de contexte qu’il faut lire.

Peut-on l’utiliser aujourd’hui ?

Ce correctif fait partie des éléments qui débloquent la v0.22.3 : il ne vit donc que sur main et n’est pas dans une release publiée (la dernière est la v0.22.2). Une fois sur une build qui le contient, /resume, --resume et /compress devraient chacun afficher une réponse portée une seule fois ; si elle se répète encore, vérifiez d’abord la version, puis regardez du côté de la couche d’affichage.

En bref

Le summary carrier est le conteneur que la compaction laisse derrière elle : il hérite de l’uid de l’original, mais son corps est « tour précédent plus résumé ». Une couche d’affichage qui identifie les messages par le seul uid, sans reconnaître les carriers, dessine deux fois le même passage. Le correctif prête au carrier l’identité d’affichage de l’original et limite la règle aux seuls carriers, pour que la sortie élaguée des outils continue d’afficher son contenu archivé. Gardez la règle de diagnostic en tête : vue doublée, base normale — problème d’affichage ; les deux doublés — problème de données.