Warum Tippen langsamer wird, je länger eine Session läuft — und wie Hermes Desktop die Kosten um zwei Drittel gesenkt hat

Es ist 15 Uhr, und du scrollst durch eine Session, die seit gestern läuft, auf der Suche nach den Schlussfolgerungen von heute Morgen. Oben angekommen, wechselst du zurück in das Eingabefeld und beginnst zu tippen — dann hältst du inne. Die Zeichen kommen einzeln an. Ein Zeichen zu löschen dauert eine halbe Sekunde. Der Cursor hängt deinen Fingern hinterher. Du probierst eine andere Maschine; dieselbe Verzögerung. Du vermutest, das Modell arbeitet im Hintergrund, dabei sitzt es nur da und wartet darauf, dass du den Satz beendest.
Das Problem war das Eingabefeld selbst. Eine Performance-Änderung, die am 26. Oktober in Hermes Desktop gemergt wurde (PR #144903), macht den Mechanismus explizit: Je mehr Verlauf geladen ist, desto langsamer wird das Tippen, weil jeder Tastendruck das gesamte Transkript neu gerendert hat. Nach dem Fix kostet dieselbe Session mit 300 Turns 9,6 ms Hauptthread-Zeit pro Taste statt 33,9 ms.
Ein Tastendruck, ein vollständiges Re-Render
Die Chat-Oberfläche von Desktop basiert auf assistant-ui, einer Komponentenbibliothek für Chat-UIs. Der Text in deinem Eingabefeld liegt nicht ruhig im Eingabefeld: Er ist ein Wert in einem gemeinsamen Store, und erstaunlich viele Dinge abonnieren ihn —
- ob der Senden-Button aktiv oder ausgegraut ist;
- ob ein einzelnes
?den Hilfe-Hinweis einblenden soll; - ob die Zeile ein Slash-Befehl ist (
/modelund Verwandte) oder eine normale Nachricht; - ob der leere Platzhalter angezeigt wird;
- wie Zeilenumbrüche im Layout behandelt werden.
Der feine Punkt: Diese Abonnements zu prüfen bleibt nicht im Eingabefeld. Ein Schreibvorgang in den Store rendert die Client-Ressourcen des Threads neu und prüft die Selektoren jeder gemounteten Nachricht erneut. Längeres Transkript, mehr gemountete Turns, größere Rechnung. Der Kommentar im neuen Code nennt eine Zahl: etwa 0,5 ms pro Tastendruck in einer kurzen Session, bis zu 19 ms, sobald der Verlauf einer langen Session auf dem Bildschirm ist — und das ist nur ein Teil der Kosten.
Das erklärt das kontraintuitive Gefühl: Das Tippen verschlechtert sich nicht so sehr, während die Session älter wird, sondern während du nach oben scrollst. Erst das Hochscrollen mountet den Verlauf tatsächlich.
Der Fix: nur in den Store schreiben, wenn sich etwas Sichtbares ändert
Die Änderung ist kein nachträglich ans Eingabefeld geschraubtes Debounce. Sie macht aus „wann muss dieser Tastendruck den Store erreichen?“ explizite Logik. Ein neues Modul, composer-store-writer.ts, leitet mit composerTextEdges(text) eine Signatur aus dem Text ab — genau die Fakten, die die Abonnenten interessieren:
text.length === 0 // empty vs not
trimmed.length > 0 // has real content
text === '?' // a lone question mark
trimmed.length > 0 && !SLASH_COMMAND_RE // ordinary text, not a slash command
text.trimEnd().includes('\n') // multi-line layout
Bleibt diese Signatur bei einem Tastendruck unverändert, würden alle Abonnenten denselben Zustand sehen, also kann das Schreiben warten. Wie lange? TYPING_STORE_IDLE_MS = 180 — nach einer Tipppause von 180 ms landet der nachlaufende Text im Store, wo Draft-Persistenz und der Runtime-Spiegel ihn lesen.
Aufgeschobene Schreibvorgänge brauchen abgedeckte Randfälle, sonst tauschst du eine Verzögerung gegen einen Bug:
flush()— ausstehenden Text landen lassen, bevor irgendein Lesevorgang den Store aktuell braucht;cancel()— ausstehenden Text beim Unmount verwerfen, statt veralteten Zustand zu hinterlassen;observed()— eine anderswo vorgenommene Store-Änderung (das Runtime-Abonnement) als neue Basis übernehmen, damit ein alter Wert keinen neuen überschreibt.
Der Sticky-Prompt-Streifen (das gespiegelte Eingabefeld, das über einem langen Transkript angeheftet bleibt) berechnet die Styles nicht mehr pro Frame für jeden sichtbaren Turn neu; er registriert jetzt einmal eine CSS-Variable.
Die Messungen
Der PR liefert ein reproduzierbares Setup: eine Session mit 300 Turns und 1.800 Nachrichten, durchgescrollt mit 2.500 Mausrad-Frames, damit der gesamte Verlauf gemountet ist (rund 42.000 DOM-Elemente), dann 60 getippte Zeichen.
| Messgröße | Vorher | Nachher |
|---|---|---|
| Hauptthread-Zeit pro Tastendruck, Verlauf nach oben gelesen | 33,9 ms | 9,6 ms |
| Hauptthread-Zeit pro Tastendruck, frisch geöffnet | 12,1 ms | 6,0 ms |
| Store-Flush pro Tastendruck | 18,0 ms | 1,3 ms |
Der Autor ist vorsichtig damit, was diese Zahlen bedeuten, und die Ehrlichkeit ist es wert, wiederholt zu werden: Einzelläufe auf einem geteilten Host, wobei ein zweiter Lauf vor dem Fix 49,3 ms pro Taste maß. Auf dieser 20-Kern-Maschine blieb die Zeit von der Eingabe bis zum Paint so oder so nahe einem Frame — der Unterschied zwischen 33,9 und 9,6 ist also vor allem auf langsamerer Hardware spürbar, wo Hauptthread-Zeit die knappe Ressource ist. Das ist kein „alles ist dreimal schneller geworden“, sondern „dein Laptop zahlt nicht länger eine Abgabe pro Tastendruck auf einen Verlauf, den er längst gerendert hat“.
Was du damit tun kannst
Die Änderung ist derzeit nur auf main. v0.22.5 wurde am 23. Oktober ausgeliefert; dieser PR wurde am 26. Oktober gemergt, also enthalten selbst die Canary-Tags von diesem Tag ihn nicht. Drei Wege:
- auf das nächste Release warten und
hermes update— der geringste Aufwand; mainverfolgen, wenn du es heute willst (der Kanalwechsel ist im Leitfaden für Update-Kanäle beschrieben);- es jetzt entschärfen, indem du Sessions kürzer hältst — die Kosten des Eingabefelds skalieren mit dem gemounteten Verlauf, also hilft eine neue Session oder ein Aufräumen der Session-Grenzen mehr als jede Einstellung.
Wenn deine Beschwerde eigentlich „Desktop startet langsam“ war, ist das ein anderes Problem, behandelt in der Analyse zur Startlatenz. Diese Änderung tut dafür nichts.
Warum man sich diesen Fall merken sollte
Wir geben standardmäßig dem Modell die Schuld, wenn ein Agent langsam wirkt: zu viele Tokens, zu viel Kontext, ein Provider, der Anfragen in die Warteschlange stellt. Dieser Bug ist eine Erinnerung daran, dass die UI-Schicht ihre eigenen Performance-Schulden aufbaut, und dass diese Art Schulden einzigartig unsichtbar ist — sie taucht nie in einem Log oder einem Trace auf, nur zwischen deinen Fingern und dem Bildschirm.
Also frag beim nächsten Mal, welche Hälfte langsam ist. Tippen langsam, während Antworten normal ankommen, ist ein Rendering-Problem. Beides langsam ist der Moment, in dem du Modell und Netzwerk anschaust. Diese Frage in zwei zu teilen spart den größten Teil des Debuggens.