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.
- 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ät | Was sie identifiziert |
|---|---|
| session | Die HB-Code-Konversation, die die Outbox besitzt. |
| queue | Der sichtbare Eintrag in dieser Outbox. |
| operation | Die unveränderliche Nachrichtenoperation, die über Zustellung und Wiederherstellung hinweg beibehalten wird. |
| idempotency | Die Remote-Mutationsanforderung. Ihre Replay-Regeln gehören zur RPC-Schicht. |
| run | Der Run und der genaue Agenten-Turn, der die Operation angenommen hat. Das Operationsregister speichert sie mit accepted |
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.
| Moment | Datensatz und was er belegt |
|---|---|
| Absenden | workspace_ |
| Kurz bevor turn/ | chat_ |
| turn/ | Das Registerergebnis erhält run |
| Das Echo kommt live an oder wird im Verlauf gefunden | Das Echo ist eine Nutzernachricht, deren client |
Eine Claude-Session schreibt dieselben Datensätze. Der Run und der Nachweis unterscheiden sich.
| Schritt | Codex-Session | Claude-Session |
|---|---|---|
| Run | turn/ | Ein neuer Agent-Host-Prozess für jeden Run. |
| Nachweis der Zustellung | user | Das Transkript enthält eine Nutzernachricht mit der UUID, die claude_ |
| Steuern | turn/ | Die Host-Anfrage send schreibt die Nachricht in die offene Query. Claude liest sie nach seinem aktuellen Schritt. |
| Letzte ersetzen, Geschwindigkeitsmodus | Verfügbar. | Nicht verfügbar. Der Submit scheitert mit einem Validierungsfehler, und es existiert keine Quittung. |
| Aktiver Turn, der weitere Eingaben annimmt | Pro 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.
| Übermittlungssteuerung | Bedeutung |
|---|---|
| intent: automatic | Die Nachricht vorn in der Warteschlange speichern, hinter Einträgen im Status sending, und die Drain-Spur ihres Sendemodus starten. |
| intent: enqueue | Die 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 queue |
| intent: replace-latest | Nur Codex. Die Nachricht vorn speichern, mit gesetztem rollback |
| send | Auf das Ende des aktiven Runs warten. |
| send | Die Steuerungsspur verwenden und die Nachricht dem Run hinzufügen, den expected |
| send | Den 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.
| Registerstatus | Bedeutung und Ersetzungsregel |
|---|---|
| sending | Ein Sendeversuch hat begonnen. failed kann ihn nicht ersetzen. sent und canceled können es. |
| sent | Der Verlauf des Agenten enthält die Nachricht. Kein anderer Status ersetzt ihn. |
| canceled | Der Run wurde gestoppt, bevor die Nachricht bestätigt war. sending kann ihn nicht ersetzen. Nur die Wiederöffnungsprüfung unten startet ihn erneut. |
| failed | Der 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
- 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.
- 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.
- Ü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.
- 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.
- 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.
| Grenze | Ergebnis bei Überschreitung |
|---|---|
| Sendungen eines Tool-Aufrufs: 1 | Eine Wiederholung desselben Aufrufs gibt already-sent zurück. Eine Wiederholung mit einem anderen Empfänger scheitert. |
| Empfänger: jede andere Session | Eine 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.
| Regel | Mechanismus |
|---|---|
| Eine Sperre pro Basis-Session | Jeder 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 Stopps | Ein 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 weiter | Nur Codex. Endet ein Turn, während ein Nachfolge-Turn denselben Run hält, lässt der Abschluss den Run in der Registry. |
| Ein Finalisierer | Das 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 nie | Ein 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ückholen | Ein 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.