openai / openai/codex

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

Open
#38,355 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug CLI config
Dominant language
Rust
Stars
125k
Forks
19.5k
PR merge metrics
PR metrics pending

Description

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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in codex-rs/core/src/client.rs::build_responses_request() and inspect the existing test responses_lite_uses_input_items_for_instructions_and_tools. Trace BaseInstructionsProvenance::Custom through request construction and the listed transport and planning paths. Done means custom model instructions retain replacement semantics with Responses Lite, with coverage for the custom override and the existing Lite behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
api, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.