Codex-Turns: Steuern, Freigaben, Ziele und Wiederherstellung
Was in einem Codex-Run passiert: wie ein Turn startet und gesteuert wird, wer eine Freigabe beantwortet, wie ein Ziel viele Turns zu einem Run verbindet, wie Stop funktioniert und was der Desktop tut, wenn ein Turn seinen Kindprozess verliert.
Gegen den Quellcode geprüft am 30. September 2026
Auf dieser Seite
Ein Thread, ein Run: Start, Besitzer und Steuern
Ein Codex-Run in HB Code ist eine Leser-Task, die einem Turn oder einer Kette von Turns auf einem Codex-Thread folgt. Alles in diesem Kapitel hängt an diesem Leser: Freigaben erreichen ihn über die Turn-Route, Stop spricht mit ihm über einen Steuerkanal, ein Ziel gibt ihm den nächsten Turn, und die Wiederherstellung gibt ihm eine neue Route. Der Prozess, der Actor und die Turn-Route stehen im Kapitel zum App-Server. Die Outbox, die entscheidet, wann eine Nachricht gesendet wird, steht im Kapitel zur Nachrichtenzustellung.
| Schritt | Was der Desktop tut und was ein Fehler bedeutet |
|---|---|
| Thread öffnen | Sendet thread/ |
| Thread beanspruchen | Schreibt den Besitzer-Eintrag für diese Codex-Thread-ID: App-Session, Run und die pid des Codex-Kindprozesses. Bei einem Fehler: Ein anderer Run besitzt den Thread. Das Senden endet mit „Codex thread … is already running for session …“. |
| Start-Sperre | Hält fest, dass dieser Run in dieser Session einen Turn startet. Kein Abschluss kann den Run schließen, bevor der Turn registriert ist. Bei einem Fehler: Ein anderer Turn-Start besitzt die Session. Nichts wurde gesendet. |
| turn/ | Schreibt den Registereintrag „sending“ und sendet dann turn/ |
| Turn registrieren | Speichert den aktiven Turn für die App-Session: Laufzeitpaar, Run, Thread, Turn und einen Steuerkanal mit 16 Plätzen. Danach weckt er die Steuerungs-Bahn der Outbox. Bei einem Fehler: Der Desktop gibt den Turn auf, den Codex schon gestartet hat. |
| Turn lesen | Der Leser nimmt Benachrichtigungen von der Turn-Route bis turn/ |
Der Besitzer-Eintrag ist eine Map im Speicher des Desktops mit der Codex-Thread-ID als Schlüssel. Er verhindert, dass zwei App-Sessions, die denselben Thread verknüpfen, ihn gleichzeitig ausführen. Derselbe Run kann ohne Fehler erneut beanspruchen. Ein veralteter Besitzer wird entfernt, wenn sein eingetragener Codex-Kindprozess tot ist. Hat der Eintrag keine pid, wird er nach 45 Sekunden entfernt, und nur, wenn der Run nicht mehr aktiv ist. Die Sperre wird nach thread/unload freigegeben und bevor die Abschlüsse die Outbox wecken, daher findet die nächste wartende Nachricht den Thread frei.
Steuern fügt dem Turn, der gerade läuft, eine Nachricht hinzu. Der Desktop sendet turn/steer auf dem Laufzeitpaar, dem der aktive Turn gehört. Er öffnet keinen Thread, startet keinen Turn und beansprucht keinen Besitzer, denn der Leser, der schon läuft, behält alle drei.
| Feld von turn/steer | Wert |
|---|---|
| thread | Der Thread des aktiven Turns. |
| expected | Die Turn-ID, die der Desktop registriert hat. Codex muss die Steuerung ablehnen, wenn ein anderer Turn aktiv ist. |
| client | Die Operations-ID des Outbox-Eintrags. Dieselbe ID benennt die Nachricht später im Verlauf. |
| input | Der Nachrichtentext und die Anhänge, aufgebaut wie der input von turn/ |
| Antwort | Was sie bedeutet und was mit der Nachricht geschieht |
|---|---|
| turn | Codex hat die Nachricht in den laufenden Turn übernommen. Die Operations-ID kommt in die Liste der angenommenen Steuerungen des Turns, und die Annahme wird gespeichert. |
| JSON-RPC-Fehler -32600 | Codex hat die Steuerung abgelehnt. Codex sendet diesen Code, wenn kein Turn aktiv ist, wenn der aktive Turn eine andere ID hat und wenn der Turn ein Review oder eine Komprimierung ist. Der Desktop liest nur diesen einen Code als „nicht zugelassen“. Die Nachricht bleibt in der Warteschlange. |
| turn | Codex hat sie für einen Turn angenommen, den der Desktop nicht erwartet hat. Ungewiss. Der Desktop sendet sie nicht erneut. Prüfen Sie die Timeline. |
| Jeder andere Fehler oder ein Timeout | Die Anfrage kann angekommen sein oder auch nicht. Ungewiss, mit derselben Behandlung. |
| Kein aktiver Turn im Desktop oder ein Turn eines anderen Runs | Der Desktop hat turn/ |
Stop and send now braucht einen weiteren Aufruf. Eine Steuerung, die Codex angenommen hat, kann im Turn noch als wartende Eingabe liegen. Der Desktop sendet turn/detachPendingInputForReplay mit derselben threadId, expectedTurnId und clientUserMessageId. Die Antworten detachedForReplay und alreadyDetachedForReplay erlauben die Wiederholung in einem neuen Turn. Die Antwort applied bedeutet, dass das Modell die Nachricht schon hat, also startet keine Wiederholung. Die Antworten recording und staleTarget sowie jeder Fehler blockieren die Wiederholung ebenfalls.
Wer eine Freigabe beantwortet
Codex bittet mit einer JSON-RPC-Anfrage um eine Freigabe und wartet auf die Antwort. Der Actor dieses Codex-Kindprozesses hält jede offene Anfrage in einer Map und muss jemanden finden, der sie anzeigen kann. Eine Anfrage, die niemand anzeigen kann, wird abgelehnt, denn ein Turn, der auf eine Antwort wartet, die nicht kommen kann, würde nie enden.
- Turn-Routeitem/*/requestApproval
Eine Anfrage für einen Turn, den ein Run liest, geht an diesen Leser. Die Karte hat keine Frist. Sie schließt mit einer Antwort, mit dem Ende des Turns oder mit serverRequest/resolved.
- Anspruch eines BeobachtersUNROUTED_APPROVAL_CLAIM_TIMEOUT · 30 s
Eine Anfrage ohne Route wird verteilt. Der Beobachter des Root-Runs oder eines beobachteten Threads muss sie innerhalb von 30 Sekunden beanspruchen. Nach einem Anspruch hat die Karte keine Frist.
- Vom Desktop abgelehntdecline
Sofort ohne Route und ohne Beobachter oder ohne unterstützte Entscheidung. Nach 30 Sekunden ohne Anspruch. Mit turn/completed, bei einem aufgegebenen Turn und wenn der Actor stoppt.
- Antwortrun.chatResolveApproval
Wird jedem laufenden Codex-Kindprozess angeboten, dem zuletzt verwendeten zuerst. Der Actor, der die Anfrage hält, prüft Anfrage-, Thread-, Turn- und Item-ID und schreibt die Antwort.
| Karte | Anfragemethode und angebotene Entscheidungen |
|---|---|
| Befehl | item/ |
| Dateiänderung | item/ |
| Berechtigungen | item/ |
- Die Zustellung hat zwei Wege. Eine Anfrage für einen Turn mit einer aktiven Route geht an den Leser dieses Turns, der die Karte in der Session zeigt. Eine Anfrage ohne Route wird an Beobachter verteilt. Anfragen aus Subagent-Threads nehmen diesen Weg, weil kein Leser ihre Turns besitzt.
- Ein Beobachter muss eine verteilte Anfrage innerhalb von 30 Sekunden beanspruchen. Der Beobachter des Runs weist zuerst nach, dass der Thread zu seinem Root-Thread gehört, mit einer Grenze von 10 Sekunden für diese Prüfung. Eine nicht beanspruchte Anfrage wird abgelehnt, wenn die 30 Sekunden enden.
- Eine Anfrage ohne unterstützte Entscheidung und eine Anfrage ohne Route und ohne Beobachter werden sofort abgelehnt.
- Codex kann dieselbe Anfrage nach einer Wiederverbindung erneut senden. Eine identische Anfrage behält ihren Platz. Bei einer anderen Anfrage mit derselben ID lehnt der Desktop diese ID ab und schließt die Karte.
- Wenn turn/completed eintrifft, lehnt der Actor jede offene Anfrage dieses Turns ab, bevor der Leser den Abschluss sieht. Das Aufgeben eines Turns und das Herunterfahren des Actors tun dasselbe.
- serverRequest/resolved von Codex schließt die Karte als anderswo erledigt. Während des initialize-Handshakes kann es keine Karte geben, daher wird eine Freigabeanfrage abgelehnt.
Eine Antwort aus dem Desktop-Fenster oder von einem Telefon nimmt einen einzigen Weg. Ein Telefon ruft run.chatResolveApproval auf, und der Desktop bietet die Antwort jedem laufenden Codex-Kindprozess an, den zuletzt verwendeten zuerst. Jeder Actor vergleicht Anfrage-ID, Thread-ID, Turn-ID und Item-ID mit seiner Map, also nimmt genau ein Kindprozess sie an. Eine Entscheidung, die die Anfrage nicht angeboten hat, wird abgewiesen.
Die Zugriffsstufe Unrestricted sendet approvalPolicy never. Trifft trotzdem eine Anfrage ein, beantwortet der Leser sie ohne Karte. Er wählt die erste Entscheidung, die die Anfrage anbietet, aus acceptForSession, accept und „breit annehmen“. Die anderen Zugriffsstufen senden approvalPolicy on-request.
Ziele: viele Turns in einem Run
Ein Ziel ist Zustand, den Codex an einem Thread hält: eine Zielvorgabe, ein Status, ein Token-Budget und Zähler. Solange der Status active ist, startet Codex den nächsten Turn von selbst, wenn ein Turn endet. Der Desktop startet diese Turns nicht. Er findet jeden und hängt einen Leser an, und die ganze Kette bleibt ein Chat-Run mit einer Operations-ID.
- Zielthread/goal/get · set · clear
Zustand, den Codex am Thread hält: Zielvorgabe, Status, Token-Budget und Zähler. Solange der Status active ist, startet Codex den nächsten Turn von selbst.
- Anhängenthread/resume · 3 × 250 ms
Nach jedem Turn liest der Leser das Ziel. Solange es active ist, setzt der Leser den Thread fort und nimmt den neuesten Turn: eine neue Turn-ID oder einen Turn, der noch läuft.
- Ein RunOperations-ID
Alle Turns der Kette gehören zu einem Chat-Run mit einer Operations-ID. Eine Steuerung, die zwischen zwei Turns gesendet wird, wartet auf den nächsten Turn.
- Reihenfolge bei Stopstatus: paused · turn/interrupt
Die Pause kommt zuerst und wiederholt bei Transportfehlern alle 250 ms. Das Unterbrechen hat 8 Sekunden, um den Leser zu erreichen, und 8 Sekunden für die Antwort. Bei einem Fehler gibt der Desktop den Turn auf.
| Methode | Verwendung |
|---|---|
| thread/ | Liest das Ziel. Der Leser ruft es nach jedem Turn auf. |
| thread/ | Setzt objective, status oder token |
| thread/ | Entfernt das Ziel. |
Ein Ziel hat die Felder objective, status, tokenBudget, tokensUsed, timeUsedSeconds, createdAt und updatedAt. Der Status ist einer von active, paused, blocked, usageLimited, budgetLimited und complete. Der Leser folgt der Kette nur, solange der Status active ist.
- Ein Ziel-Run startet mit thread/goal/set und dem Status active. Er sendet kein turn/start. Codex startet den ersten Turn.
- Nach jedem Turn sendet der Leser thread/goal/get. Solange das Ziel active ist, sendet er thread/resume mit excludeTurns: false und nimmt den neuesten Turn. Ein Turn zählt, wenn seine ID neu ist oder wenn er noch läuft. Der Leser versucht es 3-mal im Abstand von 250 ms und liest dann das Ziel erneut.
- Ein laufender Turn bekommt eine Turn-Route und wird wie jeder andere Turn gelesen. Ein Turn, der schon abgeschlossen ist, wird aus dem Snapshot in der resume-Antwort abgeschlossen.
- Jeder Codex-Chat-Run endet mit derselben Prüfung. Eine Nachricht an einen Thread mit einem aktiven Ziel bleibt daher ein Run, bis das Ziel den Status active verlässt. Ein Run, der kein aktives Ziel findet, sucht trotzdem einmal mit thread/resume nach einem neueren Turn und endet dann.
- Eine Steuerung, die zwischen zwei Turns eintrifft, wartet. Jeder neue Turn weckt die Steuerungs-Bahn der Outbox erneut.
- Ein Ziel-Run entlädt seinen Thread am Ende mit thread/unload. Ein fehlgeschlagenes Entladen wird dort nur protokolliert.
Stop ist eine Leiter mit vier Stufen
Stop markiert den Run als abgebrochen und arbeitet dann eine Leiter ab. Die Reihenfolge ist für Ziele wichtig: Einem Turn, der unterbrochen wird, während sein Ziel noch active ist, würde sofort ein neuer Turn folgen.
| Schritt | Aufruf, Grenze und Fehler |
|---|---|
| Auf den Turn warten | Keiner. Ein Run, der noch keinen registrierten Turn hat, wird alle 25 ms geprüft. Grenze: 45 Sekunden. Bei einem Fehler: Stop meldet, dass der Abbruch nicht bestätigt wurde. Der Run läuft weiter. |
| Ziel pausieren | thread/ |
| Unterbrechen | turn/ |
| Aufgeben | Der Actor lehnt die offenen Freigaben des Turns ab, verwirft die Turn-Route und schreibt turn/ |
Codex beantwortet turn/interrupt, wenn der Turn angehalten hat, und sendet turn/completed mit dem Status interrupted. Der Leser nimmt diesen Abschluss als Ende des Turns, und der Run endet als abgebrochen. Einen Run, der zwischen turn/start und der Registrierung des Turns abgebrochen wurde, behandelt der Run selbst: Er pausiert das Ziel und gibt den neuen Turn auf, und er wiederholt beides bei Transportfehlern alle 250 ms.
Die Fünf-Sekunden-Regel nach einem Fehler
Codex meldet ein Problem in einem Turn mit einer error-Benachrichtigung, und das Feld willRetry sagt, ob Codex weitermacht. Der Fehler allein beendet den Turn nie. Der Turn endet mit turn/completed, und der Leser wartet darauf, aber nur 5 Sekunden.
- Fehler mit erneutem Versucherror { willRetry: true }
Der Leser ignoriert ihn. Codex versucht es erneut, und der Turn läuft weiter.
- AbschlussfristTURN_FAILURE_SETTLEMENT_TIMEOUT · 5 s
Beginnt mit dem ersten Fehler ohne erneuten Versuch. Der Fehler wird gespeichert. turn/completed innerhalb des Fensters beendet den Turn als fehlgeschlagen, welchen Status es auch nennt.
- Frist endetFailed
Es kam kein turn/completed. Der Leser lässt den Turn mit dem gespeicherten Fehler scheitern und verwirft die Turn-Route. Er sendet kein turn/interrupt.
- DrainTURN_POST_TERMINAL_DRAIN_TIMEOUT · 75 ms
Nach einem sauberen turn/completed nimmt der Leser späte Item-Benachrichtigungen an, bis 75 ms ohne eine vergehen. Dann schließt er offene Freigabekarten und verwirft die Route.
| Was der Leser sieht | Ergebnis des Turns |
|---|---|
| error mit will | Nichts ändert sich. Codex versucht es erneut. |
| error mit will | Der Fehler wird gespeichert, und die Frist von 5 Sekunden beginnt. Ein zweiter Fehler ersetzt den gespeicherten und behält die erste Frist. |
| turn/ | Fehlgeschlagen, mit dem Fehler des Abschlusses, sonst mit dem gespeicherten Fehler. |
| turn/ | Fehlgeschlagen, mit dem gespeicherten Fehler. |
| turn/ | Fehlgeschlagen, wenn ein Fehler gespeichert ist. Sonst unterbrochen, und der Run endet als abgebrochen. |
| Kein turn/ | Fehlgeschlagen, mit dem gespeicherten Fehler. Der Leser verwirft die Turn-Route. |
| Die Route schließt nach einem gespeicherten Fehler | Fehlgeschlagen, mit dem gespeicherten Fehler. Es wird kein Wiedereinstieg versucht. |
| turn/ | Abgeschlossen. Der Leser liest weiter, bis 75 ms Stille vergehen, und verwirft dann die Route. |
Der Leser wacht alle 100 ms auf, wenn keine Benachrichtigung eintrifft. Er nutzt diesen Takt für Stop-Anfragen und für die Frist, und ein stiller Turn scheitert nie allein wegen der Stille. Den Drain von 75 ms gibt es, weil Codex die letzten Item-Benachrichtigungen kurz nach turn/completed senden kann. Dieselben 5 Sekunden gelten für einen Subagent-Thread: Nach einem Fehler ohne erneuten Versuch zählt sein Thread nach 5 Sekunden nicht mehr als aktiv für den Kindprozess.
turn/completed und ein Fehler ohne erneuten Versuch werden auf einer vollen Turn-Route nie verworfen. Sie warten in einer eigenen Bahn in der Reihenfolge ihres Eintreffens, und gewöhnliche Events für diesen Turn werden verworfen, solange einer wartet.
Geht die Turn-Route verloren, fragt der Desktop nach genau diesem Turn
Der Leser hat ein Signal für eine verlorene Verbindung: Seine Turn-Route schließt, während kein Fehler gespeichert ist. Der Actor schließt jede Route, wenn er stoppt, zum Beispiel wenn das stdout des Kindprozesses schließt. Der Desktop fragt Codex dann, was aus dem Turn geworden ist, und er fragt nach genau diesem Turn. Ein Turn kann nicht in einen anderen Prozess wechseln. Ein neuer Codex-Kindprozess lädt den Thread aus dem gespeicherten Verlauf und meldet einen Turn, der noch lief, als interrupted. Die Antwort ist nur dann inProgress, wenn der antwortende Kindprozess den Turn noch ausführt.
- Verlorene VerbindungConnectionLost
Die Turn-Route schließt, und kein Fehler ist gespeichert. Nur dieser Fehler startet einen Wiedereinstieg. Bei jedem anderen Fehler ohne Endstatus gibt der Desktop den Turn auf.
- Neuer Clientlease_codex_app_server_for_runtime
Der Leser wartet bis zu 1 Sekunde darauf, dass der alte Client schließt, und least dasselbe Laufzeitpaar. Die Registry startet einen neuen Kindprozess, wenn der alte weg ist.
- Wiedereinstiegthread/resume { excludeTurns: false }
Der Desktop nimmt den Turn mit der erwarteten ID. Die festgehaltenen Zugriffseinstellungen des Turns, die pid des Besitzers und die Beobachter gehen auf den neuen Client über.
- Antwortinterrupted · inProgress · completed · failed
Ein neuer Kindprozess meldet einen Turn, der noch lief, als interrupted. Ohne Fehler scheitert der Run, oder er wartet auf den nächsten Turn eines aktiven Ziels. In einen Turn wird einmal wieder eingestiegen.
- Nur eine verlorene Verbindung startet einen Wiedereinstieg. Bei jedem anderen Fehler, der kein Endstatus ist, gibt der Desktop den Turn mit turn/interrupt auf.
- Der Leser wartet bis zu 1 Sekunde darauf, dass der alte Client schließt. Dann least er einen Client für dasselbe Laufzeitpaar, und die Registry startet einen neuen Kindprozess, wenn der alte weg ist.
- Die Zugriffseinstellungen, die der alte Client für den Turn festgehalten hat, gehen auf den neuen Client über, und der Besitzer-Eintrag bekommt die neue pid.
- Hat der Client gewechselt, werden die Beobachter für Subagent-Freigaben und für Agent-Nachrichten gestoppt und am neuen Client neu gestartet.
- Der Wiedereinstieg ist thread/resume mit excludeTurns: false. Der Desktop nimmt den Turn mit der erwarteten ID aus der Antwort. Hat die Antwort keinen solchen Turn, scheitert der Run, und der Desktop gibt den Turn auf.
| Status in der Antwort | Was der Run tut |
|---|---|
| in | Der antwortende Kindprozess führt den Turn noch aus. Der Actor öffnet eine neue Turn-Route, und der Leser registriert den Turn erneut und liest weiter. |
| completed | Der Snapshot schließt den Run ab. Es wird keine Route geöffnet. |
| interrupted ohne Fehler | Die Antwort eines neuen Kindprozesses für einen Turn, der noch lief. Ein Run mit einem aktiven Ziel wartet auf den nächsten Turn des Ziels. Jeder andere Run scheitert mit „Codex app-server turn was interrupted“. |
| interrupted mit einem Fehler oder failed | Der Run scheitert mit diesem Fehler. |
Bei jeder Antwort gleicht der Desktop zuerst die Zeilen auf dem Bildschirm mit dem Snapshot des Turns ab. So bleibt die Arbeit, die Codex vor dem Verlust gespeichert hat, in der Timeline. In einen Turn wird einmal wieder eingestiegen. Scheitert auch die neue Route, endet der Run mit diesem Fehler. Zwischen zwei Turns eines Ziels hat der Leser keine Route, die er verlieren kann. Dort stellt er die Verbindung stattdessen bei Transportfehlern von thread/goal/get und thread/resume wieder her, höchstens 2-mal in Folge.
Kind-Threads, beobachtete Threads, Rollback
Ein Codex-Agent kann Subagents starten, und jeder Subagent ist ein eigener Thread unter dem Root-Thread. Kein Run des Desktops besitzt ihre Turns. Der Desktop listet sie auf, speichert, wo ihre Dateien liegen, und folgt einem nur live, solange ein Fenster ihn zeigt.
| Mechanismus | Wie es funktioniert und seine Grenzen |
|---|---|
| Kind-Liste | thread/ |
| Kind-Pfad | thread/ |
| Beobachteter Thread | Ein Fenster, das einen Subagent zeigt, registriert eine Beobachtung für das Paar aus Session und Thread. Die erste Beobachtung startet einen Folger, der die Benachrichtigungen des Threads ohne Route in Live-Zeilen umwandelt. Der Folger stoppt, wenn die letzte Beobachtung freigegeben ist und kein Turn des Threads live ist. Grenzen: Watch-ID höchstens 256 Bytes. Der Root des Threads muss der Root der Session sein, geprüft innerhalb von 10 Sekunden. |
| Folger | Abonniert die Aktivität und die Freigaben ohne Route des Clients. Wenn der Client verschwindet, abonniert er erneut. Nach dem Ende des Turns des Kind-Threads gleicht er die Live-Zeilen mit dem Verlauf ab. Grenzen: Abonniert nach 1 Sekunde erneut. 3 Abgleichsversuche. |
| Rollback | thread/ |
| Live-Einstellungen | thread/ |
Ohne Beobachter folgt nichts einem Subagent-Thread. Seine Benachrichtigungen bleiben ohne Route, und der Desktop liest den Thread aus dem Verlauf, wenn Sie ihn öffnen. Eine Freigabe von einem Subagent braucht keine Beobachtung: Der Beobachter des Root-Runs beansprucht sie, wie der Abschnitt zu Freigaben beschreibt.