Responses Lite And Full
Codex backend Responses has two request dialects. The ordinary dialect, which Codex Pooler calls Full, sends instructions and tools as top-level request fields. The Lite dialect carries the same information inside the request input array instead, and sets a marker so the provider knows which dialect it is reading.
Both dialects describe the same turn, reach the same upstream route, and return the same response shape. They differ only in how the outgoing request is assembled.
Codex Pooler decides the dialect per Pool and per model, then guarantees it on the way upstream. Clients keep one Pool API key, one base URL, and one model id in either mode.
Use this page when you need to know what Lite actually changes, why a request shape differs from what your client sent, or what a serving mode does and does not affect. For the operator workflow that sets the mode, see Pools. For where these requests are admitted and dispatched, see Runtime Routes.
Why Do Two Dialects Exist?
Section titled “Why Do Two Dialects Exist?”Some Codex models are served by a backend that expects the Lite request shape. Its model catalog entry advertises this with a use_responses_lite boolean. A Codex-compatible client reads that flag and builds the matching request; a client that sends the ordinary shape to a Lite model, or the Lite shape to an ordinary one, can be rejected by the provider.
Because a Pool routes one model id across several upstream accounts, Codex Pooler cannot leave that decision to the client. It resolves the dialect itself, advertises the result in its own catalog, and applies the matching request shape at dispatch.
Lite And Full Use The Same Route
Section titled “Lite And Full Use The Same Route”Serving mode never changes the endpoint, the model id, or the credentials.
| Property | Lite and Full |
|---|---|
| Client endpoint | Identical. The same /backend-api/codex or /v1 route in both modes |
| Upstream route | Identical. Ordinary Responses work and backend compact work reach their matching backend Responses route in both modes |
| Client-visible model id | Identical |
| Pool API key and base URL | Identical |
| Response shape | Identical. Codex Pooler applies no Lite-specific handling to the response or the event stream |
| Transport choice | Identical. HTTP, SSE, and websocket eligibility never read the serving mode |
The dialect marker itself travels differently per transport:
- Over HTTP and SSE it is the upstream request header
x-openai-internal-codex-responses-lite: true. It is absent, notfalse, in Full. - Over websockets it is a request-body entry,
client_metadata.ws_request_header_x_openai_internal_codex_responses_lite, whose value is the string"true". It is set per turn, so one websocket connection can legitimately carry Lite and Full turns.
There is no Lite marker in the HTTP request body, and no marker of either kind in any response Codex Pooler returns to a client.
What Changes On The Wire
Section titled “What Changes On The Wire”On the ordinary Responses lanes — HTTP, SSE, and the websocket response.create turn — Lite rewrites the outgoing request body. Backend compact routes receive the same Lite rewrite before their compact-specific projection runs. This is a real translation, not a flag.
| Request field | Full | Lite |
|---|---|---|
tools | Sent as a top-level array | Removed from the top level. The tool list becomes the first input item, an additional_tools item with role developer |
instructions | Sent as a top-level string | Removed from the top level. A non-blank value becomes a developer message item placed immediately after the tools item |
parallel_tool_calls | The client’s value, preserved exactly, including absent when the client omitted it | Forced to the boolean false |
reasoning.context | Left as sent, so the provider default applies | Forced to the string all_turns |
input_image detail | Preserved | The detail field is removed from input_image entries in typed message content and in function_call_output and custom_tool_call_output outputs |
| Dialect marker | Removed if a client supplied one | Added by Codex Pooler |
The additional_tools item is always present in Lite, even when the request declared no tools. In that case its tools array is empty.
A Request In Both Dialects
Section titled “A Request In Both Dialects”A client sends this ordinary Responses body:
{ "model": "example-model", "instructions": "Example system instructions.", "tools": [ { "type": "function", "name": "lookup", "parameters": { "type": "object", "properties": {} } } ], "parallel_tool_calls": true, "input": [ { "type": "message", "role": "user", "content": [{ "type": "input_text", "text": "Example user turn." }] } ]}Served in Full, the tools and instructions stay where they are. Served in Lite, the same turn is restructured before dispatch:
{ "model": "example-model", "parallel_tool_calls": false, "reasoning": { "context": "all_turns" }, "include": ["reasoning.encrypted_content"], "input": [ { "type": "additional_tools", "role": "developer", "tools": [ { "type": "function", "name": "lookup", "parameters": { "type": "object", "properties": {} } } ] }, { "type": "message", "role": "developer", "content": [{ "type": "input_text", "text": "Example system instructions." }] }, { "type": "message", "role": "user", "content": [{ "type": "input_text", "text": "Example user turn." }] } ]}Top-level instructions and tools are gone, the tool list leads the input, and the instructions follow it as a developer message.
These blocks isolate the serving-mode difference. Codex Pooler applies other normalization to every ordinary backend Responses request in both modes — it maps the model id to the selected upstream account’s identifier, ensures include carries reasoning.encrypted_content, and lowers non-strict tool schemas — so a captured upstream body carries those changes too, in Lite and in Full alike.
Who Decides The Dialect?
Section titled “Who Decides The Dialect?”Codex Pooler decides, and it closes the loop at both ends.
- The Pool’s serving mode for that model resolves to Lite or Full before dispatch.
GET /backend-api/codex/modelsadvertisesuse_responses_liteas that effective serving mode, not as the raw value an upstream account reported. This is what a Codex-compatible client reads to decide which dialect to build.- A Lite-aware client therefore builds the Lite shape itself, and Codex Pooler applies the same translation again at egress.
Applying the translation twice is safe because it is idempotent. A leading additional_tools developer item that carries no id is reused rather than wrapped a second time, and a request that already omits top-level instructions produces no duplicate developer message. Normalizing an already-normalized request produces an identical request.
additional_tools is also an ordinary public Responses item type. Codex Pooler adopts an existing item as the Lite tools prefix only when the request declared no top-level tools and the item is leading, carries role developer, and has no id — the exact shape a Lite-aware client produces. An additional_tools item that carries an id is always treated as ordinary input.
Clients cannot set the dialect. Codex Pooler removes any client-supplied x-openai-internal-codex-responses-lite header and any client-supplied ws_request_header_x_openai_internal_codex_responses_lite entry from client_metadata on every request, then adds its own only when the effective mode is Lite. Codex Pooler never reads a Lite preference out of a request body or header.
The public GET /v1/models response carries no serving-mode field.
How Codex Pooler Resolves The Mode
Section titled “How Codex Pooler Resolves The Mode”A serving mode belongs to one Pool and one canonical exposed model id. The same model can use different modes in different Pools, and the choice applies to every Pool API key in that Pool.
| Configured mode | Effective mode | Recorded source |
|---|---|---|
auto (the default, stored as no override) | Lite when any routable catalog source for that Pool-model pair reports literal true for Lite support, otherwise Full | catalog |
lite | Lite | override |
full | Full | override |
Two details of Auto matter in practice:
- Only a literal
trueselects Lite.false, a missing key, the string"true", the number1, and any other malformed value all select Full. When a model entry has no per-source map at all, only a literaltruetop-level value selects Lite. - “Routable” means health-level routable. The assignment is active, eligible, has an
activehealth status, is past any cooldown, and belongs to a visible upstream account. It does not mean that account has quota available, or that this turn will select it. A single Lite-advertising account is enough to put the whole Pool-model pair in Lite.
A model with no routable source at all is not resolved to a serving mode. The request fails eligibility with a 503 and no upstream dispatch, rather than defaulting to a dialect.
Serving-mode choices are stored independently of the model catalog. They persist while a model is stale, retired, or suppressed, remain editable, and re-apply when the model returns under the same exposed model id.
What Serving Mode Does Not Affect
Section titled “What Serving Mode Does Not Affect”This section exists because the mode is easy to over-attribute. Lite and Full change the outgoing request shape and nothing else.
| Concern | Effect of serving mode |
|---|---|
| Context window | None. A model advertises the same context window in Lite and in Full. Codex Pooler derives the advertised window from the operator context-window override, the model’s pricing bucket, and the model’s effective context-window percentage — the serving mode is not an input, and the Lite boolean is applied after that policy has already run |
| Token budgets and auto-compaction limits | None |
| Pricing and accounting | None. Reservation is derived from the request payload and the API key’s output-token policy. The mode appears in request and attempt metadata only as diagnostic labels |
| Routing and eligibility | None. Serving mode never removes an upstream assignment from the candidate set and never reorders it |
| Catalog partition selection | None. Partitions are computed from the pristine upstream model entries, before the effective mode is applied |
| Plan family | None. Lite support is a model catalog property. Codex Pooler has no rule that ties Lite to Free, Go, Plus, Pro, Team, Business, or Enterprise |
| Response payloads | None |
In the Codex model catalog, the Lite flag and the context-window family are independent fields. A model can advertise Lite with a large context window, or the ordinary dialect with a small one. Neither implies the other.
What This Means For Clients
Section titled “What This Means For Clients”Codex Pooler applies the Lite translation server-side, at egress, on every lane that dispatches ordinary or backend compact Responses work. A client that knows nothing about Lite can send an ordinary Full-shaped body and Codex Pooler produces the Lite shape for it. Compact dispatch then keeps its own narrow projection and validation rules: compact-only controls are still removed, while Lite tools and instructions are carried in the rewritten input array.
Most OpenAI-compatible clients are unaware of Lite and need no configuration for it. For those clients, a Lite Pool has these observable effects:
parallel_tool_callsis overridden tofalse, so parallel tool calling is off regardless of what was requested.reasoning.contextis forced toall_turns.detailpins on input images are not forwarded. Adetailofhighorlowhas no effect in Lite.- The response, including the event stream, is unchanged.
On native, non-compact Codex backend Responses routes, a present input or tools value must be an array. A non-list value is rejected before dispatch with 400 invalid_request in both Lite and Full, so it is never silently dropped or forwarded with changed meaning. The narrow /v1 surface continues to normalize a string input into an item array before serving-mode handling, so existing /v1 string-input behavior is unaffected.
There is one hard limit. On a Lite-served model, a map-shaped tool_choice is rejected before any upstream request, with HTTP 400, code unsupported_parameter, and param: "tool_choice". The rejection follows the model’s serving mode rather than the endpoint, so it applies to a Chat Completions named-function choice too. Scalar choices such as "auto", "none", and "required" are accepted in both modes. If a client needs forced typed tool selection, that model must be served in Full.
Clients that implement Lite themselves stay compatible, because the translation is idempotent: a Lite-aware client talking to a Lite Pool produces the same upstream request whether the client or Codex Pooler built the shape.
The conversion runs in one direction only. Codex Pooler translates an ordinary request into the Lite dialect when the mode is Lite; it never translates a Lite-shaped request back into the ordinary dialect. A client that forces Lite on its own side while the Pool serves Full will have its Lite-shaped body forwarded as sent, without the Lite marker. Leave client-side Lite settings at their default and let the Pool decide, which is what a client does when it reads use_responses_lite from the Pool’s own catalog.
Stability Within A Request
Section titled “Stability Within A Request”One HTTP request, or one websocket response.create turn, keeps the serving mode it started with. The resolved mode is captured once, before dispatch, and is then immutable for that request. It survives:
- same-assignment retry,
- cross-assignment failover to a different upstream account,
- cross-node owner forwarding.
Saving a new mode does not mutate a request or turn that is already in flight. A change is picked up by the next HTTP request, or by the next response.create turn on an already-open websocket connection.
Because the effective mode is part of the backend catalog body, changing it changes the GET /backend-api/codex/models ETag. A backend websocket keeps its upgrade ETag for backward compatibility, but every accepted response.create turn also receives a codex.response.metadata event whose x-models-etag value is authoritative for that turn. An already-open connection can therefore observe the new ETag on its next turn; an in-flight retry keeps the turn snapshot it started with.
Operational Evidence
Section titled “Operational Evidence”Codex Pooler records the mode on each request and attempt as three metadata labels: the configured mode, the effective mode, and the source. They are bounded values only — auto, lite, or full for the configured mode; lite or full for the effective mode; catalog or override for the source. No request body, prompt, tool payload, header, or credential is retained with them.
The mode a Pool is using is visible in two places:
- the Pool’s edit dialog,
Modelsstep, which shows the configured choice and the resolved effective mode per model, and - the authenticated
GET /backend-api/codex/modelsresponse, whoseuse_responses_liteboolean is the effective mode for that Pool.
Full is an advanced, provider-dependent override, and Codex Pooler never silently downgrades it. When an ordinary Responses HTTP request under an explicit Full override receives a terminal non-rate-limit 4xx, the request log classifies it as full_upstream_rejection and the client receives only a fixed server-owned error; the provider message, body, code, and param are not forwarded. A 429 is classified upstream_rate_limited and an ordinary 5xx keeps upstream_status, both of which are unrelated to the serving mode. Auto and Lite traffic produce no serving-mode-specific error code.
Serving modes are database-backed Pool configuration. There is no environment variable or Helm value that sets them.
Related References
Section titled “Related References”- Pools for the operator workflow that selects Auto, Lite, or Full
- Runtime Routes for the routes these requests are admitted on and the custom tool contract that interacts with
tool_choice - Routing Strategies for how an upstream account is chosen, which serving mode never influences
- Request logs for per-request metadata and error classification