Zum Artikel springen
HB CodeDocsApp herunterladen

HandbuchArchitektur

Nachrichtenzustellung: Outbox, Turn, Verlauf

Wie eine Nachricht in die dauerhafte Outbox gelangt, einen Agenten-Turn erreicht, aus dem gespeicherten Verlauf des Agenten bestätigt wird und auf Sie wartet, statt erneut gesendet zu werden, wenn die Antwort verloren geht.

Gegen den Quellcode geprüft am 5. Oktober 2026

Auf dieser Seite

Die Schritte einer Nachricht vom Senden bis zum Verlauf

Codex-Sessions und Claude-Sessions teilen sich die Outbox, das Operationsregister, die Run-Registry und die Regeln, die einen Eintrag entfernen. Der gemeinsame Code liegt in chat/. Das Modul agents/dispatch wählt für vier Schritte den Adapter der Session: für den Versand, für das Lesen des Verlaufs nach einem ungewissen Versand, für den Stop und für das Zurückholen einer angenommenen Steuerung. Dieses Kapitel beschreibt den gemeinsamen Teil und nennt jeden Schritt, der sich unterscheidet.

Eine Nachricht wird in drei getrennten Momenten gespeichert, angenommen und bestätigt
Eine Nachricht wird in drei getrennten Momenten gespeichert, angenommen und bestätigtDie Zeichnung folgt einer Beispielnachricht in einer Codex-Session aus dem Composer des Desktops von links nach rechts. Der Desktop speichert den Outbox-Eintrag in einer Transaktion und gibt die Quittung zurück, und der Composer wird geleert, bevor ein Turn existieren muss. Wenn turn/start antwortet, hält das Operationsregister fest, welcher Turn die Nachricht angenommen hat. Erst wenn Codex die Nutzernachricht mit ihrer operationId zurückmeldet, steht im Register sent, und der Eintrag verlässt die Warteschlange. Eine Claude-Session hat dieselben drei Momente, und nur der Nachweis im dritten unterscheidet sich.
Composeram Desktop
Outbox-Eintrag
Operations­register
Codex-Kindprozess
queued
sending
sending
Turn läuft
gespeichert
angenommen
bestätigt
senden
Quittung, Composer wird geleert
turn/start
Antwort mit Turn-ID
sent
entfernt
item/completed
zeichnet die Nutzernachricht auf
  • GespeichertQuittung · queueId · operationId

    Eine Transaktion speichert den Anfrage-Fingerabdruck und den Outbox-Eintrag, ein Drain-Task startet, und die Quittung geht zurück. Der Composer des Desktops wird mit der Quittung geleert. Ein Turn muss noch nicht existieren.

  • AngenommenrunId · threadId · turnId · acceptedAt

    Wird ins Operationsregister geschrieben, wenn die Codex-Anfrage turn/start antwortet. Das belegt, dass ein Turn die Nachricht übernommen hat. Erst das Echo belegt, dass Codex sie aufgezeichnet hat.

  • BestätigtuserMessage.clientId == operationId

    Codex zeichnet die Nutzernachricht mit der operationId auf und sendet item/completed. Das Register markiert die Operation als sent, danach verlässt der Eintrag die Warteschlange.

  • Keine eindeutige AntwortuserMessage.clientId im Verlauf

    Bekommt turn/start keine eindeutige Antwort, sucht der Desktop die operationId im Verlauf. Gefunden heißt gesendet und entfernt. Fehlt sie oder ist der Verlauf nicht lesbar, bleibt der Eintrag mit abgeschaltetem Auto-Drain stehen und bittet darum, zuerst die Timeline zu prüfen.

Eine Nachricht hat mehrere Identitäten

IdentitätWas sie identifiziert
sessionIdDie HB-Code-Konversation, die die Outbox besitzt.
queueIdDer sichtbare Eintrag in dieser Outbox.
operationIdDie unveränderliche Nachrichtenoperation, die über Zustellung und Wiederherstellung hinweg beibehalten wird.
idempotencyKeyDie Remote-Mutationsanforderung. Ihre Replay-Regeln gehören zur RPC-Schicht.
runId / threadId / turnIdDer Run und der genaue Agenten-Turn, der die Operation angenommen hat. Das Operationsregister speichert sie mit acceptedAt, und die Annahme einer Steuerung steht zusätzlich am Outbox-Eintrag.

Der Submit-Dienst berechnet einen unveränderlichen Anfrage-Fingerabdruck und prüft die aufgezeichnete Operationsidentität vor der Zulassung. Der Fingerabdruck ist ein SHA-256 über sessionId, queueId, userText, promptText, attachments, threadId, speedMode, accessMode, das erwartete Modell mit Aufwand, rollbackBeforeSend, source und createdAt. Er lässt intent, sendMode und expectedActiveRunId absichtlich aus, sodass eine Nachricht einreihen und dann sofort senden auf dieselbe Operation konvergiert, während das Umlenken einer Steuerung auf einen anderen Run ein Konflikt ist. Die Datenbanktransaktion beansprucht das Paar (session_id, operation_id) und speichert das Outbox-JSON zusammen damit, und ein anderer Fingerabdruck unter derselben Operations-ID kann den ursprünglichen Anspruch nicht überschreiben. Eine Operations-ID, deren Eintrag entfernt wurde, kann nicht erneut angelegt werden. Der Submit scheitert mit einem Konflikt und verlangt eine neue operationId.

Ein Outbox-Geltungsbereich ist die Basis-Session plus jede Session-ID, die das Alias-Suffix ::skills-agent an sie anhängt. Die Start-und-Abbruch-Sperre, die Prüfung auf einen aktiven Run und der Sent-Nachweis arbeiten alle auf dem Geltungsbereich. Ein Sent-Nachweis unter einem Alias entfernt die Operation aus der Outbox jedes Alias. Zeigt der Geltungsbereich mehr als einen aktiven Run oder steht ein Abbruch aus, behandelt der Desktop ihn als mehrdeutig und sendet nichts aus der Warteschlange.

Verwahrung, Annahme und Historie sind getrennt

Jeder Moment schreibt seinen eigenen Datensatz. Die Quittung belegt nicht, dass ein Turn gestartet wurde, und ein angenommener Turn belegt nicht, dass der Agent die Nachricht aufgezeichnet hat. Die Tabelle zeigt die Momente einer Codex-Session.

MomentDatensatz und was er belegt
Absendenworkspace_chat_outbox und workspace_chat_outbox_operation_identity, in einer Transaktion geschrieben. Der Desktop hat die Nachricht in Verwahrung und gibt die Quittung zurück.
Kurz bevor turn/start den Actor erreichtchat_operation_ledger mit Status sending. Ein Sendeversuch hat begonnen, also kann ein Turn existieren.
turn/start antwortetDas Registerergebnis erhält runId, threadId, turnId und acceptedAt. Ein Turn hat die Nachricht angenommen.
Das Echo kommt live an oder wird im Verlauf gefundenDas Echo ist eine Nutzernachricht, deren clientId der operationId entspricht. Der Registerstatus wird sent, danach wird der Outbox-Eintrag entfernt. Codex hat die Nachricht aufgezeichnet, und die Zustellung ist bestätigt.

Eine Claude-Session schreibt dieselben Datensätze. Der Run und der Nachweis unterscheiden sich.

SchrittCodex-SessionClaude-Session
Runturn/start auf dem Codex-App-Server-Actor.Ein neuer Agent-Host-Prozess für jeden Run.
Nachweis der ZustellunguserMessage.clientId entspricht der operationId, live oder im Verlauf.Das Transkript enthält eine Nutzernachricht mit der UUID, die claude_operations gespeichert hat. Der Desktop liest sie während des Runs und am Ende.
Steuernturn/steer fügt die Nachricht dem aktiven Turn hinzu.Die Host-Anfrage send schreibt die Nachricht in die offene Query. Claude liest sie nach seinem aktuellen Schritt.
Letzte ersetzen, Geschwindig­keits­modusVerfügbar.Nicht verfügbar. Der Submit scheitert mit einem Validierungsfehler, und es existiert keine Quittung.
Aktiver Turn, der weitere Eingaben annimmtPro Session registriert. Eine Steuerung und Stop and send now brauchen ihn.Der aktive Run der Session, ab dem Moment, in dem der Host die Nachricht hat, die ihn gestartet hat. Die Turn-ID ist die Claude-ID dieser Nachricht.

Der gespeicherte Eintragsstatus hat zwei Werte: queued und sending. Flags tragen den Rest. autoDrain sagt, dass der Drain den Eintrag selbst senden darf. error und holdReason sagen, warum ein Eintrag wartet. acceptance hält Run, Thread und Turn eines angenommenen Steers. handoff hält die Stufe von Stop and send now. sessionMessage hält die Herkunft einer Nachricht aus einer anderen Session: den Absender, seinen Aufruf, Run und Thread, seinen Zugriff und sein Projekt und den Austausch. Der Desktop behält die Herkunft in seinem Speicher. Ein Client bekommt die Angabe message der User-Zeile, zu der der Eintrag wird, ohne Link und ohne delivery. Sent, canceled und blocked sind Ergebnisse eines Befehls. Sie sind keine Werte von WorkspaceChatOutboxStatus. Ein gespeicherter Eintrag, der sich nicht dekodieren lässt, erscheint als blockierter Eintrag und wird nicht verworfen.

Übermittlungs­steuerungBedeutung
intent: automaticDie Nachricht vorn in der Warteschlange speichern, hinter Einträgen im Status sending, und die Drain-Spur ihres Sendemodus starten.
intent: enqueueDie Nachricht hinten in der Warteschlange speichern. Sie geht raus, wenn die Session frei ist und die Einträge vor ihr weg sind. Ein enqueue für eine queueId, die schon wartet, behält ihren Platz.
intent: replace-latestNur Codex. Die Nachricht vorn speichern, mit gesetztem rollbackBeforeSend.
sendMode: queueAuf das Ende des aktiven Runs warten.
sendMode: steer-runningDie Steuerungsspur verwenden und die Nachricht dem Run hinzufügen, den expectedActiveRunId nennt.
sendMode: interrupt-runningDen aktiven Run stoppen, warten, bis er zur Ruhe gekommen ist, und diese Nachricht dann als eigenen Run starten.

Die Validierung verknüpft die Steuerungen: enqueue verlangt sendMode queue, replace-latest verlangt interrupt-running, steer-running verlangt expectedActiveRunId, und jeder andere Modus weist dieses Feld zurück. Die Outbox hat keine Obergrenze für Einträge. Das Telefon wartet 20 Sekunden auf die Quittung.

Das Operationsregister ist die Tabelle chat_operation_ledger mit dem Primärschlüssel (app_session_id, operation_id). Sein Status bewegt sich nur in Richtung eines stärkeren Nachweises.

Register­statusBedeutung und Ersetzungsregel
sendingEin Sendeversuch hat begonnen. failed kann ihn nicht ersetzen. sent und canceled können es.
sentDer Verlauf des Agenten enthält die Nachricht. Kein anderer Status ersetzt ihn.
canceledDer Run wurde gestoppt, bevor die Nachricht bestätigt war. sending kann ihn nicht ersetzen. Nur die Wiederöffnungsprüfung unten startet ihn erneut.
failedDer Versuch scheiterte, bevor der Agent die Nachricht übernahm. Jeder Status kann ihn ersetzen.

In einer Codex-Session hält der Registereintrag außerdem admission_input_digest, einen SHA-256 über den Text mit normalisierten Zeilenenden und die geordneten Bildpfade. Ein Sent-Nachweis unter einem beliebigen Alias derselben Basis-Session zählt. Lässt sich SQLite nicht schreiben, hält ein Speicher-Fallback mit 256 Einträgen den Datensatz. Ein voller Fallback verwirft seinen ältesten Datensatz, der nicht sent ist, und er lehnt den neuen Datensatz ab, wenn alle 256 Sent-Nachweise sind.

Sie können eine abgebrochene Operation erneut senden. Hatte der abgebrochene Versuch eine Annahme, liest der Desktop zuerst den Verlauf des Agenten. Eine gefundene Nachricht schließt die Operation als sent ab. Nur ein lesbarer Verlauf ohne die Nachricht öffnet die Registerzeile wieder.

Was die Composer-Aktionen tun

Eine eingereihte Nachricht wartet auf den Run. Ein Steer kommt zum laufenden Run hinzu. Eine Unterbrechung stoppt den Run.
Eine eingereihte Nachricht wartet auf den Run. Ein Steer kommt zum laufenden Run hinzu. Eine Unterbrechung stoppt den Run.Drei Zeilen zeigen denselben laufenden Turn R und eine Nachricht, die im selben Moment gesendet wird. Eine eingereihte Nachricht wartet, bis R endet, und startet dann ihren eigenen Run. Eine Steuerung fügt sich in R ein. Eine Unterbrechung prüft das Modell, stoppt R, wartet, bis R zur Ruhe kommt, und startet die Nachricht dann als eigenen Run, früher als die eingereihte.
Senden
Einreihen
Run R
wartet in der Warteschlange
eigener Run
Steuern
Run R
fügt sich in Run R ein
R schon beendet: eigener Run. Anderer Run aktiv: wartet auf dessen Ende.
Unterbrechen
Run R
gestoppt
stoppt Run R
wartet ≤ 10 s
eigener Run
Ohne aktiven Run startet jede Aktion einen neuen Run, sobald sie vorn in der Warteschlange steht. Ein Versand aus dem Composer geht nach vorn, hinter Einträge im Status sending. Für später einreihen geht ans Ende.
  • Einreihenintent: enqueue

    Für später einreihen stellt die Nachricht ans Ende der Warteschlange. Sie wird nie in einen laufenden Turn gemischt. Das Bearbeiten einer wartenden Nachricht behält ihre queueId und ihren Platz und vergibt eine neue operationId.

  • Steuernsteer-running · expectedActiveRunId

    An den beobachteten Run gebunden. Die Nachricht kommt in diesen Run, also gelten dessen Modell, Aufwand, Geschwindigkeit und Zugriff. Codex nimmt sie mit turn/steer an, und Claude liest sie nach seinem aktuellen Schritt. Endet der Run, bevor er sie verwendet, kommt die Nachricht mit abgeschaltetem Auto-Drain zurück.

  • Unterbrecheninterrupt-running

    Das Modell wird geprüft, bevor der Run gestoppt wird. Die Nachricht startet, sobald der alte Run die Run- und die Turn-Registry verlassen hat, geprüft alle 25 ms, höchstens 10 s lang. Erscheint während des Wartens ein neuerer Turn, bleibt sie in der Warteschlange.

  • Letzte ersetzenreplace-latest · rollbackBeforeSend

    Nur Codex. Stoppt einen laufenden Turn wie eine Unterbrechung und setzt dann den letzten Turn zurück, bevor diese Nachricht gesendet wird. Kennt Codex den Thread nicht mehr, löscht der Desktop die Thread-Verknüpfung und sendet die Nachricht an einen frischen Thread.

Einreihen, Steuern und Unterbrechen funktionieren bei beiden Agenten. Letzte ersetzen gibt es nur in einer Codex-Session. Eine Steuerungsantwort, die beweist, dass die Anweisung nicht zugelassen wurde, gibt die Operation in die automatische Warteschlangenverwahrung zurück, und Transportunsicherheit durchläuft dieselbe Abstimmung wie jeder andere Versand. Ist der alte Run nach 10 Sekunden noch nicht zur Ruhe gekommen, bleibt auch der Ersatz einer Unterbrechung in der Warteschlange.

Letzte ersetzen prüft zuerst das Modell und bittet dann Codex, den letzten Turn zurückzusetzen. Meldet Codex, dass der Thread fehlt, löscht der Desktop die Thread-Verknüpfung der Session unter der Sperre der Verlaufsquelle und sendet die Nachricht an einen frischen Thread.

Das Bearbeiten einer wartenden Nachricht schreibt den Text neu und behält ihren Platz in der Warteschlange, sofern Sie sie nicht sofort senden oder umsortieren. Das Entfernen eines wartenden Eintrags löscht ihn und lässt den aktiven Run unberührt. Ein Eintrag im Status sending lässt sich nicht entfernen. Der Befehl gibt blocked zurück, und der Eintrag bleibt, bis das Register oder der Verlauf ihn abschließt.

Die Admission-Grenze steuert die sichere Wiederherstellung

Jeder Sendeversuch endet in einem von sechs Ergebnissen. Den Ergebnistyp ChatSendError teilen sich beide Agenten, und jeder Adapter entscheidet, zu welcher Klasse ein Fehler gehört. Die Klasse sagt der Outbox, ob der Agent die Nachricht übernommen haben kann, denn nur dann kann ein zweiter Versand sie verdoppeln.

Von sechs Sendeergebnissen kehrt nur ein aufgeschobener Versand mit eingeschaltetem Auto-Drain in die Warteschlange zurück
Von sechs Sendeergebnissen kehrt nur ein aufgeschobener Versand mit eingeschaltetem Auto-Drain in die Warteschlange zurückLinks ein Outbox-Eintrag im Status sending. Sechs Sendeergebnisse fächern sich von ihm auf, und rechts steht zu jedem der Eintragszustand, der folgt. Zugestellt entfernt den Eintrag. Eine angenommene Steuerung bleibt sending mit ihrer Annahme. Aufgeschoben kehrt mit autoDrain an und einem holdReason in die Warteschlange zurück. Abgebrochen und nachweislich nicht zugelassen kehren mit autoDrain aus in die Warteschlange zurück. Bei einem ungewissen Versand liest der Desktop den Verlauf des Agenten. Eine gefundene Nachricht entfernt den Eintrag, und eine fehlende oder unlesbare hält den Eintrag mit einem Fehler zurück.
Outbox-Eintragstatus: sending
ZugestelltDer Agent hat die Nachricht aufgezeichnet.
Aus der Outbox entferntRegister: sent
Angenommene SteuerungDer laufende Turn hat sie übernommen.
Bleibt sendingAnnahme am Eintrag gespeichert
AufgeschobenDer Desktop hat sie absichtlich zurückgehalten.
Wartet, sendet von selbst erneutautoDrain an · holdReason
AbgebrochenDer Run wurde vorher gestoppt.
In der Warteschlange, wartet auf SieautoDrain aus · kein Fehler
Nachweislich nicht zugelassenDer Agent hat sie nicht übernommen.
In der Warteschlange, wartet auf Sie, zeigt den GrundautoDrain aus · error
UngewissDer Agent kann sie übernommen haben. Der Desktop liest den Verlauf des Agenten.
Im Verlauf gefunden: entferntRegister: sent
Fehlt oder unlesbar: wartet auf SieautoDrain aus · error „could not confirm“
  • ZugestelltChatSendStatus: sent

    Der Verlauf des Agenten enthält die Nachricht. Im Register steht sent, und danach verlässt der Eintrag die Outbox jedes Alias der Session.

  • Angenommene Steuerungsending · acceptedAt · runId · threadId · turnId

    Der laufende Turn hat die Nachricht als wartende Eingabe übernommen. Der Eintrag bleibt sending und behält die Annahme, bis der Zustellnachweis oder das Ende des Runs ihn abschließt.

  • AufgeschobenChatSendError::Deferred

    Der Desktop hat die Nachricht absichtlich zurückgehalten, zum Beispiel solange ein unterbrochener Run noch nicht zur Ruhe gekommen ist. Der nächste Drain versucht es erneut. Ein dedizierter Neuversand ist die Ausnahme und erhält autoDrain aus.

  • AbgebrochenChatSendStatus: canceled

    Der Run wurde gestoppt, bevor die Nachricht bestätigt war. Der Eintrag kehrt mit autoDrain aus und ohne Fehler zurück.

  • Nachweislich nicht zugelassenChatSendError::ProvenUnadmitted

    Der Agent hat die Nachricht nicht übernommen. Der Eintrag kehrt mit autoDrain aus und dem Fehlertext zurück.

  • UngewissChatSendError::AdmissionUncertain

    Der Agent kann die Nachricht übernommen haben. Derselbe Pfad behandelt ein Ergebnis, das sending ohne Annahme meldet. Der Desktop liest den gespeicherten Verlauf des Agenten, bevor er den Eintrag ändert.

Die nächste Abbildung zeigt, wo die Grenze in einem Codex-Versand liegt. Vor der Übergabe an den Actor ist ein Fehler nachweislich nicht zugelassen. Danach ist jeder Fehler ungewiss.

Nach der Übergabe an den Actor kann ein Sendeversuch mit Timeout einen Turn gestartet haben
Nach der Übergabe an den Actor kann ein Sendeversuch mit Timeout einen Turn gestartet habenDie Zeichnung zeigt eine Codex-Session. Der linke Teil, nicht maßstäblich, zeigt die Schritte vor der Übergabe: Der Sendepfad reserviert einen Platz in der Befehlswarteschlange des Actors, schreibt sending ins Operationsregister und prüft, ob der Actor offen ist. Ein Fehler dort belegt, dass nichts Codex erreicht hat. Der rechte Teil ist maßstäblich in Sekunden nach der Übergabe gezeichnet. Der Actor schreibt turn/start und wartet 30 Sekunden auf die Antwort. Codex kann den Turn trotzdem starten, und eine spätere Antwort wird ignoriert, deshalb prüft der Desktop den Verlauf, statt erneut zu senden.
Operations­register
Sendepfad
App-Server-Actor
Codex-Kindprozess
Übergabe an den Actor
Platz reserviert
sending geschrieben
Actor offen
Fehler hier: PreAdmission. Nichts hat Codex erreicht.
nicht maßstäblich
wartet auf die Antwort
Turn startet vielleicht trotzdem
turn/start, Schreiben ≤ 5 s
angenommen, nur bei rechtzeitiger Antwort
keine Antwort nach 30 s: ungewiss
Verlauf prüfen
späte Antwort ignoriert
0 s
10 s
20 s
30 s
40 s
  • Übergabecommand_permit.send

    Der reservierte Befehl geht erst an den Actor, wenn im Register sending steht und das Gate den Actor offen findet. Der Sendepfad selbst hört 31 s nach der Übergabe auf zu warten.

  • Vorher gescheitertPreAdmission

    Eine abgelaufene Reservierung, ein gescheitertes Schreiben von sending, ein Actor, der sich gerade abschaltet, oder ein geschlossener Actor. Der Eintrag kehrt mit abgeschaltetem Auto-Drain und dem Fehler in die Warteschlange zurück.

  • Danach ungewissAdmissionUncertain

    Eine Fehlerantwort, ein gescheitertes Schreiben, ein geschlossener Kanal, eine abgelaufene Frist oder ein gescheitertes Speichern der Annahme. Der Turn existiert vielleicht, deshalb sucht der Desktop die operationId im Verlauf. Gefunden heißt gesendet. Fehlt sie oder ist der Verlauf nicht lesbar, wird der Eintrag gehalten.

  • Späte Ergebnisseexpected_sending_entry

    Die Wiederherstellung ändert einen Eintrag nur, solange er noch dem Stand während des Sendens entspricht, sodass ein spätes Ergebnis nie eine neuere Entscheidung in der Warteschlange überschreibt.

Jede Session hat zwei Drain-Bahnen, normal und steer, jede mit eigener Sperre. Ein Aufwecken, das eintrifft, während eine Bahn beschäftigt ist, setzt nur ein Folgekennzeichen, das der abschließende Durchlauf zurückgibt, sodass nichts verloren geht, was an der Abschlussgrenze landet. Die Steer-Bahn lässt nur den Kopf der Warteschlange zu, nur für eine exakte Steuerung des aktiven Runs, und startet nie einen neuen Turn. Eine Steuerung, deren Run schon beendet ist, wartet am Kopf, solange ein anderer Run arbeitet, und startet danach als neuer Run.

Die Zustellbereinigung verwendet das Paar aus Warteschlange und Operation. In einer Codex-Session hält die Registry Runtime-Clients für aktive Arbeit geliehen, einschließlich der Aktivität untergeordneter Threads, und leitet die Auflösung von Freigaben an den Actor, dem die vollständige Anfrageidentität gehört.

Ungewisse Zustellung

Nach einem ungewissen Sendevorgang liest der Desktop den gespeicherten Verlauf des Agenten der Session. In einer Codex-Session liest er die eingefrorene Verlaufsroute der Operation. Die Zustellung gilt nur dann als bestätigt, wenn eine userMessage.clientId gleich der Outbox-Operations-ID vorliegt, auf dem indexierten Thread des Speicherprofils oder auf dem exakten Pfad mit seinem Vorfahren-Manifest. Geschwister-Threads werden nie durchsucht. In einer Claude-Session sucht der Desktop die gespeicherte Nachrichten-UUID der Operation im Claude-Transkript. Eine bestätigte Operation wird ins Sent-Register geschrieben, bevor die Outbox-Verwahrung entfernt wird. Fehlende Belege, ein Lesefehler oder eine abweichende Route lassen den Eintrag mit abgeschaltetem Auto-Drain sichtbar, mit dem Text „The desktop could not confirm whether this message reached the agent. Check the timeline before sending it again.“ Es gibt keine automatische Wiederholung und keinen Backoff.

Ein Neustart verwendet dasselbe Lesen. Beim Start startet der Desktop beide Drain-Spuren für jede gespeicherte Outbox. Ein Client, der eine Outbox lädt, während kein Drain und kein Run aktiv ist, löst das Lesen des Verlaufs ebenfalls aus, für jeden Eintrag, der noch im Status sending steht. Lässt sich das Register in diesem Moment nicht lesen, hält der Desktop diese Einträge mit demselben Text zurück.

Stop and send now ist der einzige Pfad, der eine angenommene Nachricht erneut sendet. Er funktioniert bei beiden Agenten. Die Abbildung zeigt am Beispiel einer Codex-Session, wann der Desktop ihn erlaubt. In einer Claude-Session holt der Desktop die Nachricht mit der Host-Anfrage cancel zurück, und Claude antwortet, ob es die Nachricht schon begonnen hat. Ein Telefon ruft ihn als run.chatOutboxStopAndSendNow auf. Der Desktop führt die Übergabe in einer eigenen Task aus, sodass sie auch dann endet, wenn das Relay nicht mehr auf die Antwort wartet.

Eine angenommene Steuerung wird nur erneut gesendet, wenn der Agent bestätigt, dass sie nie angewendet wurde
Eine angenommene Steuerung wird nur erneut gesendet, wenn der Agent bestätigt, dass sie nie angewendet wurdeDie Zeichnung zeigt eine Codex-Session. Eine Claude-Session folgt denselben Schritten und holt die Steuerung mit der Host-Anfrage cancel zurück. Stop and send now kann eine Steuerung zurückholen, die der laufende Turn angenommen, aber noch nicht verwendet hat. Nach einer Modellprüfung markiert der Desktop den Eintrag als angefordert und bittet Codex, die Steuerung aus den wartenden Eingaben von Run R zu lösen. Erst wenn Codex das bestätigt, widerruft der Desktop die Annahme, stoppt R, wartet, bis R zur Ruhe gekommen ist, und reiht den Eintrag mit derselben operationId wieder ein, sodass er seinen eigenen Turn startet.
Agenten-Runs
Wartende Eingabenvon Run R
Outbox-Eintrag
Operations­register
Run R
gestoppt und zur Ruhe gekommen
eigener Run
dieselbe operationId
wartet auf Anwendung
nie angewendet
sending
angefordert
freigegeben
Annahme für Run R
Stop and send now
Lösen anfragen
detachedForReplay
widerrufen
R stoppen
eigener Turn, Auto-Drain an
Jede andere Antwort oder keine Antwort sendet nichts erneut: applied, recording oder ein geänderter Turn halten den Eintrag mit dem Grund und abgeschaltetem Auto-Drain.
Ist Run R nach 5 s nicht zur Ruhe gekommen, wartet der Eintrag als freigegeben. Ein späteres Lesen des Verlaufs, das die Nachricht nicht findet, reiht sie wieder ein.
  • Angeforderthandoff.phase: requested

    Zuerst wird das Modell geprüft. Dann hält der Eintrag Run R, seinen Thread und seinen Turn fest, mit abgeschaltetem Auto-Drain.

  • Lösenturn/detachPendingInputForReplay

    Bittet Codex, die Steuerung anhand ihrer operationId aus den wartenden Eingaben des Runs zu nehmen. Nur detachedForReplay oder alreadyDetachedForReplay lässt den Desktop weitermachen.

  • Widerrufenchat_operation_ledger

    Löscht nur die Annahme für genau diesen Run, Thread und Turn. Steht im Register schon sent, wird nichts erneut gesendet.

  • Eigener Turnhandoff.phase: dedicated

    Der Eintrag wird mit eingeschaltetem Auto-Drain und derselben operationId wieder eingereiht und startet seinen eigenen Turn, sobald Run R zur Ruhe gekommen ist. Der Desktop wartet 5 s auf das Ergebnis des Stopps.

Ein Telefon liest die Outbox mehrerer Sessions mit run.chatReconcileSessions. Ein Aufruf nimmt höchstens 16 Sessions an und gibt höchstens 8 MiB zurück. Er scheitert mit invalid params für eine Session, die mehr als 256 Outbox-Einträge hat.

Nachrichten zwischen Sessions

Ein Agent kann mit dem Tool send_session_message eine Nachricht an eine andere Session desselben Desktops senden. Beide Agenten haben das Tool, und der Empfänger kann jeden der beiden Agenten nutzen. Die Nachricht ist ein gewöhnlicher Outbox-Eintrag des Empfängers, also gilt alles in diesem Kapitel auch für sie: Verwahrung, Warteschlange, Register und Zustellnachweis. Eine Nachricht, die keine Freigabe braucht, geht als Steuerung in den laufenden Turn des Empfängers. Ohne laufenden Turn, oder wenn die Nachricht auf Ihre Freigabe wartet, geht sie in die Warteschlange.

Eine Session-Nachricht kommt in den laufenden Turn des Empfängers oder startet einen Turn, wenn der Empfänger frei ist
Eine Session-Nachricht kommt in den laufenden Turn des Empfängers oder startet einen Turn, wenn der Empfänger frei istZwei Sessions auf einer Zeitachse, nicht maßstäblich. Der Agent in Session A ruft send_session_message auf, während sein Run arbeitet. Der Desktop prüft die Halteregeln und speichert die Nachricht als Hop 1 in der Outbox von Session B. Das Tool-Ergebnis kommt sofort zurück, und A arbeitet weiter. Session B hat einen laufenden Turn, also ist der Eintrag eine Steuerung für diesen Run. Der Desktop prüft die Halteregeln erneut, und der Agent in B bekommt die Nachricht nach seinem aktuellen Schritt. Der Agent in B antwortet mit demselben Tool. Die Antwort ist Hop 2 desselben Austauschs. Session A hat in diesem Moment keinen Run, also geht die Antwort in die Warteschlange von A und startet dort einen neuen Run.
Session Asendet
Outbox von A
Outbox von B
Session Bempfängt
Run in A
neuer Run in A
Hop 1 · Steuerung
Hop 2
B arbeitet an eigener Nachricht
Ergebnis „queued“, A arbeitet weiter
send_session_message
Halteregeln geprüft
erneut geprüft
kommt nach dem aktuellen Schritt von B dazu
B antwortet mit demselben Tool
nicht maßstäblich
Eine Nachricht wartet stattdessen auf Sie, wenn der Empfänger eine höhere Zugriffsstufe hat als der Absender, oder wenn die Sessions in verschiedenen Projekten liegen und der Empfänger nicht schreibgeschützt ist.
  • Tool-Aufrufsend_session_message · recipientSessionId · message

    Das Modell wählt nur den Empfänger und den Text. Der Desktop nimmt die Absender-Session, den Run und die Aufruf-ID vom aufrufenden Agenten. Ein Tool-Aufruf sendet eine Nachricht.

  • Eintrag beim Empfängersession-message-<uuid> · steer-running oder queue

    Ein Eintrag in der Outbox des Empfängers. Mit einem laufenden Turn und ohne Halt hat er den intent automatic, den sendMode steer-running und die Run-ID des Empfängers. Sonst hat er den intent enqueue und den sendMode queue, am Ende der Warteschlange. userText hält den Inhalt, und promptText hält den Umschlag, den der Agent erhält. Der Routendatensatz in chat_session_messages wird in derselben Transaktion geschrieben.

  • Halteregelnhöherer Zugriff

    Geprüft, wenn der Tool-Aufruf den Eintrag speichert, und erneut vor jedem Drain. Ein gehaltener Eintrag hat autoDrain aus und einen holdReason. Er geht nur raus, wenn Sie ihn aus der Warteschlange senden.

  • Turn beim EmpfängeraccessMode des Empfängers

    Die Nachricht hat keine eigene Zugriffsstufe. Für eine Nachricht, die einen Turn startet, liest der Desktop die Stufe des Empfängers unmittelbar vor dem Start des Turns. Eine Nachricht, die in einen laufenden Turn kommt, läuft mit dem Zugriff dieses Turns.

  • Austauschchain_id · hop

    Ein Run, den eine Session-Nachricht gestartet hat oder dem sie sich angeschlossen hat, setzt diesen Austausch mit hop + 1 fort. Jeder andere Run beginnt einen neuen Austausch bei hop 1. Der hop ist nur ein Eintrag: Keine hop-Zahl hält eine Nachricht zurück.

Der Prompt-Text ist ein Umschlag. Seine erste Zeile beginnt mit dem einfachen Satz „Message from another HB Code session.“ und sagt dem Agenten dann, wie er antwortet: „Answer this message in one of three ways: do it, ask one question, or object with the reason.“ Die nächste Zeile öffnet das Tag session_message mit den Attributen from_session_id, from_title und from_agent. Dann folgen der Inhalt und das schließende Tag. Der einfache erste Satz existiert, weil Claudes Index gespeicherter Sessions eine erste Nachricht überspringt, die mit einem Tag beginnt. Der Agent speichert den Umschlag mit der Nachricht, und die Timeline liest den Absender aus diesem gespeicherten Text zurück. Eine Zeile mit einer eigenen Operations-ID von Ihnen wird nie als Session-Nachricht gelesen, daher nennt ein Umschlag, den Sie tippen, keinen Absender.

GrenzeErgebnis bei Überschreitung
Sendungen eines Tool-Aufrufs: 1Eine Wiederholung desselben Aufrufs gibt already-sent zurück. Eine Wiederholung mit einem anderen Empfänger scheitert.
Empfänger: jede andere SessionEine Nachricht an die eigene Session des Absenders scheitert.

Eine gespeicherte Nachricht wartet auf Sie, wenn der Empfänger eine höhere Zugriffsstufe hat als der Absender. Das Projektverzeichnis spielt keine Rolle: Eine Nachricht an eine Session in einem anderen Projekt geht wie jede andere Nachricht. Wenn der Empfänger einen Turn ausführt, vergleicht der Desktop den Zugriff dieses Turns. Die vier Zugriffsstufen bilden für beide Agenten eine geordnete Skala, von read-only bis unrestricted. Das Tool-Ergebnis sagt dann waiting-for-approval mit dem Grund, und der sendende Agent sagt es seinem Nutzer. Sonst sagt das Ergebnis queued, auch für eine Nachricht, die sich einem laufenden Turn anschließt. Der Desktop prüft die Regeln vor jedem Drain erneut, weil sich die Einstellungen des Empfängers ändern können, während die Nachricht wartet. Ein laufender Turn, dessen Zugriff der Desktop nicht bestätigen kann, oder eine Empfänger-Session, die der Desktop in diesem Moment nicht lesen kann, hält die Nachricht ebenfalls zurück. Für die Länge eines Austauschs gilt keine Grenze.

Ein Client kann keine Session-Nachricht anlegen. Ein Submit mit einer Operations-ID, die mit session-message- beginnt, scheitert, sofern dieser Eintrag nicht schon in der Warteschlange steht. Wer den wartenden Eintrag mit einem anderen intent als enqueue sendet, gibt ihn frei, und dann greift keine Halteregel mehr.

Die Tabelle chat_session_messages hält eine Zeile pro Nachricht: operation_id als Primärschlüssel, die Empfänger-Session, die Absender-Session, sender_call_id, sender_run_id, sender_thread_id, chain_id, hop, created_at und delivery. Das Paar (sender_session_id, sender_call_id) ist eindeutig, was einen wiederholten Tool-Aufruf sicher macht. Die Zeile wird in derselben Transaktion wie der Outbox-Eintrag geschrieben. chain_id und hop halten den Austausch fest, und sender_thread_id nennt den Agent-Thread, der die Nachricht gesendet hat. delivery ist sent, needs-approval oder declined, und die Zeile des Absenders zeigt den Wert.

Der Desktop gibt Claude eine Regel in dem Text, den Claude an seinen System-Prompt anhängt. Die Regel sagt Claude, für HB-Code-Sessions send_session_message zu verwenden, weil die eigenen Tools SendMessage und ListAgents von Claude Code sie nicht erreichen.

Die Run-Registry

Die Run-Registry ist gemeinsam. Sie hält im Speicher höchstens einen aktiven Run pro App-Session, unter einer Run-ID der Form run-<uuid>-<Millisekunden>. Ein zweiter Start für dieselbe Session scheitert mit „Chat send already in progress for this session. Please wait or stop current run.“ Die Registry hält die Operation fest, die den Run gestartet hat, und die letzte Session-Nachricht, die zu ihm hinzugekommen ist. So findet eine Session-Nachricht ihren Austausch.

RegelMechanismus
Eine Sperre pro Basis-SessionJeder Drain-Versuch, jede Unterbrechung und Stop and send now nehmen die Start-und-Abbruch-Sperre, einen Mutex. Ein Start und ein Stop greifen deshalb nie ineinander.
Kein Start während eines StoppsEin ausstehender Abbruch blockiert einen neuen Run mit „Run cancellation is in progress. Please wait.“ Die Outbox wartet.
Ein gesteuerter Run läuft nach dem Ende seines Turns weiterNur Codex. Endet ein Turn, während ein Nachfolge-Turn denselben Run hält, lässt der Abschluss den Run in der Registry.
Ein FinalisiererDas Flag finalizing wird pro Run einmal beansprucht. Sein Besitzer reduziert das Live-Overlay, und kein späterer Live-Schreiber fügt Zeilen hinzu.
Wiederherstellen ersetzt nieEin Turn, der startet, nachdem sein Vorgänger den Run entfernt hat, trägt dieselbe Run-ID wieder ein. Ist ein anderer Run aktiv, wird das Wiederherstellen abgelehnt.
Verwaiste Runs zurückholenEin Run, dessen jüngste Aktivität älter als 45 Sekunden ist, wird entfernt, sofern sein Agent kein Veto einlegt. Ein registrierter Codex-Turn legt ein Veto ein. Ein lebender Prozess des Claude-Agent-Hosts legt ein Veto ein.

Die Prüfung läuft bei jedem neuen Start und jedes Mal, wenn ein Client die aktiven Runs liest, zum Beispiel mit run.list. Die 45 Sekunden sind länger als der Timeout für Agentenanfragen, also wird ein langsamer Start nicht zurückgeholt.

Was Stop macht

Stop startet einen Abbruch-Worker pro Session und Run. Jeder Aufrufer, der denselben Stop verlangt, teilt dessen Ergebnis. Ein Aufrufer wartet 5 Sekunden und erhält dann die Antwort, dass der Abbruch noch nicht zur Ruhe gekommen ist. Der Worker läuft ohne den Aufrufer weiter und hält neue Runs blockiert, bis er ein Ergebnis hat. Ein Erfolg bleibt 60 Sekunden für wiederholte Anfragen erhalten. Ein Fehler wird sofort verworfen, sodass die nächste Anfrage den Run erneut prüft. Stürzt der Versuch selbst ab, versucht es der Worker nach 250 ms erneut. Nach dem Ergebnis läuft der Drain für jede Outbox im Geltungsbereich der Session.

Dispatch fragt zuerst den Claude-Adapter. Hat die Session einen Claude-Run mit dieser ID, sendet der Adapter einen Interrupt an den Agent-Host, wartet auf das Ende des Runs und beendet den Host. Sonst markiert der Desktop den Run als abgebrochen und bittet den Codex app-server, den aktuellen Turn abzubrechen.

Stop entfernt keine Nachricht. run.cancel lässt wartende Nachrichten, wie sie sind. Eine angenommene Steuerung, die der Agent vor dem Ende des Runs nicht übernommen hat, kehrt mit abgeschaltetem Auto-Drain und dem Text „The active run stopped before this message reached the agent. Review and send it again.“ in die Warteschlange zurück. In einer Claude-Session verwirft der interrupt jede Nachricht, die noch auf den Turn wartet, also kehrt jede von ihnen auf diesem Weg zurück. Bereits gelaufene Befehle und bereits bearbeitete Dateien bleiben ebenfalls, lesen Sie also Timeline und Git-Diff, sobald der Run zur Ruhe gekommen ist. Eine eingereihte Nachricht entfernen, einen Run stoppen und eine Datei zurücksetzen sind drei verschiedene Aktionen, und nur die letzte berührt Ihr Arbeitsverzeichnis.

Session-Titel

Die erste Nachricht benennt die Session. Der Desktop schreibt einen vorläufigen Titel aus den ersten 4 Wörtern der ersten nicht leeren Zeile, oder „Fresh chat“, wenn diese Zeile nichts hergibt. Er ersetzt nur einen Platzhaltertitel. Dann schreibt der eigene Agent der Session den endgültigen Titel mit 2 bis 4 Wörtern und höchstens 36 Zeichen. Codex nutzt das Modell gpt-5.6-luna mit mittlerem Reasoning-Aufwand und versucht es bis zu 3-mal. Claude nutzt sein kleines Modell, haiku. Pro Session läuft nur eine Generierung gleichzeitig. Ein Fehler behält den vorläufigen Titel und wird unter session_title_generation protokolliert. Eine Session, die eine Session-Nachricht startet, wird nach dem Inhalt dieser Nachricht benannt und nicht nach dem Umschlag.