---
type: research
title: Codex Mode Runtime Trace — Mode Inside the Desktop App
description: Live evidence of the Codex mode (top-level, no submodes) inside the ChatGPT desktop — webrun/alpha-search tool, model switch, task-title agent, and the differences vs the work submode
tags: [openai, chatgpt, codex-mode, app-server, webrun, alpha-search, reverse-engineering]
timestamp: 2026-08-26
---

# Codex Mode Runtime Trace — Mode Inside the Desktop App

Part of [openai-research](openai-research.md). Live capture 2026-08-26 20:57 (BRT 15:57): the user opened the Codex mode (top-level, no submodes), asked a search question (`"Se a minha mãe tivesse bolas, ela seria o meu pai.\n\nPesquise online a quem se atribui essa frase.\n"` — attribution found: Max Verstappen), then switched the model mid-conversation (`gpt-5.6-sol` → `gpt-5.6-luna`, effort `low`) and asked "que legal, e qual foi a repercussão?" (multi-turn). Evidence: `app-server-stdio.jsonl`, `logs_2.sqlite`, `state_5.sqlite`.

## Thread metadata (state_5.sqlite — `01a03fdd-1e38-…`)

- `model: gpt-5.6-luna` (final; `Configuring session` shows `gpt-5.6-sol` at 20:57:30 then `gpt-5.6-luna` at 20:57:42 — the in-conversation switch)
- `reasoning_effort: low` (the UI "light"), `sandbox_policy: {"type":"disabled"}`, `approval_mode: never`, `memory_mode: enabled`
- **`thread_source: user`** (NOT `chatgpt_handoff` — direct user-initiated thread), `source: vscode`
- `tokens_used: 626,727` — same order of magnitude as the work submode (706,805 on the Taylor thread)
- Transport: same app-server; `responses_websocket` "connected" frames; model refresh `GET /backend-api/codex/models?client_version=0.150.0` every ~3min

## The tool: `webrun` → `POST /backend-api/codex/alpha/search`

```
handle_tool_call_with_source: dispatch_tool_call_with_code_mode_result {
  otel.name=webrun tool_name=webrun call_id="exec-a1889571-…" aborted=false }
  : endpoint_session.execute_with { http.method=POST api.path="alpha/search" }:
  Request completed POST https://chatgpt.com/backend-api/codex/alpha/search status=200 OK
```

- The Codex mode web search is the **`webrun`** tool, executed via the **code-mode host** (`dispatch_tool_call_with_code_mode_result`), hitting **`alpha/search`** on the app-server backend — this is the `/codex/alpha/search` endpoint documented as "Search on demand" in [codex-flow-protocol](codex-flow-protocol.md), now confirmed as the tool path of the Codex mode.
- Compared to the work submode: work used `web_search_call` (standalone web search `action.search` in the Responses stream) — different execution seams.

## The task-title agent

A turn-mate system prompt ran for the task created from that prompt: `"You are a helpful assistant. You will be presented with a user prompt, and your job is to provide a short title for a task that will be created from that prompt.\nThe tasks typically have to do wi…"` — a lightweight title generator that annotates the task/thread from the user text (the longer `state_5.threads.title` shows the original prompt, not the generated title in this run — the agent result feeds the creation flow).

## Events per tool call

`app-server event: account/rateLimits/updated` and `thread/tokenUsage/updated` after each tool call — the UI quota/usage updates ride the same app-server event stream (also seen in the work submode).

## No `x-codex-turn-state` on the desktop (correction)

`x-codex-turn-state` appears **0 times** in the recent desktop logs (Codex mode, 20:57–21:07). The sticky turn-state routing came from the self-contained probe of 2026-08-24 (API access), not from the desktop app — the desktop uses the `responses_websocket` session instead. See [codex-flow-protocol](codex-flow-protocol.md) note.

## Codex mode vs work submode (what differs)

| Surface | Codex mode | Work submode |
|---|---|---|
| Thread source | `user` | `chatgpt_handoff` (from chat handoff) / `user` when direct |
| Web search tool | `webrun` → `alpha/search` | `web_search_call` (standalone) + `web__run` (CTR) |
| Scheduling tool | — | `automation_update` (MCP `codex_app`) |
| Initiation | top-level tab | chat handoff (card) or Work tab |
| Effort scale | same codex scale (`low` vs `high`) | same (thread `reasoning_effort`) |
| Quota | codex quota | codex quota (agent turns) |

## Attestation provider (x-oai-attestation) — same mechanism as work

The app-server attaches `x-oai-attestation` (header constant `X_OAI_ATTESTATION_HEADER`, core client `generate_attestation_header_for` → `AttestationProvider::header_for_request`) when `model_provider.supports_attestation()`. The desktop provider is the **DeviceCheck addon**: each `Configuring session` is preceded by `AttestationGenerate { request_id: 2 }` (sol) and `{ request_id: 3 }` (luna) — the token is generated per session/thread, exactly as in the work submode. So flow A (attestation) applies to the Codex mode too (and to work); the sentinel/PoW fallback is the chat-submode/main mechanism.

## Standalone web search — same service, two seams (RESOLVED)

From codex-rs tests (`core/tests/suite/code_mode.rs:489`, `core/src/tools/spec_plan_tests.rs`):

- The Responses-native tool `web_search` (server-side, `ToolSpec::WebSearch` with `external_web_access`/`indexed_web_access`) is what the **model sees when no `web.run` executor** is present (work submode: `web_search_call`).
- With `Feature::StandaloneWebSearch` + a **`web` namespace tool executor** (extension/MCP/CTR), the model sees `web.run` (not `web_search`) — and the tool runs as a `custom_tool_call "exec"` with the JS runtime:
  ```js
  const result = await tools.web__run({ search_query: [{ q: "…" }] });
  text(result);
  ```
- That runtime (code mode host) performs **`POST /v1/alpha/search`** on the app-server → desktop: `POST /backend-api/codex/alpha/search`, with body:
  ```json
  {"model": "<slug>", "commands": {"search_query": [{"q": "…"}]},
   "settings": {"allowed_callers": ["direct"], "external_web_access": true}}
  ```
- So the **Codex mode `webrun`** and the work submode `web_search_call` are two seams of the **same standalone web search service** (the web search backend; the work submode runs it server-side inside the Responses stream — no client `alpha/search` — while the Codex mode runs it client-side via `webrun` → `alpha/search`). `WebSearchMode`: Live / Indexed / Cached / Disabled (per-turn config).

## Task-title agent + onboarding strings

The turn-mate `"You are a helpful assistant … provide a short title for a task"` system prompt annotates the task being created (this ran in the handoff/creation turn). It is the "task title" service — evidence of a nested agent in the creation path, feeding the `wham/tasks` creation.

## No "phantom tokens"

Searched the app (asar, app-server strings) and codex-rs: **no phantom/token-swap endpoint exists**. The only token exchange is the **link-session** (`auth_token → __oailb` 1h, see [auth-and-link-session](auth-and-link-session.md)); no derived sidecar tokens beyond the OAuth family (id/access/refresh) + `__oailb`.

## TypeScript types (code mode surface)

```ts
// ── the webrun tool call (tracing) ───────────────────────────────────────
export interface WebrunToolCallTrace {
  otel_name: 'webrun'
  tool_name: 'webrun'
  call_id: string          // 'exec-<uuid>' — the code-mode executor call id
  aborted: boolean
}
// …dispatched through: dispatch_tool_call_with_code_mode_result (code mode host) →
//   endpoint_session.execute_with { http.method: 'POST', api.path: 'alpha/search' } →
//   POST https://chatgpt.com/backend-api/codex/alpha/search  ← the ONLY client-side search seam

export interface AlphaSearchRequest {
  model: string
  commands: { search_query: Array<{ q: string }> }
  settings: { allowed_callers: ['direct']; external_web_access: boolean | 'indexed' }
}
export type WebSearchMode = 'live' | 'indexed' | 'cached' | 'disabled'
export interface WebSearchModeTarget {          // per-turn config
  web_search_mode: WebSearchMode | null         // null = server default
}

// ── the CTR (web namespace) shape ────────────────────────────────────────
export interface WebRunJs {                     // custom_tool_call 'exec' input (code mode)
  // const result = await tools.web__run({ search_query: [{ q: '<query>' }] });
  // text(result);
  search_query: Array<{ q: string }>
}

// ── the task-title agent ─────────────────────────────────────────────────
export const TASK_TITLE_PROMPT: string =
  "You are a helpful assistant. You will be presented with a user prompt, and your job is to provide a short title for a task that will be created from that prompt."
// (the run: the creation path of wham/tasks; the threads.title kept the original prompt in the observed run)

// ── code mode thread metadata (state_5) ──────────────────────────────────
export interface CodeModeThreadMeta {
  id: string
  model: 'gpt-5.6-sol' | 'gpt-5.6-luna' | string
  reasoning_effort: 'low' | 'medium' | 'high' | string
  sandbox_policy: { type: 'disabled' }
  approval_mode: 'never'
  memory_mode: 'enabled'
  thread_source: 'user' | 'chatgpt_handoff' | 'realtime_voice' | string
  source: 'vscode'
  tokens_used: number                    // 626,727 observed
}
```

## Cross-references

- [codex-flow-protocol](codex-flow-protocol.md) — backend endpoints, quota snapshot
- [work-runtime-trace](work-runtime-trace.md) — the work submode loop (the analogous trace)
- [chatgpt-desktop-architecture](chatgpt-desktop-architecture.md) — the mode hierarchy
- [bezetacil](../bezetacil/bezetacil.md) — how the capture happens
