---
type: research
title: Modo ChatGPT, Submodo Chat — Protocolo Real (traces da sessão)
description: The chat submode protocol reconstructed from 40 captured traces — typing throttle, attestation, UI efforts, multi-turn
tags: [openai, chatgpt, submodo-chat, protocol, delta, traces]
timestamp: 2026-08-25
---

# Modo ChatGPT, Submodo Chat — Protocolo Real

Part of [openai-research](openai-research.md). **Source: 40 real traces captured** via bezetacil v4 in the main process (net-fetch-hook) during the test session — not static RE.

## Nomenclatura UI → payload

| Seletor na UI | `model` no payload | `thinking_effort` no payload |
|---|---|---|
| **Instant** | `gpt-5-5-instant` | **ausente** |
| **Medium** | `gpt-5-5-thinking` | **`standard`** |
| **High** | `gpt-5-5-thinking` | **`extended`** |
| Model 5.6 Sol / 5.5 (Model menu) | `/models` web slugs | per selection |

**The chat submode UI efforts are own**: `standard` (UI Medium) e `extended` (UI High) — different from the codex scale (`low/medium/high/xhigh`). The "Instant" is a **separate slug** (`gpt-5-5-instant`), without `thinking_effort` in the payload.

## O ciclo por mensagem (ordem real capturada)

```
1. User TYPES → POST /f/conversation/prepare (debounced por tecla)
   body: {action: next, client_prepare_state: sent, partial_query: {…partial text…},
          model, thinking_effort, conversation_id?}
   ← {conduit_token}                    — typing throttle: pre-caches the conversation
   (partial_query carries the PARTIAL TEXT — prepare runs on every keystroke)

2. User SENDS → GET /backend-api/ios/attestation_challenge
   headers: + x-sentinel-dc: {DeviceCheck token}
   ← {attestation_challenge: gAAAAAB…}

3. POST /f/conversation
   body: {action: next, client_prepare_state: "success",
          messages: [t6r completo — a mensagem do usuário],
          app_attest_challenge: gAAAAAB…,
          supported_encodings: [v1],
          model, thinking_effort, conversation_id?, parent_message_id?,
          local_function_signatures: [handoff]}
   ← SSE: delta protocol v1 (ver abaixo; see [flow-a-manual-step-by-step](flow-a-manual-step-by-step.md) for the full literal request)

4. Multi-turn: conversation_id + parent_message_id (última assistant message)
```

## Diferenças do que a RE estática reconstruiu (correções impostas pelos traces)

| Campo | RE estática dizia | Trace real |
|---|---|---|
| user message field | `partial_query` | **`messages`** (full t6r) — `partial_query` is only the typing throttle |
| `client_prepare_state` | `'sent'` | **`'success'`** no conversation (verified 2026-08-26/27 on real payloads); `'sent'` only in the prepare |
| `app_attest_challenge` | absent | **mandatory** on conversation (attestation, not sentinel) |
| `supported_encodings` | não existia | **`['v1']`** |
| `thinking_effort` | low/medium/high | **`standard`/`extended`** (UI Medium/High) |
| "Instant" | effort | **slug separado** (`gpt-5-5-instant`), sem effort |
| sentinel/PoW | necessário | **AUSENTE no submodo chat do desktop** — o mecanismo é attestation |
| `local_function_signatures` | só no submodo work | **PRESENTE também no submodo chat** (a tool handoff é oferecida sempre no modo ChatGPT) |

## O prepare como throttle (os 40 traces)

The prepare fires **on every keystroke** (debounced): `partial_query` carries the growing **partial text** (`'qual a cor do elefante'` → `'…azul ou rosa? re'` → `'…responda em poucas palavras'`). The server pre-processes the conversation while the user types — that is why long web sessions freeze: **continuous PoW + prepare** (the insight that preceded the PoW discovery).

Prepares sem `partial_query` (só `conversation_id`) = prepare de background ao abrir a conversa.

## `local_function_signatures` — the client-declared tool mechanism

Beyond the server-side tool registry (`web.run` — executed server-side, results interleaved as `role: tool`), the chat submode body carries **`local_function_signatures`** — functions **declared by the client** in the request, callable by the model, **executed by the client**, with the result reinjected as `role: 'tool'` (the observed handoff loop). The `handoff` tool is the live example. For a standalone harness this is the extension point to add own tools to the chat submode without touching OpenAI's server: declare the signature, execute in the harness, inject the result back.

The **handoff trigger card** ("Continue in work mode" + countdown vs "Stay") is **renderer-local UX** — no dedicated request observed in the captured traffic (the task poll/creation happens through other channels); the pending-task state drives the card.

## O handoff → task → role=tool loop (trace [37])

A conversa do chat chamou `handoff` → task criada no Work → o **resultado voltou como `role: 'tool'`** no próximo prepare/conversation, com `client_prepare_state: 'success'`. O loop chat→task→resultado dentro da mesma conversa web, confirmado nos traces.

## 6. TypeScript types (as built by the renderer builder)

The renderer builder (asar) emits (the fields below are literal in the builder):

```ts
// ── enums ────────────────────────────────────────────────────────────────
export type ConversationOrigin = 'tpp' | 'non-tpp' | string   // 'tpp' = Work tasks (sidebar filter)
export type ConversationAction2 = 'next'                      // only 'next' in the builder

// ── the conversation body (renderer builder) ─────────────────────────────
export interface RendererConversationBody {
  action: ConversationAction2
  attachment_mime_types?: string[]                // present when attachments exist; else undefined
  client_prepare_state: 'sent' | 'success' | 'failure'
  conversation_id?: string                        // undefined for a brand-new conversation
  continue_from_shared_conversation_id?: string   // shared-conversation path; undefined otherwise
  conversation_origin?: ConversationOrigin
  locale?: string
  local_function_signatures?: import('./flow-a-tool-protocol').LocalFunctionSignature[]
  message_id?: string
  messages: import('./flow-a-tool-protocol').ConversationMessage[]
  model: string
  parent_message_id?: string
  service_tier?: string                           // 'default' observed
  supported_encodings: ['v1']
  system_hints?: unknown[]                        // omitted when empty
  thinking_effort?: string                        // 'standard' | 'extended' (chat scale); undefined for Instant
  timezone: string                                // Intl.DateTimeFormat().resolvedOptions().timeZone
  timezone_offset_min: number
  is_client_thread?: boolean                      // only when the thread has no server conversation
}
```

## Cross-references

- [attestation-flow](attestation-flow.md) — o mecanismo de integridade do submodo chat
- [work-submode-protocol](work-submode-protocol.md) — o submodo work
- [auth-and-link-session](auth-and-link-session.md) — o token e as claims
- [probes](probes.md) — os traces e a metodologia
- [flow-definitions](flow-definitions.md) — onde o submodo chat se encaixa nos fluxos
- [flow-a-manual-step-by-step](flow-a-manual-step-by-step.md) — a sequência validada (A1) com headers/bodies literais
- [flow-a-tool-protocol](flow-a-tool-protocol.md) — o contrato validado de tool calling local
- [postmortem-flow-a-2026-08-27](postmortem-flow-a-2026-08-27.md) — as falhas (403) e as lições do ciclo A


## No `x-codex-turn-state` in the chat submode

Observed: the `x-codex-turn-state` header appears **zero times** in the chat submode flow (verified against the net-fetch-hook captured traffic). The sticky routing turn-state is a mechanism of the **codex backend** (work submode + Codex mode), not the chat submode — the web flow keeps turn state in the session/topic via the `resume_conversation_token` instead.
