Fortsetzen einer komprimierten Session sagt alles doppelt: im Inneren des Summary Carriers

Du komprimierst eine lange Konversation, beendest Hermes und kommst am nächsten Morgen mit --resume zurück. Die erste Hälfte sieht richtig aus. Irgendwo um den Compaction-Punkt herum beginnt eine Modell-Antwort sich zu wiederholen — nicht ein Satz, die ganze Passage, Wort für Wort, zweimal. Der Scrollback wird doppelt so lang, und du fragst dich, ob der Kontext explodiert ist und die Nachricht wirklich zweimal gespeichert wurde. Dann zählst du die Zeilen in state.db und es sind weniger als erwartet.
Dieses Duplikat wurde am 20. Oktober 2026 behoben (PR #142187). Es verdient einen eigenen Beitrag, weil es eine Struktur offenlegt, die man normalerweise nie zu sehen bekommt: den Summary Carrier.
Die Compaction verpackt diese Antwort einmal
In-Place-Compaction löscht keine alten Nachrichten. Sie behält einen geschützten Tail — den jüngsten Turn, intakt — und paart ihn mit einer Compaction-Summary in einer neuen Zeile. Diese neue Zeile ist der Carrier. Er behält die message_uid des Originals, aber nicht dessen Inhalt: sein Text ist „der vorherige Turn, dann die Summary“.
Das Design ist solide. Die uid hält „welche Nachricht ist das“ über die Compaction hinweg stabil, und Rewind, Export und Dedupe stützen sich darauf. Das Problem lag in der Display-Schicht.
Warum es doppelt gerendert wurde
Seit #140971 zeigt das Fortsetzen einer komprimierten Konversation die ganze Historie — genau das, was Nutzer gefordert hatten, statt nur der Summary. Der Display-Read liefert also zwei Mengen von Zeilen: die archivierten Originale von vor der Compaction und den neuen Carrier. Der Carrier teilt die message_uid, verpackt aber die Summary in seinem Body, und die Display-Schicht konnte nicht erkennen, dass er ein Container für dieselbe Passage ist. Sie renderte beide Zeilen, also erschien der Originaltext einmal und die Kopie im Carrier noch einmal.
Der offizielle TUI-End-to-End-Test hat es beziffert: der Resume/Compress-E2E meldete 200 words rendered more than once über /resume, --resume und /compress. Solch eine Assertion ist mehr wert als ein Screenshot — sie sagt schlicht, dass 200 Wörter mehr als einmal gezeichnet wurden.
Der Fix: der Carrier leiht sich die Display-Identität des Originals
Der Patch fasst den Storage nicht an. Er vereinheitlicht, wie Display-Identität berechnet wird: _dedupe_display_generations in hermes_state_messages.py bekommt eine Regel — ein Summary Carrier teilt die Display-Identität der früheren Zeile mit seiner message_uid, und die neueste Kopie gewinnt, sodass die ältere Kopie nicht mehr separat gerendert wird.
Der Docstring der Funktion ist explizit: Compaction-Generationen zusammenfallen lassen, sodass jede logische Nachricht einmal erscheint (der geschützte Tail wird in jede Generation kopiert — gleiche Rolle, gleicher Inhalt und Zeitstempel, unterschiedliches active-Flag und andere id); die Live-Kopie bevorzugen, dann die neueste. Und es ist eine Definition, die alle Display-Projektionen teilen, nicht eine eigene Implementierung pro Oberfläche.
Der Fix ist bewusst eng: nur der Carrier. Ein geprunter Tool-Stub behält seine archivierte vollständige Ausgabe sichtbar, und test_display_dedupe_pruned_tool_payload hält das fest — ein früherer, ungescopter uid-Match hatte es kaputtgemacht.
Ein begleitender Commit (c8aa61b7883) wandte dieselbe Regel auf Exporte und die Transcript-Liste an, sodass hermes sessions export eine getragene Antwort ebenfalls nicht mehr doppelt ausgibt.
Sind dabei Daten verloren gegangen?
Nein. Der ganze Bug betraf die Anzeige. Die Validierung nutzte die state.db des fehlschlagenden E2E selbst: vor dem Fix lieferte der Display-Read 15 Zeilen inklusive Wiederholungen, danach 14 Zeilen ohne. Weniger Zeilen sind der Beweis, dass die zusätzliche Kopie ein Rendering-Artefakt war und keine gespeicherten Daten.
Ein nützlicher Selbstcheck: wenn du Duplikate vermutest, vergleiche die Zeilenzahl in der Datenbank mit der Anzahl der Nachrichten, die die Oberfläche zeigt. Ist die Datenbank normal, die Ansicht aber doppelt, ist es Rendering. Verdoppelt sich beides, hat wirklich etwas zweimal geschrieben. Der breitere Testlauf hinter dem Patch (tests/hermes_state, Gateway-Resume- und Recovery-RPCs, tests/tui_gateway) meldete 4328 passed, 0 failed.
Ein benachbartes Problem bleibt
Ein Issue aus demselben Bereich ist noch offen: wenn die Compaction eine User-Nachricht in einen vorangehenden „model switch“-Hinweis mergt, verschwindet diese User-Nachricht aus dem Transcript. Es ist nicht derselbe Bug — der eine zeigt eine Kopie zu viel, der andere eine Nachricht zu wenig — und dieser Fix hat ihn nicht mit übernommen. Wenn eine User-Nachricht nach der Compaction verschwindet, gilt die Schlussfolgerung oben nicht.
Um Compaction und Kontext-Budgets richtig zu verstehen, lies how compaction completes in a single call und the compaction prompt rebuild. Befehle für Session-Historie, Export und Resume stehen in der hermes sessions reference, und wenn es dir darum geht, lange Sessions innerhalb eines Budgets zu halten, ist context token optimization der relevante Beitrag.
Kannst du es heute nutzen?
Dieser Fix gehört zu den Punkten, die v0.22.3 freischalten, er lebt also nur auf main und ist in keinem veröffentlichten Release (das neueste ist v0.22.2). Sobald du auf einem Build bist, der ihn enthält, sollten /resume, --resume und /compress eine getragene Antwort jeweils einmal zeigen; wiederholt sie sich weiterhin, prüfe zuerst die Version und schau dir dann die Display-Schicht an.
Die Kurzfassung
Der Summary Carrier ist der Container, den die Compaction hinterlässt: er erbt die uid des Originals, aber sein Body ist „vorheriger Turn plus Summary“. Eine Display-Schicht, die Nachrichten allein über die uid identifiziert und Carrier nicht erkennt, zeichnet dieselbe Passage zweimal. Der Fix leiht dem Carrier die Display-Identität des Originals und beschränkt die Regel auf Carrier, sodass geprunter Tool-Output weiterhin seinen archivierten Inhalt zeigt. Behalte die Diagnose-Regel im Kopf: Ansicht doppelt, Datenbank nicht — Anzeigeproblem; beides doppelt — Datenproblem.