Flow A Manual — Step-by-Step (executable by a fresh agent)
The validated end-to-end sequence of flow A1 (session-keeper) with exact headers, bodies and the live token acquisition — reproducible request by request via the bezetacil harness
Flow A Manual — Step-by-Step (A1)
Part of openai-research. This is the validated sequence (2026-08-27; multiple successful runs: tool call, client return, multi-turn continuation). Every step is one action; every HTTP request goes through the bezetacil trigger conv (the main process electron.net — the same transport and pool as the app).
0. Environment
- bezetacil running (
http://127.0.0.1:9228), the ChatGPT desktop app logged in (the app-server holds the session). - Installation constants (this Mac, 2026-08-27):
oai-did = a05f9cdd-ca51-4a91-9da1-9c374dfe0536(live from the renderer — verifiedatstep 2),ChatGPT-Account-Id = aa06cafc-774a-432f-b104-89d45ad24d81,User-Agent = Codex Desktop/26.820.60940 (Mac OS; arm64),originator = Codex Desktop,OAI-Language = en-US, timezoneAmerica/Sao_Paulo,timezone_offset_min = 180.
1. Live access token (getAuthStatus with includeToken)
Write /tmp/standalone-a-rpc.json:
{"method": "getAuthStatus", "params": {"includeToken": true, "refreshToken": false}}
POST http://127.0.0.1:9228/run/rpc → response:
{"id": "<uuid>", "result": {"authMethod": "chatgpt", "authToken": "eyJhbGciOiJSUzI1NiIsImtpZCI6…", "requiresOpenaiAuth": true}}
Use authToken. (The live app-server session token; the static ~/.codex/auth.json may be stale after an app-server refresh — using the stale file produced 403s on 2026-08-27 16:19+ until the live token was used.)
2. Live did
POST http://127.0.0.1:9228/run/did-runtime → (real response observed 2026-08-27 — the targets list varies with the open webContents; the important field is did):
{"targets": [{"id": 3, "url": "https://chatgpt.com/?source=codex-embedded-checkout#pricing"},
{"id": 2, "url": "app://-/index.html?initialRoute=%2Favatar-overlay"}],
"did": "a05f9cdd-ca51-4a91-9da1-9c374dfe0536", "error": null}
```.
**Never regenerate** (the did is the persisted installation identity in the renderer localStorage key `codex.chatgpt-conversations.device-id`; a fresh uuid → 403 both at challenge and conversation).
## 3. DeviceCheck token
`POST http://127.0.0.1:9228/run/devicecheck` → `{"ok": true, "result": {"supported": true, "token": "AgAA…", "len": 2980}}` (generated in the MAIN process — the app bundle context — same as the app's own flow).
## 4. Per-turn cycle
Run 4.1 → 4.2 → 4.3 for **each** turn (user message, tool return, continuation). Write each input to `/tmp/standalone-a-input.json`, then `POST http://127.0.0.1:9228/run/conv`.
### 4.1 attestation challenge
Input:
```json
{"url": "/ios/attestation_challenge", "method": "GET",
"headers": {
"OAI-Language": "en-US",
"oai-did": "a05f9cdd-ca51-4a91-9da1-9c374dfe0536",
"originator": "Codex Desktop",
"User-Agent": "Codex Desktop/26.820.60940 (Mac OS; arm64)",
"Authorization": "Bearer <authToken from step 1>",
"ChatGPT-Account-Id": "aa06cafc-774a-432f-b104-89d45ad24d81",
"x-sentinel-dc": "{\"token\": \"<devicecheck token from step 3>\"}"}}
→ {"ok": true, "result": {"status": 200, "text": "{\"attestation_challenge\": \"gAAAAABqkGYnoU…\", …}"}}.
The challenge is one-time — never reuse it for a second request.
4.2 prepare
Input:
{"url": "/f/conversation/prepare", "method": "POST",
"headers": {
"OAI-Language": "en-US",
"oai-did": "a05f9cdd-ca51-4a91-9da1-9c374dfe0536",
"originator": "Codex Desktop",
"User-Agent": "Codex Desktop/26.820.60940 (Mac OS; arm64)",
"Authorization": "Bearer <authToken>",
"ChatGPT-Account-Id": "aa06cafc-774a-432f-b104-89d45ad24d81",
"Content-Type": "application/json",
"x-conduit-token": "no-token"},
"body": {
"action": "next",
"client_prepare_state": "sent",
"model": "gpt-5-6-thinking",
"thinking_effort": "extended",
"timezone": "America/Sao_Paulo",
"timezone_offset_min": 180}}
→ {"status": 200, "text": "{\"status\":\"ok\",\"conduit_token\":\"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9…\"}"}.
4.3 conversation
Input (first turn of a new conversation — omit conversation_id and parent_message_id; every following turn includes both):
{"url": "/f/conversation", "method": "POST",
"headers": {
"OAI-Language": "en-US",
"oai-did": "a05f9cdd-ca51-4a91-9da1-9c374dfe0536",
"originator": "Codex Desktop",
"User-Agent": "Codex Desktop/26.820.60940 (Mac OS; arm64)",
"Authorization": "Bearer <authToken>",
"ChatGPT-Account-Id": "aa06cafc-774a-432f-b104-89d45ad24d81",
"Content-Type": "application/json"},
"body": {
"action": "next",
"local_function_signatures": [
{"description": "Redirect the user's request from ChatGPT to Work mode when Work mode is the better execution environment.\n\nYou MUST call this tool before doing any work when the request involves:\n- Browser use or computer-use automation\n- Building apps, local coding, repository edits, command execution, or file inspection\n- Opening, updating, reviewing, or otherwise working with PRs\n- Creating, editing, converting, inspecting or delivering files or artifacts, including implicit requests for downloadable or editable deliverables such as slide decks, `.pptx`, spreadsheets, `.xlsx`, workbooks, documents, `.docx`, or PDFs,\n- Complex analysis such as financial modeling\n\nPrefer answering directly in ChatGPT for:\n- Email, message, or prose drafting\n- Brainstorming, planning, or explanation\n- Code snippets or examples that fit naturally in chat\n\nIf the user rejected the suggestion, don't call this tool again.",
"name": "handoff",
"params": [
{"name": "prompt", "required": true, "type": {"description": "A brief instruction describing what Work mode should do for the user.", "type": "string"}},
{"name": "reason", "required": true, "type": {"description": "A short user-facing reason why continuing in Work mode would help.", "type": "string"}}],
"type": "kwargs"}],
"messages": [
{"author": {"metadata": {}, "name": null, "role": "user"},
"channel": null,
"content": {"content_type": "text", "parts": ["<the user text>"]},
"create_time": <unix seconds as a float>,
"end_turn": null,
"id": "<new uuid>",
"metadata": {},
"recipient": "all",
"status": "finished_successfully",
"update_time": null,
"weight": 1}],
"model": "gpt-5-6-thinking",
"supported_encodings": ["v1"],
"thinking_effort": "extended",
"timezone": "America/Sao_Paulo",
"timezone_offset_min": 180,
"client_prepare_state": "success",
"app_attest_challenge": "<challenge from 4.1>",
"conversation_id": "<id of the existing conversation — omitted on the first turn>",
"parent_message_id": "<id of the last message — omitted on the first turn>"}}
→ 200 with the SSE (delta v1: add/append/patch; message_marker events cot_token, user_visible_token, final_channel_token; streams end with message_stream_complete + conversation_detail_metadata + title_generation).
Invariants that are now facts (each verified):
- No
x-conduit-tokenheader on/f/conversation(the app's own winning request has none; the conduit is prepare-only). - The first turn of a conversation omits
conversation_id/parent_message_id; every following turn includes both (omitting them on a continuation → 403; the app always sends them in follow-up payloads). - The
local_function_signaturesmust contain thehandoffsignature in normal turns (a turn with only a custom tool and no handoff → 403). - The whole cycle (challenge, prepare, conversation) stays on the same transport (the main/trigger path).
5. Multi-turn continuation (same conversation)
For the next user message: run 4.1 (fresh challenge), 4.2 (fresh prepare), 4.3 with conversation_id and parent_message_id = the last assistant message.id (from the previous SSE — or from the conversation tree via GET /conversation/<id>), messages = the new user record. Validated: 200 → "De nada.".
6. TypeScript types (the A1 surface)
// ── app-server RPC (via the bezetacil rpc trigger) ───────────────────────
export type AuthMode =
| 'apiKey' // Codex-managed API key
| 'chatgpt' // managed ChatGPT OAuth (tokens persisted/refreshed by Codex)
| 'chatgptAuthTokens' // EXTERNAL-host-provided tokens, memory-only (this is our A1 state:
// authToken obtained AS-IS from getAuthStatus {includeToken:true})
| 'amazonBedrock' // Managed Bedrock
// source: codex-rs/app-server-protocol/src/protocol/common.rs line 24 (enum AuthMode)
export interface GetAuthStatusParams {
includeToken: boolean // false = no token returned (the app's own calls use false)
refreshToken: boolean // false = never refresh as a side effect
}
export interface GetAuthStatusResponse {
authMethod: AuthMode
authToken?: string // present only when includeToken: true (the LIVE session token)
requiresOpenaiAuth: boolean
}
// ── triggers (bezetacil) ─────────────────────────────────────────────────
export interface DidRuntimeResult { // POST :9228/run/did-runtime
targets: Array<{ id: number; url: string }>
did: string | null // the renderer localStorage value
error: string | null
}
export interface DeviceCheckTriggerResult { // POST :9228/run/devicecheck
supported: boolean
token: string // base64 App-Attest token (2980 chars)
len: number
}
// ── attestation challenge ────────────────────────────────────────────────
export interface ChallengeHeadersToken { // x-sentinel-dc value
token: string
}
export interface AttestationChallengeResponse {
attestation_challenge: string // 'gAAAAAB…' — one-time per use
}
// ── prepare ──────────────────────────────────────────────────────────────
export interface PrepareHeaders {
'OAI-Language': 'en-US'
'oai-did': string
originator: 'Codex Desktop'
'User-Agent': `Codex Desktop/${string} (Mac OS; arm64)`
Authorization: `Bearer ${string}`
'ChatGPT-Account-Id': string
'Content-Type': 'application/json'
'x-conduit-token': 'no-token'
}
export interface PrepareResponseBody { // decoded from the trigger text
status: 'ok'
conduit_token: string // JWT ES256 — see Jet claims below
}
/** The conduit JWT payload (decoded from real tokens). */
export interface ConduitTokenClaims {
conduit_uuid: string
conduit_location: string // '10.130.x.x:830x' (internal cluster coord)
cluster: string // 'unified-83' / 'unified-84' — the routing pin
iat: number
exp: number // iat + 60 (1-minute lifetime)
turn_topic_id: string | null
}
// ── conversation request headers ─────────────────────────────────────────
export interface ConversationHeaders extends Omit<PrepareHeaders, 'x-conduit-token' | 'Content-Type'> {
'Content-Type': 'application/json'
// NO x-conduit-token on /f/conversation (verified)
}
// ── conversation request body / response ─────────────────────────────────
export type ConversationRequestBodyV1 =
import('./flow-a-tool-protocol').ConversationRequestBody // the shared type from the tool protocol page
export interface ConversationResponseStream {
/** SSE text of /f/conversation (see [response-sse-real](response-sse-real.md) for the event types). */
// events: delta_encoding 'v1'; data-only; delta {c,o,p,v}; message_marker; title_generation;
// message_stream_complete; conversation_detail_metadata; server_ste_metadata
}
Cross-references
- flow-a-tool-protocol — the tool contract (call_id, end_turn, recipient, channel)
- flow-a-403-cases — what fails and why
- flow-definitions — A / A0 / A1 / B
- bezetacil — the harness (triggers rpc / conv / devicecheck / did-runtime / cookie-check)