Zum Artikel springen
PlanToCodeDocsApp herunterladen

HandbuchArchitektur

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.

Gegen den Quellcode geprüft am 17. September 2026

Auf dieser Seite

Drei Quellen, eine Liste

Eine Zeile erscheint einmal pro Schlüssel, egal wie viele Quellen eine Kopie halten
Eine Zeile erscheint einmal pro Schlüssel, egal wie viele Quellen eine Kopie haltenIhre 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.
useruser-message::op-41
activityitem-7
agent-replyitem-9
useruser-message::op-42
typisierter SchlüsselTyp, dann ID
wartende NachrichtOutbox-Echo
Live-Overlayorigin: live
Verlaufsseiteorigin: canonical
Sie seheneine Zeile pro Schlüssel
npm testläuft · verborgen
nochmal bittesendet · verborgen
nochmal bittefrüher gesendet
nochmal bittefrüher gesendet
npm testExit 0
npm testExit 0
Tests grünstreamt
Tests grünstreamt
nochmal bitteLive-Zeile
nochmal bitteLive-Zeile
„nochmal bitte“ erscheint zweimal: gleicher Text, andere Operations-ID. Text ist nie Identität.
Die Reihenfolge ergibt sich aus Zeitstempel, dann sourceOrder, dann Schlüssel, nie aus der Ankunft.
  • Typisierter Schlüsseltype + "\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 Liveorigin: 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 Echouser-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.

EingabeAutoritätWann abzugleichen ist
Kanonisches History-FensterDie Desktop-History-Route und ihr Boundary-Token.Beim initialen Laden, Aktualisieren und Spleißen älterer Seiten.
Live-OverlayDesktop settlementEpoch, revision und typisierte Live-Blöcke.Bei gültigen Snapshots des aktuellen Ziels oder Live-Aktualisierungen.
Outbox und übermitteltes EchoDesktop-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 MessungenDie 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 erste Abbildung zeigt, wie Clients Zeilen über diesen Schlüssel zusammenführen. Nach jedem wesentlichen Merge wird die eine kanonische Reihenfolge neu hergestellt: nach Zeitstempel, dann nach dem vom Desktop gelieferten sourceOrder-Vektor, dann nach dem Identitätsschlüssel. Eine Zeile ohne sourceOrder sortiert sich bei gleichem Zeitstempel hinter Zeilen mit einem. Zeitstempel sind Metadaten für die Reihenfolge und nie Identität für die Deduplizierung.

Der Desktop liest eigenen paginierten 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 dem rohen Sidecar-Cursor, einer Generation, diesem Quellentoken, dem vorherigen führenden Blockschlüssel und der Fensterrevision. Der Desktop parst kein Codex-JSONL und fragt die privaten Datenbanken des Kinds nicht ab.

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. Das persistierte Overlay-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
Das Overlay gibt eine Live-Zeile nur in einem Snapshot ab, der ihre Verlaufszeile mitbringtZeile 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.
Desktop-AppChatansicht
Desktop-OverlayLive-Zeilen
Telefondieselbe Session
A · Live-Zeile
A · Verlaufszeile
hält A · Epoche 3
A · Live-Zeile
A · Verlaufszeile
falls angewendet: A fehlt
neuestes Fenster
Verlauf mit A + Overlay ohne A
Epoche 3 → 4
neues Update · Epoche 4
neuere Epoche: nicht angewendet, Zeilen bleiben
neuestes Fenster
Verlauf mit A + Overlay ohne A
  • Settlement-EpocheliveOverlay.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.

  • RevisionliveOverlay.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 Fensterrun.codexChatLoad · run.codexChatReconcileSessions

    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.

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, und ein leerer Ziel-Snapshot wird gespeichert, damit das Ziel seine Epoche und Revision behält.

Die Settlement-Epoche ist ein Sicherheitszaun pro Ziel. Der Desktop erhöht sie, wann immer mindestens eine gewöhnliche Live-Zeile das Overlay in Richtung kanonischen Verlauf verlassen hat: beim Aufbau eines neuesten Fensters, nach dem Abschlussereignis eines Runs und bei der Reparatur nach Neustart. Nach einem Abschlussereignis vermerkt er nur Session und Run, dann durchsucht er thread/items/list bis zum Ende nach jeder Overlay-Identität dieses Runs und wiederholt die Sichtbarkeitsprüfung bis zu dreimal mit 100 und 250 Millisekunden Verzögerung. Mobil wendet ein Fenster und sein Overlay in einer Reducer-Operation an. 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.

Paginierung ist ein Spleiß-Vertrag

Eine ältere Seite setzt nur an der Zeile an, an der ihr Cursor geschnitten wurde
Eine ältere Seite setzt nur an der Zeile an, an der ihr Cursor geschnitten wurdeDer 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.
run.codexChatLoadPageBefore pageSize · beforeCursor · expectedBoundaryToken
Ihr Fenster
älterer Verlauf, nicht geladen
item-120älteste Zeile
neuere Zeilen
oldestCursor: Naht item-120, Revision 1
Seite vom Desktop
3 ältere Zeilenitem-117 · item-118 · item-119
item-120Nahtschlüssel, keine neue Zeile
windowRevision 2 · orderedBlockKeys: ältere Zeilen, dann die Naht
Fenster nach dem Einfügen
item-117
item-118 · item-119
item-120Seite oben angesetzt
neuere Zeilen
nächster oldestCursor: Naht item-117, Revision 2
Naht
Naht fehlt (auf Telefonen: nicht die älteste Zeile) oder Revision nicht neuer: Die Seite wird verworfen. Telefone laden das neueste Fenster, und der Desktop blättert von diesem Cursor nicht weiter.
Quellentoken weicht von expectedBoundaryToken ab: Der Desktop sendet eine leere Seite mit reloadRequired, und jeder Client lädt das neueste Fenster.
  • NahtboundaryBlockKeys

    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.

  • FensterrevisionwindowRevision

    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.

  • Cursorthread-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.codexChatLoadPageBefore und run.codexChatLoadThreadPageBefore mit pageSize, beforeCursor und expectedBoundaryToken. Ein Cursor, dessen Quellentoken von der aktuellen Quelle abweicht, scheitert mit Timeline source changed. Eine Anfrage holt bis zu vier Sidecar-Seiten, bis eine einen sichtbaren Block liefert, und eine Antwort über 8 MiB scheitert mit errorCode TIMELINE_SNAPSHOT_TOO_LARGE. Telefone warten 115 Sekunden auf run.codexChatLoad und run.codexChatLoadThread. 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-RichtlinieWert
Initiale Timeline-Seite12 Einträge.
Ältere Seite / maximale Seite100 / 100 Einträge.
Initialer Timeline-Timeout115 Sekunden.
Timeout für paginierte Timeline60 Sekunden.
Timeout für die Bestätigung von Outbox-Befehlen20 Sekunden.
Initialer Timeline-WiederholungsversuchHöchstens 3 Wiederholungen nach der ersten Anfrage, nur für dasselbe aktuelle Timeline-Ziel. Basisverzögerung 0,75 Sekunden, maximal 3 Sekunden.

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 von Codex behaltene Ausgabe. Das Rendern oder Abgleichen einer Timeline liest nie Ausgabebytes; ein offener Betrachter holt sie separat.

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.
  • 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.