# Timeline: Verlaufsseiten, Live-Overlay, Settlement

> Wie eine geordnete Konversation aus gespeicherten Verlaufsseiten, einem revisionierten Live-Overlay und ausstehenden Nachrichten zusammengesetzt wird, und warum Identität statt Text oder Zeit entscheidet, was existiert.

Handbuch / Architektur · Gegen den Quellcode geprüft am 2026-10-05 · https://www.hbcode.ai/de/docs/timeline

## Die Timeline fügt drei Quellen zu einer Liste zusammen

**Eine Zeile erscheint einmal pro Schlüssel, egal wie viele Quellen eine Kopie halten**

Ihre ausstehenden Nachrichten, das Live-Overlay und Verlaufsseiten können je eine Kopie derselben Zeile halten. Ein Client behält eine Zeile pro typisiertem Schlüssel: Eine Verlaufskopie gewinnt gegen eine Live-Kopie, ein ausstehendes Echo verschwindet, sobald eine Zeile seine Operations-ID trägt, und zwei Nachrichten mit demselben Text bleiben zwei Zeilen, weil ihre Schlüssel sich unterscheiden. Die Schlüssel und Nachrichten sind Beispiele.

- **Typisierter Schlüssel** (type + "\0" + id): Eine Verlaufskopie und eine Live-Kopie derselben Zeile tragen denselben Schlüssel. Ein Client behält eine Zeile pro Schlüssel und ordnet Zeilen nie über ihren Text zu.
- **Verlauf vor Live** (origin: canonical): Teilen sich eine Verlaufskopie und eine Live-Kopie einen Schlüssel, zeigt der Client die Verlaufskopie. Der Desktop entfernt die Live-Kopie aus dem Overlay, sobald die Zeile im Verlauf steht.
- **Ausstehendes Echo** (user-message::<operationId>): Der Desktop zeigt eine gesendete Nachricht sofort an, unter dem Schlüssel, den ihre Verlaufszeile haben wird, und blendet dieses Echo aus, sobald eine Zeile ihre Operations-ID trägt.

| Eingabe | Autorität | Wann abzugleichen ist |
| --- | --- | --- |
| Kanonisches History-Fenster | Die Desktop-History-Route und ihr Boundary-Token. | Beim initialen Laden, Aktualisieren und Spleißen älterer Seiten. |
| Live-Overlay | Desktop settlementEpoch, revision und typisierte Live-Blöcke. | Bei gültigen Snapshots des aktuellen Ziels oder Live-Aktualisierungen. |
| Outbox und übermitteltes Echo | Desktop-Custody und -Akzeptanz. Die lokale Benutzeroberfläche kann ausstehende Absichten anzeigen. | Wenn sich die Outbox ändert oder kanonische Nachweise eine akzeptierte Operation bestätigen. |
| Viewport und Messungen | Die ausgewählte Client-Ansicht. | Beim Wechseln des Ziels, Laden älterer Inhalte oder Verfolgen neuer Aktivitäten. |

## Typisierte Identität und kanonische Reihenfolge

Jeder Block hat einen typisierten Identitätsschlüssel, den Blocktyp mit einem NUL-Byte an die Block-ID gefügt. Die fünf Blocktypen sind user, system, activity, agent-reply und thinking. Eine Block-ID ist nur innerhalb ihres Typs eindeutig. Die erste Abbildung zeigt, wie Clients Zeilen über diesen Schlüssel zusammenführen. Zeitstempel sind Metadaten für die Reihenfolge und nie Identität für die Deduplizierung.

Nach jedem wesentlichen Merge sortiert ein Client neu: nach Zeitstempel, dann nach dem sourceOrder-Vektor vom Desktop, dann nach dem Identitätsschlüssel. sourceOrder ist eine kurze Liste von Zahlen, die Element für Element verglichen wird. Die erste Zahl ordnet die Quelle der Zeile ein, deshalb sortiert sich eine Verlaufszeile vor eine Live-Zeile mit demselben Zeitstempel. Eine Zeile ohne sourceOrder sortiert sich bei gleichem Zeitstempel hinter Zeilen mit einem.

| Quelle der Zeile | sourceOrder | Woher die Zahlen kommen |
| --- | --- | --- |
| Verlauf, paginiert | [0, 1, ordinal, entryIndex] | Die Ordinalzahl des gespeicherten Elements und der Index der Zeile in diesem Element. Codex-Seiten und Claude-Seiten nutzen beide diese Form. |
| Verlauf, alter Codex-Lesepfad | [0, 0, ordinal, entryIndex] | Die Position des Elements in einer thread/resume-Antwort. |
| Live-Zeile | [2, sequence] | Ein Zähler im Desktop-Prozess. Die Zeile erhält ihre Nummer einmal, wenn sie zum ersten Mal in den Run eintritt. |
| Statuszeile eines aktiven Runs | [2, startedAt] | Die Startzeit des Runs in Millisekunden, damit jeder Neuaufbau dieselbe Position ergibt. |
| Synthetische Zeile | [3, sequence] | Eine Zeile, die der Desktop ohne Transkript-Element und ohne Live-Ereignis erzeugt hat. |

Für IDs gilt bei beiden Agenten eine Regel: Eine Live-Zeile muss die ID tragen, die ihre gespeicherte Zeile haben wird. Das Settlement und der Merge im Client vergleichen nur Schlüssel. Ein Adapter, der einer Live-Zeile eine andere ID gäbe, würde diese Zeile zweimal zeigen.

Der Desktop liest eigenen paginierten Codex-Verlauf über thread/items/list, neueste zuerst, höchstens 100 Elemente pro Sidecar-Seite. Er bewahrt createdAtMs und updatedAtOrdinal, ein nicht unterstütztes öffentliches Element erscheint als Diagnosezeile, die den Cursor trotzdem weiterbewegt, und ein Eintrag mit weder noch beiden von item und unsupportedItem lässt die ganze Seite scheitern. Das boundaryToken, das ein Client erhält, ist der SHA-256 des Rollout-Pfads oder von owned-thread-store plus Besitzer-ID, es identifiziert also die Verlaufsquelle und nicht eine Seite. oldestCursor ist das Präfix thread-items-v5: gefolgt von base64url-JSON mit der Thread-ID, dem rohen Sidecar-Cursor, einer Generation, diesem Quellentoken, dem vorherigen führenden Blockschlüssel und der Fensterrevision. Ein Cursor für einen anderen Thread wird abgelehnt. Der Desktop parst kein Codex-JSONL und fragt die privaten Datenbanken des Kinds nicht ab.

Eine Claude-Session nutzt dieselben typisierten Schlüssel, dieselbe Reihenfolge und dieselben Zeilentypen. Nur die Verlaufsquelle unterscheidet sich. Der Desktop liest Claudes gespeichertes Transkript über das Claude Agent SDK und projiziert jede Nachricht in Timeline-Zeilen. Das boundaryToken ist die Claude-Session-ID oder bei einem Child-Thread die Subagent-ID. oldestCursor ist JSON mit vier Feldern: session enthält diese ID, before die UUID der ältesten geladenen Nachricht, anchor den führenden Blockschlüssel und revision die Fensterrevision. Der Desktop akzeptiert höchstens 4.096 Zeichen und lehnt einen Cursor ab, dessen session von der offenen Unterhaltung oder dem offenen Subagenten abweicht. Wenn das Transkript die before-Nachricht nicht mehr enthält, kommt die Seite leer mit reloadRequired zurück. Ein Transkript, das nicht existiert, lädt als leere Unterhaltung. Der gespeicherte Claude-Verlauf beginnt nach der letzten Komprimierung, weil das SDK nur diesen Teil liefert.

- [Claude-Verlauf und die gemeinsamen Timeline-Zeilen](https://www.hbcode.ai/de/docs/claude-agent-sdk.md#history-projection): Wie aus einer Claude-Nachricht Zeilen werden und welche IDs ihre Text-, Thinking- und Tool-Zeilen erhalten.

## Live-Overlay-Revisionen und die Settlement-Epoche

Eine Timeline enthält kanonische History sowie Live-Blöcke, die dort noch nicht vollständig abgebildet sind. Der Desktop speichert pro Ziel einen Overlay-Snapshot in der Tabelle chat_live_timeline_overlays, mit dem Schlüssel app_session_id plus timeline_target_id. Das Ziel ist bei der Hauptunterhaltung die Session und bei einem Kind die Thread-ID oder die Subagent-ID. Das Snapshot-Format ist Version 2. Es speichert formatVersion, settlementEpoch, revision und Blöcke, die Wire-Daten sowie Settlement-Metadaten enthalten. Der Client erhält lediglich settlementEpoch, revision und Wire-Blöcke. Die interne Settlement-Identität und -Provenienz verbleiben auf dem Desktop.

**Das Overlay gibt eine Live-Zeile nur in einem Snapshot ab, der ihre Verlaufszeile mitbringt**

Zeile A ist in der Desktop-App und auf einem Telefon live. Lädt die Desktop-App ein neuestes Fenster, dessen Verlaufsseite A enthält, entfernt der Desktop A aus dem Overlay und erhöht die Settlement-Epoche in derselben Antwort. Das Telefon lehnt das nächste Live-Update ab, weil dessen Epoche neuer ist, behält seine Zeilen und lädt sein eigenes neuestes Fenster. So wird A zu einer Verlaufszeile, ohne je zu verschwinden. Der gestrichelte Balken zeigt, was das Anwenden dieses Updates bewirkt hätte. Die Epochennummern sind Beispiele.

- **Settlement-Epoche** (liveOverlay.settlementEpoch): Steigt nur, wenn eine Live-Zeile in den Verlauf wechselt. Ein Client übernimmt eine höhere Epoche nur aus einem Snapshot des neuesten Fensters, weil nur dieser Snapshot auch die Verlaufszeilen mitbringt.
- **Revision** (liveOverlay.revision): Ein globaler Zähler auf dem Desktop. Innerhalb einer Epoche ersetzt eine höhere Revision das ganze Overlay, eine niedrigere wird ignoriert, und dieselbe Revision mit anderen Zeilen lässt den Client neu laden.
- **Neuestes Fenster** (run.chatLoad · run.chatReconcileSessions): Der Desktop liest die neueste Verlaufsseite, entfernt Overlay-Zeilen, deren Schlüssel diese Seite enthält, und gibt beides in einer Antwort zurück. Die Desktop-App ruft denselben Builder auf. run.chatReconcileSessions tut das für höchstens 16 Sessions in einem Aufruf.

liveOverlay ist { settlementEpoch, revision, blocks } und enthält die vollständige, noch nicht aufgelöste Live-Projektion für ein Ziel. Innerhalb einer Settlement-Epoche löscht eine leere höhere Revision das Overlay. Revisionen stammen aus einer dauerhaften globalen Sequenz, der einzigen Zeile von chat_live_timeline_overlay_revisions, deshalb kann ein gelöschtes Ziel keine alte Revision wiederverwenden. Ein leerer Ziel-Snapshot wird gespeichert, damit das Ziel seine Epoche und Revision behält. Overlay-Schreibvorgänge vergleichen eine erwartete Revision innerhalb einer Transaktion, der Zähler ist bei JavaScripts exakter Ganzzahlgrenze 9.007.199.254.740.991 gedeckelt, und ein veralteter Schreiber wiederholt gegen den aktuellen Zustand, statt ihn zu überschreiben.

Das Overlay enthält zwei Klassen von Zeilen. Eine gewöhnliche Live-Zeile hat ein gespeichertes Gegenstück im Verlauf des Agenten: eine Nutzernachricht, Antworttext, einen Befehl, einen Tool-Aufruf. Die anderen Zeilen sind flüchtig. Keine Verlaufszeile ersetzt sie jemals.

| Zeilenklasse | Was sie ist | Wie sie das Overlay verlässt |
| --- | --- | --- |
| Gewöhnliche Live-Zeile | Eine Zeile, deren Schlüssel auch im gespeicherten Verlauf vorkommt. | Ein Lesen des Verlaufs belegt ihren Schlüssel. Die Abbildung unten zeigt die drei Lesewege. |
| Statuszeile | Der thinking-Block mit der ID thinking-<runId>. Sein Phasenlabel kommt von der neuesten Aktivitätszeile: Reasoning, Running command, Editing files, Reading, Using tool, Planning oder Working. | Eine neuere Statuszeile für dasselbe Ziel ersetzt sie. Ein neuestes Fenster entfernt sie, wenn ein anderer Run der aktuelle Run ist. Die Codex-Bereinigung nach einem Run entfernt sie. |
| Marker für die Kontextkomprimierung | Der Marker, der erscheint, während der Agent seinen Kontext komprimiert. | Ein neuestes Fenster entfernt ihn, wenn sein Run nicht der aktuelle Run ist. Die Codex-Bereinigung nach einem Run entfernt ihn. |
| Fortschrittsspiegel im Elternthread | Eine Kopie des Fortschritts eines Child-Threads in der Elternunterhaltung. | Ein Spiegel-Schreibvorgang eines anderen Runs ersetzt ihn. Ein neuestes Fenster entfernt ihn, wenn ein anderer Run der aktuelle Run ist. Die Codex-Bereinigung nach einem Run entfernt ihn. |
| Freigabekarte | Eine Aktivitätszeile mit einem approval-Feld. | Sie bleibt mit ihrer Entscheidung stehen. Die Codex-Bereinigung nach einem Run entfernt sie zusammen mit den anderen flüchtigen Zeilen dieses Runs. Ein Claude-Run hat keine solche Bereinigung, seine Karte bleibt deshalb im Overlay. |

Die Settlement-Epoche ist ein Sicherheitszaun pro Ziel. Der Desktop erhöht sie in demselben Schreibvorgang, der mindestens eine gewöhnliche Live-Zeile entfernt, weil der Verlauf diese Zeile jetzt enthält. Flüchtige Zeilen erhöhen sie nie. Ein Herausgeber kann außerdem eine Zeile zurückziehen, die er nicht mehr zeigt, weil keine Verlaufszeile eine solche Zeile je abschließen würde.

**Ein neuestes Fenster schließt eine Live-Zeile bei beiden Agenten ab, und nur Codex hat zwei weitere Pfade**

Vier Fälle derselben Live-Zeile A auf einer Zeitachse, mit dem Ende des Runs und einem App-Neustart als festen Momenten. Wenn ein Client während des Runs ein neuestes Fenster lädt, enthält die Seite A, und das Overlay entfernt A, bei beiden Agenten. Wenn kein Client lädt, endet ein Codex-Run mit einem exakten Scan, der A abschließt. Wenn die App vor diesem Scan beendet wird, überlebt die Journalzeile der Bereinigung, und die Reparatur schließt A nach dem Neustart ab. Eine Claude-Zeile bleibt über das Ende des Runs und den Neustart hinweg im Overlay, bis das nächste neueste Fenster lädt.

- **Neuestes Fenster** (run.chatLoad · run.chatReconcileSessions): Von beiden Agenten geteilt. Der Desktop entfernt jede Overlay-Zeile, deren Schlüssel die geladene Seite enthält, und erhöht die Epoche in demselben Schreibvorgang.
- **Exakter Scan** (thread/items/list · 3 Scans · 100 ms, 250 ms): Nur Codex. Nach dem Abschlussereignis durchsucht der Desktop den Verlauf bis zum Ende nach den exakten Schlüsseln dieses Runs. Ein nicht gefundener Schlüssel erhält einen zweiten Scan nach 100 ms und einen dritten nach 250 ms.
- **Reparatur nach Neustart** (Journalzeile: Session, Run): Nur Codex. Die Journalzeile nennt die Session und den Run. Sie bleibt, bis ein Scan jede Zeile dieses Runs abgeschlossen hat, und die Reparatur startet, wenn ein Ziel der Session geöffnet wird.

Drei Lesewege können eine gewöhnliche Zeile abschließen. Der erste ist gemeinsam: Wenn ein neuestes Fenster lädt, entfernt der Desktop jede Overlay-Zeile, deren Schlüssel diese Seite enthält. Beide Agenten rufen dafür dieselbe Funktion auf, und eine Claude-Session hat nur diesen einen Leseweg. Die anderen beiden sind Codex-Pfade. Am Ende eines Codex-Runs schreibt der Desktop die Session und den Run in chat_pending_terminal_overlay_cleanups. Dann durchsucht er thread/items/list bis zum Ende nach den exakten Schlüsseln dieses Runs in jedem Ziel der Session. Ein Scan, der einen Schlüssel nicht findet, läuft nach 100 Millisekunden und nach 250 Millisekunden erneut, insgesamt drei Scans. Die ganze Bereinigung hat 2 Minuten. Wenn sie scheitert oder die App beendet wird, bleibt die Journalzeile bestehen, und die Reparatur führt denselben exakten Scan aus, sobald ein Codex-Ziel dieser Session geöffnet wird.

Telefone holen mehrere Sessions in einem Aufruf. run.chatReconcileSessions nimmt höchstens 16 Sessions an und liefert für jede das neueste Fenster, das Overlay, die Outbox und den aktiven Run. Es erlaubt 256 Outbox-Einträge pro Session und prüft die Grenze von 8 MiB, während die Antwort wächst. Mobil wendet ein Fenster und sein Overlay in einer Reducer-Operation an.

## Live-Zustellung: Eine Funktion sendet an drei Senken

Jede Live-Änderung verlässt die Desktop-Laufzeit durch eine Funktion, emit_chat_stream_event. Sie speist drei Senken: das WebView-Ereignis chat:stream, das WebView-Ereignis chat:progress und das Relay-Ereignis chat:timeline-updated. Der Ereignisname in der Payload entscheidet, welche Senken sie erhalten.

**Ein Schreibvorgang geht an drei Senken, und jede Senke erhält einen anderen Teil**

Ein Codex-Run startet, schreibt das Overlay dreimal innerhalb von 50 Millisekunden, bleibt still und wird abgeschlossen. Jeder Schreibvorgang geht sofort an das Relay. Ein abonniertes Desktop-Fenster erhält ein Ereignis mit dem neuesten Snapshot, wenn der Timer von 50 Millisekunden endet. Nach einem vollen Tick von 5 Sekunden ohne neuen Schreibvorgang sendet die Laufzeit das gespeicherte Overlay erneut an das Relay, und kein Fenster erhält diese Wiederholung. Nur die Ereignisse started und completed erreichen chat:progress. Die Revisionsnummern sind Beispiele.

- **Block-Snapshot** (event: blocks · liveOverlay): Das ganze Overlay eines Ziels nach einem Schreibvorgang. Es geht an das Relay und an chat:stream und nie an chat:progress.
- **Zusammenfassung** (50 ms · 128 Ziele): Der erste Snapshot für ein Ziel startet den Timer, und ein neuerer Snapshot ersetzt den wartenden. Ein Run-Ereignis sendet zuerst die wartenden Snapshots.
- **Abonnement** (subscriptionId · streamRevision): Ein Fenster erhält Snapshots nur für das Ziel, das es abonniert hat. Run-Ereignisse gehen an jedes Fenster.
- **Relay-Ereignis** (chat:timeline-updated): Wird nur für eine Session gesendet, an der ein Telefon Interesse hält. Der Desktop fasst es nicht zusammen.
- **Wiederholung** (alle 5 s): Für jedes Ziel eines aktiven Runs ohne neuen Schreibvorgang seit dem letzten Tick liest die Laufzeit das gespeicherte Overlay und sendet es erneut an das Relay.

| Ereignis in der Payload | chat:stream (Desktop-Fenster) | chat:progress (Desktop) | Relay-Ereignis (Telefone) |
| --- | --- | --- | --- |
| blocks | Nur Fenster mit einem Live-Abonnement für dieses Ziel, 50 Millisekunden lang zusammengefasst. | Nein. | Ja, sofort. |
| started, completed, failed, aborted | Jedes Fenster. | Ja, als event, appSessionId, runId, timestamp und operationId. | Ja. Ein failed-Ereignis trägt außerdem agentErrorInfo. |
| runtime | Jedes Fenster. | Nein. | Ja. |
| agent-threads | Jedes Fenster. | Nein. | Ja. |

Eine agent-threads-Payload hat nur event, appSessionId, timestamp und eine threadId mit dem Wert null. Sie sagt jedem Client, dass sich die Agentenliste der Session geändert hat: Ein Kind-Thread oder ein Subagent hat gestartet, oder einer von ihnen hat einen neuen Status. Der Client lädt die Liste dann neu. Keine Timeline-Zeile trägt diese Angabe. Keine Zeile steht für einen Subagent oder einen Workflow, und keine Zeile öffnet einen Thread. Eine Session sendet ihr erstes Signal sofort und danach höchstens ein weiteres Signal am Ende eines Fensters von 1 Sekunde.

Eine blocks-Payload ist ein Snapshot: das ganze Overlay eines Ziels nach einem Schreibvorgang, als liveOverlay. Der Erzeuger speichert das Overlay zuerst und sendet den gespeicherten Snapshot. Ein Client, der ein Ereignis verpasst hat, verliert deshalb nichts, was der nächste Snapshot nicht enthält.

Ein Desktop-Fenster abonniert jeweils ein Ziel. Der Abonnement-Aufruf liefert das gespeicherte Overlay, das Fenster startet also mit einem vollständigen Zustand. Danach sendet die Laufzeit dem Fenster nur Snapshots seines Ziels und versieht jeden mit der subscriptionId und einer streamRevision, einem Zähler für alle Fenster gemeinsam. Der erste Snapshot für ein Ziel startet einen Timer von 50 Millisekunden. Ein neuerer Snapshot für dasselbe Ziel ersetzt den wartenden, und der Timer sendet ein Ereignis. Höchstens 128 Ziele warten gleichzeitig. Ein weiteres Ziel sendet alle sofort. Ein Run-Ereignis sendet zuerst die wartenden Snapshots, deshalb sieht ein Fenster completed nie vor den letzten Zeilen.

Die Relay-Senke leitet eine Payload nur für eine Session weiter, an der ein Telefon Interesse hält. Eine blocks-Payload braucht außerdem ein liveOverlay mit genau settlementEpoch, revision und blocks, eine Revision über 0 und beide Zahlen im exakten Ganzzahlbereich. Der Desktop fasst diese Senke nicht zusammen.

Ein Telefon kann unterwegs ein Ereignis verlieren. Für diesen Fall läuft alle 5 Sekunden ein Reparatur-Timer, solange ein Run aktiv ist. Für jedes Ziel ohne neuen Schreibvorgang seit dem letzten Tick liest er das gespeicherte Overlay und sendet es erneut an das Relay. Die Wiederholung geht nicht an Desktop-Fenster. Die gemeinsamen Live-Emitter starten diesen Timer. Der Codex-Adapter sendet jede Zeile über sie. Der Claude-Adapter speichert und sendet seine Zeilen direkt und nutzt einen gemeinsamen Emitter nur für eine Freigabekarte. Ein Claude-Run hat den Timer deshalb erst nach seiner ersten Karte.

- [Timeline-Events erreichen nur interessierte Telefone](https://www.hbcode.ai/de/docs/device-routing.md#interest): Wie ein Telefon Interesse an einer Session anmeldet und wie lange das Relay es behält.
- [Claude-Live-Runs](https://www.hbcode.ai/de/docs/claude-agent-sdk.md#live-runs): Wie ein Claude-Run seine Zeilen veröffentlicht und endet.

## Freigaben nutzen ein gemeinsames Modell

Eine Freigabeanfrage von jedem der beiden Agenten wird zum selben ChatApprovalRequest. Er nennt die Request-ID, den Thread, den Turn, das Item und die Entscheidungen, die diese Anfrage erlaubt. Der Desktop lehnt eine Entscheidung ab, die nicht in dieser Liste steht.

| Teil | Werte |
| --- | --- |
| Art | commandExecution, fileChange, permissions. |
| Entscheidung | accept, acceptForSession, acceptBroadly, decline, cancel. |
| Ergebnis | accepted, acceptedForSession, acceptedBroadly, declined, cancelled, resolvedExternally. |
| Kartenstatus | pending, dann das Ergebnis. resolvedExternally erscheint als expired. |

Die Karte ist eine Live-Aktivitätszeile der Art event. Ihre ID beginnt mit approval: und der Request-ID, gefolgt von der Thread-ID, der Turn-ID und der Item-ID, jeweils mit vorangestellter Länge. Dieselbe ID trägt die offene Karte und die entschiedene Karte, die Entscheidung ersetzt die Karte also an Ort und Stelle. Die Karte ist eine flüchtige Overlay-Zeile.

Clients antworten mit run.chatResolveApproval, das requestId, threadId, turnId, itemId und decision trägt. Eine Session-ID trägt es nicht. Die Dispatch-Schicht fragt zuerst den Claude-Adapter, der antwortet, wenn er eine offene Berechtigung mit dieser Request-ID hält. Andernfalls erledigt sie der Codex-App-Server, dem die Anfrage gehört.

- [Codex: wer eine Freigabe beantwortet](https://www.hbcode.ai/de/docs/codex-turns.md#approvals): Die Codex-Anfragetypen, die Frist und was eine abgelaufene Anfrage bewirkt.
- [Claude-Live-Runs und Freigaben](https://www.hbcode.ai/de/docs/claude-agent-sdk.md#live-runs): Wie aus einer Claude-Berechtigungsanfrage eine Karte wird und wie die Antwort zum Host zurückkehrt.

## Der Desktop schreibt die Wörter jeder Zeile

Der Desktop besitzt die Wörter und die Angaben einer Aktivitätszeile, und jeder Client besitzt das Layout. Ein Client zeigt die Wörter so, wie sie sind, und erzeugt keine eigenen Wörter. Desktop-Tests prüfen diese Regeln an den Zeilen der gemeinsamen Beispiele.

| Feld | Regel |
| --- | --- |
| title | Die Überschrift der Zeile, zum Beispiel „Message to Android landing“. |
| subtitle | Die Kontextzeile, zum Beispiel die Zusammenfassung einer Nachricht. Sie wiederholt nie den Titel. Eine Befehlszeile hat keine. |
| display.signal | Eine Tatsache, zum Beispiel „Exit 1“, „Draft 2“ oder „Sent“. Sie ist nie ein Statuswort und wiederholt nie den Titel oder die Kontextzeile. |
| status | Der Zustand der Zeile: in-progress, completed, failed oder declined. Die Kontextzeile und das Signal sagen nie ein Statuswort. |
| detail | Der Text, der sich bei einem Tippen öffnet, mit seinem Format: markdown, plaintext oder shell. Ein plaintext-Detail hat nur dann einen languageHint, wenn der Text vollständiges JSON, JSONL, ein Unified Diff oder ein Dump der Form FILE <path> ist. Ein Lesen einer Datei hat einen sourcePath. Eine Nachrichtenzeile hat kein detail. |

Jeder Client zeigt ein plaintext-Detail ohne languageHint und ohne sourcePath als einfachen umbrochenen Text. Er rät keine Sprache, färbt keine Schlüsselwörter und zeigt keine Zeilennummern.

## Nachrichtenzeilen tragen eine typisierte Angabe message

Eine Zeile, die eine Nachricht zwischen zwei Sessions oder zwischen zwei Agenten ist, trägt eine typisierte Angabe: message. Eine Aktivitätszeile hat sie als activity.message. Eine User-Zeile, die eine andere Session gesendet hat, hat sie als message. Der Desktop erzeugt jedes Wort dieser Angabe. Das Desktop-Fenster und die Telefone zeigen diese Wörter und erzeugen keine eigenen Wörter.

| Feld | Inhalt |
| --- | --- |
| direction | sent oder received. |
| kind | message, oder task bei einer empfangenen Zeile, die einem Agenten seine Aufgabe gibt. |
| from, to | Die Wörter für die beiden Enden. Sie sind nie leer und nie eine ID. Das Ende, das die Zeile zeigt, ist „This session“, oder „This agent“ in einem Agent-Thread. |
| peer | Die Art des anderen Endes, session oder agent, und sein Agent, codex oder claude. Der Agent fehlt nur, wenn der Aufruf eine Session nannte, die es nicht gibt. |
| delivery | Nur bei einer gesendeten Zeile, deren Aufruf beendet ist: sent, needs-approval oder not-sent. |
| failureReason | Nur wenn delivery not-sent ist: die erste Zeile des Grunds, höchstens 220 Zeichen. |
| link | Das andere Ende der Nachricht: appSessionId (fehlt: diese Session), threadId (fehlt: die Haupt-Timeline) und ein opaker anchor. |

Die Wörter für das andere Ende:

- Eine andere HB-Code-Session: ihr Titel, „Untitled session“ bei einem leeren Titel oder „Unknown session“, wenn der Aufruf keine Session nannte.
- Der führende Agent einer Claude-Session: „Claude“.
- Ein Claude-Subagent: sein Titel in der Agentenliste, sonst „Subagent“.
- Eine Claude-Session außerhalb von HB Code: ihr Name, sonst „Another Claude session“.
- Codex: „Codex“ für den Root-Thread und die Wörter des Agentenpfads für einen Agent-Thread, zum Beispiel „Android landing“.

Der Desktop erzeugt auch den Titel der Zeile: „Message to <to>“ bei einer gesendeten Zeile und „Message from <from>“ oder „Task from <from>“ bei einer empfangenen Zeile. Eine User-Zeile hat einen Titel nur zusammen mit message. Jeder Client verwendet den Titel als Bedienungshilfen-Label und als Titel des Nachrichtendetails. display.signal ist Sent, Needs approval oder Not sent, abgeleitet aus delivery. Solange ihr Aufruf läuft, hat eine Zeile kein Signal. Eine Nachrichtenzeile hat auf der Leitung kein detail. Jeder Client erzeugt das Detail aus dem Titel, der Angabe message und dem Text der Zeile.

Diese Zeilen sind Nachrichtenzeilen: das Ergebnis eines send_session_message-Aufrufs, die User-Zeile, die eine andere Session gesendet hat, ein Claude-SendMessage-Aufruf, die Nachrichten des führenden Agenten in einem Claude-Subagent-Thread, eine Nachricht von einer anderen Claude-Session und eine Nachricht, die ein Codex-Thread von einem anderen Agenten bekommen hat. Ein send_session_message-Aufruf, der noch läuft, bleibt die einfache Aufrufzeile „Send message to session“.

Eine Zeile verlinkt auf ihr anderes Ende, wenn eine Timeline von HB Code dieses Ende enthält. Eine gesendete Session-Nachricht verlinkt auf die User-Zeile in der Empfänger-Session, aber nur solange ihre delivery sent ist. Die empfangene User-Zeile verlinkt auf die Aufrufzeile des Absenders, in der Haupt-Timeline oder in dem Agent-Thread, der die Nachricht gesendet hat. Ein Claude-SendMessage an einen Subagent dieser Session verlinkt auf den Subagent-Thread zum Zeitpunkt des Aufrufs. Eine Nachricht des führenden Agenten in einem Claude-Subagent-Thread verlinkt auf die Haupt-Timeline zum Zeitpunkt der Nachricht. Codex-Zeilen und Nachrichten von einer anderen Claude-Session haben keinen Link. Der Desktop vergleicht nie Text, um das andere Ende zu finden.

Die delivery einer gesendeten Session-Nachricht folgt der Warteschlange des Empfängers. Die Tabelle chat_session_messages speichert sie: sent, needs-approval, solange die Nachricht auf Sie wartet, oder declined, nachdem Sie die Nachricht aus der Warteschlange entfernt haben. Eine abgelehnte Nachricht erscheint als not-sent mit dem Grund „The user removed the message from the queue.“ Jede Änderung der Warteschlange veröffentlicht die Zeile des Absenders erneut.

- [Nachrichten zwischen Sessions](https://www.hbcode.ai/de/docs/message-delivery.md#session-messages): Der Umschlag, die Grenzen und die Regeln zum Zurückhalten auf dem Desktop.

## Wie eine ältere Seite an die Liste anschließt

**Eine ältere Seite setzt nur an der Zeile an, an der ihr Cursor geschnitten wurde**

Der Cursor merkt sich die älteste Zeile, die das Fenster bei seiner Ausgabe hielt: die Naht. Der Desktop liefert die älteren Zeilen, gefolgt vom Schlüssel dieser Naht, mit einer um eins höheren Fensterrevision. Ein Client stellt die Zeilen nur voran, solange diese Naht noch an ihrem Platz ist und die Revision neuer ist, und verwirft die Seite sonst. Die Naht des nächsten Cursors ist die oberste Zeile der Seite. Die Zeilennamen sind Beispiele.

- **Naht** (boundaryBlockKeys): Der Schlüssel der ältesten Zeile, die das Fenster bei der Ausgabe des Cursors hielt. Der Desktop liest ihn aus dem Cursor zurück und liefert ihn nach den älteren Zeilen.
- **Fensterrevision** (windowRevision): Um eins höher als die Revision im Cursor. Ein Client wendet eine Seite nur an, wenn dieser Wert höher ist als seine eigene Revision, sodass dieselbe Seite nie zweimal landet.
- **Cursor** (thread-items-v5:<base64url>): JSON mit der Thread-ID, dem Sidecar-Cursor, einer Generation, dem Quellentoken, dem Nahtschlüssel und der Revision. Clients senden ihn als beforeCursor zurück, mit ihrem boundaryToken als expectedBoundaryToken.

Ältere Seiten kommen von run.chatLoadPageBefore und run.chatLoadThreadPageBefore mit pageSize, beforeCursor und expectedBoundaryToken. In einer Codex-Session scheitert ein Cursor, dessen Quellentoken von der aktuellen Quelle abweicht, mit Timeline source changed, und eine Anfrage holt bis zu vier Sidecar-Seiten, bis eine einen sichtbaren Block liefert. Eine Antwort über 8 MiB scheitert mit errorCode TIMELINE_SNAPSHOT_TOO_LARGE. Telefone warten 115 Sekunden auf run.chatLoad und run.chatLoadThread. Der Desktop gibt diesen beiden Methoden eine Dispatch-Grenze von 95 Sekunden um eine Handler-Frist von 90 Sekunden, jeder anderen Methode, ältere Seiten eingeschlossen, 45 Sekunden. Sein RPC-Decoder akzeptiert eine pageSize bis 640, bevor die Laufzeit 100 durchsetzt.

| Aktuelle Manifest-Richtlinie | Wert |
| --- | --- |
| Initiale Timeline-Seite | 12 Einträge. |
| Ältere Seite / maximale Seite | 100 / 100 Einträge. |
| Fenster mit Anker | 40 Einträge, für jeden Client. |
| Initialer Timeline-Timeout | 115 Sekunden. |
| Timeout für paginierte Timeline | 60 Sekunden. |
| Timeout für die Bestätigung von Outbox-Befehlen | 20 Sekunden. |
| Initialer Timeline-Wiederholungsversuch | Höchstens 3 Wiederholungen nach der ersten Anfrage, nur für dasselbe aktuelle Timeline-Ziel. Basisverzögerung 0,75 Sekunden, maximal 3 Sekunden. |

> **Ein Client-Timeout bricht die Arbeit am Desktop nicht ab** Der Client-Timeout steuert das Warten und die Wiederherstellung. Er bricht die Desktop-Ausführung nicht ab und beweist nicht, dass eine übermittelte Mutation nicht gespeichert wurde. Verwenden Sie die Outbox- und RPC-Identitätsregeln, wenn eine Bestätigung fehlt.

## Ein Laden mit Anker öffnet das andere Ende an seiner Zeile

Ein Client folgt einem Link in zwei Schritten. Er öffnet die Session und den Thread des Links über seinen normalen Öffnungspfad. Dann lädt er diese Timeline mit einem Anker. run.chatLoad, run.chatLoadThread, chat_load_command und chat_load_thread_command nehmen einen typisierten anchor an.

| Anker | Quelle |
| --- | --- |
| {kind: "link", token} | Der message.link.anchor einer Nachrichtenzeile. |
| {kind: "newer", token} | Der newerCursor einer früheren Antwort für dieselbe Timeline. |
| {kind: "row", row: {type, id}} | Der type und die id eines Blocks der Timeline, die lädt, so wie eine Antwort sie geliefert hat. |

Ein Token ist opak: das Präfix ta1. und base64url-JSON, das der Desktop geschrieben hat. Ein Client erzeugt oder liest nie ein Token. Ein leeres Token oder ein leerer Zeilenschlüssel scheitert im Relay als invalid params und im Desktop-Fenster als Validierungsfehler. Ein Token der anderen Art oder ein Text, den der Desktop nicht geschrieben hat, scheitert mit „The timeline anchor is invalid; reload the timeline“.

Die Antwort ist die normale Ladeantwort mit drei weiteren Feldern, die immer vorhanden sind. anchorRow ist der Block, den der Client zentriert und markiert. anchorNotice ist ein Satz des Desktops, wenn das Laden keine Zeile geöffnet hat, zum Beispiel „This message is not in the saved history.“ oder „This row is not in the saved history.“ Ein Laden mit Link oder mit Zeile hat genau eines der beiden Felder. newerCursor ist gesetzt, wenn das Fenster eine Seite in der Vergangenheit ist. Eine solche Seite hat keinen aktiven Run, und ein Client zeigt unter ihr keine Zeilen des Live-Overlays. Ein Laden mit dem Anker newer liefert die Zeilen, die auf die Seite folgen, ohne Lücke und ohne Überschneidung. Der Client wiederholt dieses Laden, bis eine Antwort keinen newerCursor hat.

Der Desktop öffnet jedes Fenster mit Anker mit 40 Zeilen, dem Wert von mobileChatPolicy.timeline.anchoredPageSize, unabhängig vom pageSize der Anfrage. Eine Stelle in den neuesten 40 Zeilen ergibt das normale neueste Fenster. Bei Claude findet ein einziger Host-Lesevorgang, historyAround, die Seite mit der Stelle, und die Stelle liegt in der Mitte dieser Seite. Bei Codex liest der Desktop Seiten von der neuesten Seite rückwärts, bis eine Seite die Stelle enthält. Eine Zeile, die nur im Live-Overlay ist, hat keine gespeicherte Stelle, daher ergibt ein Link auf sie einen Hinweis.

Kein Client hat ein Bedienelement für Zurück oder Vorwärts. Der Weg zurück ist der Link auf der Zeile am anderen Ende. In jedem Client ist der Name des anderen Endes in der Beschriftungszeile der Zeile der Link. Ein Tippen auf einen anderen Teil der Zeile öffnet das Nachrichtendetail. Wenn eine Lückenwiederherstellung läuft, während eine Seite in der Vergangenheit sichtbar ist, lädt der Client diese Seite erneut mit einem Zeilenanker an der Zeile, die der Leser gerade liest. Die Leseposition bleibt.

- [iOS: Nachrichten zwischen Sessions](https://www.hbcode.ai/de/docs/ios/chat.md#session-messages): Der Link zur Gegenstelle, die genaue Landung und die Warteschlange auf dem iPhone.
- [Android: Nachrichten zwischen Sessions](https://www.hbcode.ai/de/docs/android/chat.md#session-messages): Der Link zur Gegenstelle, die genaue Landung und die Warteschlange auf Android.

## Desktop verwendet eine virtualisierte React-Timeline

useTimelineVirtualizer passt den eingebundenen @timeline-virtualizer an stabile Zeilenschlüssel, Zeilenhöhenschätzungen, gemessene Elemente und innere Leseanker an. Er verankert kurze Inhalte am Ende, folgt angehängten Elementen nur dann, wenn autoScrollToLatest aktiviert ist, und kann eine vorherige Leseposition sowie den Messungs-Cache wiederherstellen. Für das Scrollen ist die direkte DOM-Positionierung aktiviert.

WorkspaceTimelineLayers unterscheidet die übergeordnete Konversation vom ausgewählten untergeordneten Thread. Es weist dem aktiven Ziel den Viewport-Besitz zu und verwendet für das übergeordnete Element einen beibehaltenen Zustand. Der Besitz umfasst ein Ziel, eine Viewport-ID, eine Aktivierungs-Epoche, einen Modus und ein active-Flag. Eine Änderung des ausgewählten Threads wirkt sich daher sowohl auf den Ansichtsbesitz als auch auf die Zeilenliste aus.

## Befehlszeilen tragen Deskriptoren, keine Ausgabe

Eine Befehlszeile trägt den Befehlstext und entweder einen Artefakt-Deskriptor oder die vom Agenten behaltene Ausgabe. Das Rendern oder Abgleichen einer Timeline liest nie Ausgabebytes. Ein offener Betrachter holt sie separat.

- [Befehlsausgabe, Dokumente und Vorschauen](https://www.hbcode.ai/de/docs/output-and-previews.md): Artefakte, Bereichslesezugriffe, die HTML-Sandbox, Webseiten als Markdown und Dateiübertragungen.

## Nützliche Regressionsfälle

- Wechseln Sie das Projekt, während eine History-Anfrage aktiv ist. Die alte Antwort darf das neue Ziel nicht ersetzen.
- Verschieben Sie Live-Inhalte in die kanonische Historie und geben Sie ein leeres Overlay mit einer neueren Settlement-Epoche zurück. Alte Live-Zeilen dürfen nicht wieder auftauchen.
- Senden Sie drei Snapshots für ein Ziel innerhalb von 50 Millisekunden. Ein abonniertes Fenster muss ein Ereignis mit dem neuesten Overlay erhalten, und ein completed-Ereignis muss danach eintreffen.
- Beenden Sie einen Claude-Run und laden Sie kein Fenster. Die Live-Zeilen müssen im Overlay bleiben und es mit dem nächsten neuesten Fenster verlassen.
- Laden Sie ältere Nachrichten, während der Assistent streamt. Behalten Sie den Leseanker bei und vermeiden Sie doppelte Zeilen.
- Geben Sie eine nicht verfügbare oder fehlerhafte History-Antwort zurück. Bewahren Sie lesbare Historie und stellen Sie Wiederherstellungsmöglichkeiten bereit, anstatt fälschlicherweise eine leere Konversation anzuzeigen.
- Folgen Sie einem Link zu einer Zeile, die älter als das neueste Fenster ist, und folgen Sie dann dem Link auf dieser Zeile zurück. Jedes Laden muss genau diese Zeile öffnen, und eine Seite in der Vergangenheit darf unter sich keine Live-Zeilen zeigen.
