Warum dein /save-Export Turns verschluckte — und das Limit dahinter


Du hast eine lang laufende Session, die Kompaktierung hat mehrere Runden davon archiviert, und du willst sie zur Aufbewahrung exportieren. /save json über die CLI funktioniert einwandfrei. Du machst dasselbe in der Desktop-App, importierst das Ergebnis später und stellst fest, dass Teile fehlen — die Turns, die die Kompaktierung archiviert hat, sind einfach weg. Du fragst dich, ob du dich falsch erinnerst oder ob irgendein /undo sie gelöscht hat.

Hier stapeln sich zwei Probleme: dasselbe /save lief über drei verschiedene Codepfade (CLI, Messaging-Plattformen und die session.save-RPC von Desktop/TUI), und die beiden Regeln, auf die es ankommt — was exportiert wird und ab wann es zu groß ist — stimmten am Ende nicht überein. Der Fix führt all das auf einen einzigen Pfad zusammen.

Drei /save-Oberflächen, zuvor drei Antworten

/save unterstützt json (einen vollständigen Snapshot), md (ein lesbares Transkript) und html (eine eigenständige, teilbare Seite). Desktop und TUI rufen die session.save-RPC auf, während CLI und Messaging-Plattformen eine andere Implementierung nutzten. Der Unterschied zeigte sich in der Export-Projektion:

  • Ein Transkript (md / html) ist „das, was der Nutzer gesehen hat“, also muss es die Turns enthalten, die die In-Place-Kompaktierung archiviert hat;
  • Ein JSON-Snapshot ist „das, was ein Import wiederherstellt“, also trägt er jede gespeicherte Zeile mit ihren active/compacted-Flags. Nur aktive Zeilen zu exportieren, lässt jeden archivierten Abschnitt der Konversation weg; die Flags zu exportieren, erlaubt dem Import, sie als archiviert wiederherzustellen statt sie als aktiven Kontext erneut abzuspielen.

Der RPC-Pfad befolgte diese Regel nicht durchgängig, sodass eine Session je nach genutzter Oberfläche unterschiedlich exportiert wurde.

Die neue Regel: ein Pfad, von allen dreien geteilt

Der Fix extrahiert die bereits korrekte CLI-Logik in ein gemeinsames load_save_snapshot(db, session_id, fmt) in hermes_cli/session_export.py, und alle drei Oberflächen rufen es auf. Es tut drei Dinge:

  1. Wählt die Projektion nach Format: include_compacted=True für Transkripte, include_inactive=True für JSON;
  2. Für einen JSON-Snapshot werden rückgängig gemachte Zeilen verworfen: nur Zeilen, die active oder compacted sind, überleben, während Zeilen, die durch /undo, Rewind oder Bearbeitung entfernt wurden, draußen bleiben — bewusst, denn ein Import sollte nicht wiederherstellen, was der Nutzer rückgängig gemacht hat;
  3. Erzwingt das Export-Limit (siehe unten).

Zwei kleinere Dinge kamen mit. Der JSON-Snapshot nutzt jetzt die kanonische Export-Form, passend zur CLI, statt der Legacy-Keys der Desktop-App — und wenn eine Session keine gespeicherte Zeile hat (oder das Lesen fehlschlägt), schreibt der In-Memory-Fallback dieselbe Form (id, model, started_at, system_prompt, messages). Außerdem liest er die Zeile, die der Live-Agent gerade schreibt, sodass eine Komprimierungs-Rotation keinen veralteten Parent exportiert.

Das Export-Limit: sessions.max_export_messages

Eine Session mit 100.000 Zeilen auf einmal im Speicher zu materialisieren, kann den Prozess erschöpfen, deshalb gibt es eine Schutzvorrichtung:

sessions:
  max_export_messages: 20000   # der Standardwert; 0 deaktiviert die Schutzvorrichtung

Sie wird pro Session geprüft, sodass ein Backup der gesamten Datenbank aus vielen kleinen Sessions unberührt bleibt — nur eine überdimensionierte Session wird abgelehnt. Nach diesem Fix beachtet auch das Desktop/TUI-/save json sie, und:

  • die Ablehnung kommt als RPC-Fehlercode 4131 (nicht der generische 5011) mit derselben Meldung, die die CLI ausgibt;
  • bei turn-isolierten (Compute-Host-)Sessions reicht der Host den Fehlercode des Handlers in control.error weiter und der Parent gibt ihn durch, sodass remote ausgeführte Sessions den echten Grund melden;
  • die Meldung weist dich an, sessions.max_export_messages: 0 in config.yaml zu setzen, um die Schutzvorrichtung zu deaktivieren.

Das letzte Detail ist leicht zu übersehen und wichtig: das Limit wird aus der Config des Profils gelesen, dem die Session gehört. Die Desktop-App liest jetzt die eigene state.db dieses Profils unter dessen Config, sodass das Export-Limit eines Sekundärprofils sein eigenes ist und nicht das deines Hauptprofils.

Nach Szenario wählen

Sind die Regeln einmal klar, ist die Formatwahl einfach:

Dein Ziel Verwende Warum
Archivieren / auf einen anderen Rechner umziehen /save json Vollständiger Snapshot mit Archiv-Flags; unverändert re-importierbar
An eine Kollegin senden /save html oder /save md Lesbares Transkript, inklusive der durch Kompaktierung archivierten Turns
Session zu groß, Export abgelehnt hermes sessions export für eine einzelne Session, oder max_export_messages erhöhen/deaktivieren Das Limit wird pro Session beurteilt
Irgendwo öffentlich posten Jedes Format plus redact Bereinigt API-Keys, Tokens und Zugangsdaten vor dem Schreiben

Die Erkenntnis, die es wert ist, behalten zu werden: „Export“ dient zwei verschiedenen Zwecken, und die Regeln unterscheiden sich bewusst. Ein Transkript ist für Menschen, also bleibt es so nah wie möglich an dem, was du gesehen hast — archivierte Turns inklusive. JSON ist für Maschinen, also bewahrt es Flags und entfernt rückgängig gemachte Zeilen. Wenn du angenommen hattest, der JSON-Fehlbetrag bedeute Datenverlust, erklärt diese Regel das weg.

Wann du es bekommst

Der Fix wurde am 3. Oktober 2026 in den main-Branch gemergt. Die neueste Version ist v0.21.5 (Tag v2026.9.24), also lebt er derzeit nur auf main — obwohl sessions.max_export_messages selbst bereits in veröffentlichten Versionen existiert und heute funktioniert. Installationen, die main folgen, werden mit dem nächsten hermes update konsistent.

Für das größere Bild vergleicht der Leitfaden zum Speichern und Exportieren von Sessions /save mit hermes sessions export, und der Leitfaden zu Session-Grenzen erklärt, wie Sessions abgegrenzt werden. Wenn dich der Komprimierungsmechanismus selbst interessiert, gehen Integrität der Kompaktierung mitten im Turn und der Beitrag zur Kompaktierung in einem einzigen Aufruf tiefer. Details auf Befehlsebene stehen in der Referenz zum sessions-Befehl.

Die Kurzfassung

Der Wert hier ist kein neues Feature, sondern die Beseitigung von Mehrdeutigkeit: früher konntest du nicht sagen, ob ein kurzer Desktop-JSON-Export ein Bug oder eine Einstellung war. Jetzt teilen drei Oberflächen eine Regel, die beiden Projektionen haben ausdrückliche Begründungen, und eine überdimensionierte Session liefert einen bestimmten Code mit einem Hinweis, auf den du direkt reagieren kannst. Wenn du Sessions regelmäßig exportierst, lohnt sich heute eine Prüfung: welche Oberfläche und welches Format deine Backup-Pipeline nutzt — und ob das, was sie erzeugt, tatsächlich sauber wieder importiert wird.