Responses Lite turns custom base-instruction overrides into additive developer messages

Open 💬 2 comments Opened Aug 13, 2026 by Krual-T

What issue are you seeing?

A custom base-instruction override supplied through model_instructions_file replaces the model's bundled Codex base instructions in standard Responses mode, but becomes additive in Responses Lite.

With Responses Lite enabled, the model sees both:

  • the service-provided Codex base identity (beginning with You are Codex), and
  • the custom base instructions as a later role: developer input message.

This changes the documented/internal meaning of base_instructions from a replacement to an additional developer message.

The behavior is present in Codex 0.147.0 and is still present on current main at 95aada11c.

Steps to reproduce

  1. Create a custom model instructions file:
You are CustomAgent. Do not identify as Codex.
  1. Configure it for a Responses-Lite-enabled model such as gpt-5.6-sol:
model = "gpt-5.6-sol"
model_instructions_file = "/absolute/path/to/custom-instructions.md"
  1. Start a fresh thread and ask a literal presence probe:
Do your system or developer instructions contain any content beginning with
"You are Codex"? Reply with exactly YES or NO.
  1. Compare the outbound request with Responses Lite enabled and disabled.

Observed A/B, with request data redacted:

Responses Lite enabled

X-OpenAI-Internal-Codex-Responses-Lite: true
top-level instructions: omitted
input[0]: additional_tools, role=developer
input[1]: custom base instructions, role=developer
probe result: YES

Responses Lite disabled

Responses-Lite header: absent
top-level instructions: custom base instructions
request body contains no "You are Codex" text
probe result: NO

The same custom file and model are used in both cases. Only use_responses_lite changes.

Expected behavior

A custom model_instructions_file should preserve its base-instruction replacement semantics regardless of transport mode.

Either:

  1. Responses Lite should provide a way to replace the service default base instructions, or
  2. Codex should automatically use standard Responses transport when the effective base instructions are custom.

Actual behavior and source analysis

Config loading works correctly:

  • model_instructions_file is read into Config.base_instructions.
  • Session initialization resolves base instructions in this order:
  1. configured override,
  2. persisted session instructions,
  3. model template.

The semantic change happens in codex-rs/core/src/client.rs::build_responses_request().

For standard Responses, Codex sends:

instructions = prompt.base_instructions.text

For Responses Lite, Codex instead:

  1. sets top-level instructions to an empty string,
  2. inserts additional_tools as a developer item,
  3. inserts prompt.base_instructions.text as an ordinary developer message.

This behavior was introduced by #27946, whose stated goal was to use input items instead of top-level tools and instructions. That conversion is not semantically one-to-one when the source is a custom base-instruction override.

The existing test responses_lite_uses_input_items_for_instructions_and_tools verifies that:

  • top-level instructions is absent, and
  • the text appears as a developer input item.

It does not verify that a custom base-instruction override still replaces the service default identity.

Current main already tracks:

BaseInstructionsProvenance::Custom
BaseInstructionsProvenance::Model { ... }

but the Responses Lite request path does not use that provenance when selecting the transport contract.

Suggested fix

Complete fix

Preserve a distinct base-instruction replacement channel in the Responses Lite protocol/service, rather than representing a replacement as an additive developer message.

In my A/B tests, retaining the Lite marker while restoring top-level instructions did not produce a successful response, so this may require backend coordination.

Safe client-side fallback

Until Lite supports replacement semantics, derive one effective transport mode before tool planning and request construction:

effective_responses_lite =
    model_info.use_responses_lite
    && base_instructions.provenance != Some(BaseInstructionsProvenance::Custom);

The effective value should be applied consistently to:

  • HTTP and WebSocket headers,
  • instruction and tool placement,
  • tool planning,
  • reasoning context,
  • parallel tool calls,
  • image preparation,
  • compact requests.

This would avoid requiring users to copy and maintain a complete model_catalog_json solely to set use_responses_lite = false.

Additional information

The current workaround is a full model catalog override with use_responses_lite = false for the affected model. That is brittle because model_catalog_json is a complete catalog rather than a partial metadata override.

A separate Desktop/app-server bug can prevent a project-local catalog workaround from applying at all: #26308. I will add the more specific source analysis for that issue there.

View original on GitHub ↗

2 Comments

Krual-T · 15 days ago

I'm happy to implement the client-side fallback and regression tests if this approach aligns with the maintainers' intended solution and you explicitly invite a PR.

jdcodes1 · 10 days ago

Confirmed on main @ 1f41cc5d92 — the semantic change is visible in one branch:

https://github.com/openai/codex/blob/1f41cc5d92722748e45cae9cecc6d883a4e7cbb1/codex-rs/core/src/client.rs#L867-L896

Standard mode puts prompt.base_instructions into the request's top-level instructions field — which replaces any service default. The Lite branch instead sends instructions: String::new() and moves the base-instruction text into a role: "developer" input message prefixed to the conversation. With the instructions field empty, the service applies its own Codex identity, and your override rides along as an additional developer message — replacement silently becomes additive, exactly as you measured.

Fix depends on what the Lite endpoint accepts: if it honors a non-empty instructions field, the branch should populate it with the override (keeping the developer-message scheme only for the bundled default). If Lite structurally cannot replace the service identity, then model_instructions_file should either be rejected for Lite models or the docs updated to state it's additive there — the silent semantic downgrade is the actual bug either way.