## What changed
- Add `approvalsReviewer` to the experimental `turn/settings/update` API.
- Apply reviewer changes to subsequently captured steps and new background approval requests while preserving pending approvals and future-thread defaults.
- Allow reviewer-only updates without `step_model_switching`, while continuing to enforce managed reviewer restrictions and model-required auto review.
- Use an explicit live reviewer update for MCP approvals while retaining refreshed thread defaults for clients that have not set one.
## Testing
- Cover switching between user and automatic review during a live turn, future-turn isolation, and managed-authority rejection.
GitOrigin-RevId: e1216b09c6a7972c2c9eaf4955d8d57d60604ca1
## Why
Realtime history should be recorded consistently for every Core host, including
when no app-server event listener is attached.
## What changed
- Move transcript segmentation, session boundaries, and backing-agent artifact
promotion into Core for paginated threads.
- Persist canonical realtime items through the thread store in event order and
emit dedicated history lifecycle events for hosts to present.
- Translate those Core events into the existing app-server realtime item
notifications without app-server persisting the items a second time.
## Testing
- Cover Core-only persistence across repeated sessions, ephemeral sessions,
accepted and rejected steering, typed input ordering, and artifact promotion.
- Verify app-server notifications correspond to the persisted timeline.
GitOrigin-RevId: 7cbef14129d77f6d6d7099b733be91f5279c55f5
## What changed
- Add an in-process `sideband_base_url` override to `ConversationStartTransport::ExistingCall`, while keeping app-server requests on the configured or default endpoint.
- Prefer the per-call endpoint when attaching and reconnecting a sideband, falling back to `experimental_realtime_ws_base_url` when no override is supplied.
- Preserve runtime authentication headers on the selected endpoint without adding bearer authorization.
## Testing
- Cover configured and per-call endpoint selection, authentication headers, call-specific handshake paths, and reconnect behavior.
GitOrigin-RevId: 16c439fe0a9ff8bcdda4ec615bb9983e30935052
## What changed
- Add an optional `toolOutput` to `turn/start` for starting or steering a turn with a named function-call output instead of user input.
- Emit standalone outputs as `functionCallOutput` thread items, persist them in durable history, and include them in resumed threads.
- Validate that tool outputs have a name, are not combined with nonempty `input`, satisfy the text-size limit, and use supported image URLs.
## Testing
- Cover standalone tool-output notifications, model input, and resumed history in both legacy and paginated history modes.
- Cover the text-size limit for structured tool output.
GitOrigin-RevId: a1eeec7b9b7de67b1a1c5e829a54552b85ec118b
## Why
App-server clients need the explanation and steering instruction returned with a
misalignment policy violation to offer a user-confirmed continuation. Missing or
incomplete details must continue to behave as a terminal block.
## What changed
- Parse optional misalignment classification, explanation, and steering details
from streamed and HTTP Responses errors and propagate them through core errors.
- Include the details in live app-server `error` and `turn/completed` payloads and
export the corresponding protocol schemas and TypeScript types.
- Keep explanations and steering messages out of serialized rollout events and
redact them from debug output.
- Document how clients can resume with `turn/start` after user confirmation.
## Testing
- Cover streamed, HTTP, and WebSocket-wrapped errors, including malformed and
classification-only details.
- Verify live app-server notifications expose resumable details without writing
the explanation or steering message to the rollout.
GitOrigin-RevId: 329258a444c2cd91d0c57ab1720830b33ddcfac5
## What changed
- Add the experimental `turn/start.cyberAccessProgram` option with `standard`,
`daybreakBlue`, and `daybreakRed` values.
- Forward the selection as `access_programs.cyber` on Responses, WebSocket,
and remote-compaction requests made with ChatGPT authentication, while
omitting it for API-key and custom-provider requests.
- Preserve the per-turn selection across recovery, compaction, and child-agent
turns without making it a persistent thread setting.
## Testing
- Cover app-server forwarding, authentication boundaries, WebSocket reuse,
turn recovery, compaction, and child-agent inheritance.
GitOrigin-RevId: d2eb468f365b2214c5099bc21741cf30f8bd2eb5
## What changed
- Add an optional `turnTrigger` field to app-server `turn/start` requests and
expose it in the generated protocol schemas.
- Propagate non-empty trigger values to Responses request metadata as the
reserved `turn_trigger` field, while preserving the original value when a
request steers an active turn.
- Classify turns started by queue dispatch, goal continuation, retry recovery,
and realtime handoff.
## Testing
- Cover HTTP and WebSocket metadata forwarding, steering behavior, reserved
metadata handling, and the built-in trigger classifications.
GitOrigin-RevId: c12fe2c522286db21b76081ae6154fbf6bfb3639
## What changed
- Add the experimental `turn/settings/update` method for changing `model`,
`effort`, `summary`, and `serviceTier` on a specific live turn without
changing future turns.
- Report whether the update was applied or the target turn was unavailable,
and reject unsupported fields, disabled feature use, unsafe model changes,
and direct updates to parent-owned subagents.
- Document the method's feature requirements, null handling, and limits.
## Testing
- Cover current-turn and future-turn isolation, rejected and unavailable
targets, feature and capability gates, saved threads, and subagent ownership.
GitOrigin-RevId: 18440f6d7ee5fcd06ae23bd41b22c492b62bb2b5
## What changed
- Add `serviceTierForTurn` to `turn/start` so a newly started turn can override the service tier without changing the thread's saved tier.
- Treat `"default"` as standard speed and omitted or `null` values as inheriting the thread tier.
- Keep `serviceTier` authoritative for subsequent turns when both fields are supplied, and ignore the turn-scoped override when steering an active turn.
## Testing
- Extend the app-server turn-start test to verify both the one-turn `"default"` override and inheritance on the following turn.
GitOrigin-RevId: 4c2c4db21d8d31617a7a6e6df65bf54834e72383
## Why
Clients need a bounded view of realtime conversations that preserves the order of speech, agent work, and turn lifecycle events without loading the full thread history.
## What changed
- Persist realtime session boundaries, transcript segments, and promoted agent items in paginated thread rollouts.
- Add the experimental `thread/timeline/list` API to page ordinary items, realtime items, and turn boundaries in canonical rollout order. Include `activeRealtimeSessionAtPageStart` so each page can be rendered independently.
- Emit typed `thread/realtime/item/started`, `thread/realtime/item/transcript/delta`, and `thread/realtime/item/completed` notifications around durable realtime items.
- Keep the existing `thread/items/list` response and legacy thread history behavior unchanged.
## Testing
- Cover mixed timeline pagination, realtime-session state at page boundaries, rollout replay, subagent history prefixes, transcript streaming, steering, and promoted agent artifacts.
GitOrigin-RevId: faa2c420192d63e060e8fb32446e3c797422656e
## Why
Parent-owned Multi-Agent V2 subagents must remain controlled by their parent,
but several app-server endpoints could still mutate them directly.
## What changed
- Centralize the direct-input ownership check and apply it to turn injection,
MCP calls, reviews, compaction, rollback and revert, shell and Guardian
actions, realtime requests, and goal mutations.
- Enforce the same policy for goal mutations on unloaded threads while keeping
goal reads and turn interruption available.
- Document the affected endpoint behavior and continue exposing
`canAcceptDirectInput: false` for parent-owned subagents.
## Testing
- Cover direct-input rejection for both legacy and paginated thread history.
- Cover goal mutation ownership checks for unloaded V2 subagents, V1
subagents, and non-subagent V2 threads.
GitOrigin-RevId: 791b7af98490aed5eab4e672b90661acad11ec23
## Why
Clients that create and negotiate realtime calls themselves need Codex to join
those calls without creating another call or overwriting the client-owned
session configuration.
## What changed
- Add the `existingCall` transport to `thread/realtime/start`, accepting a
client-provided `callId` and optional `realtimeSessionId`.
- Attach Codex over the call's sideband WebSocket without SDP negotiation or a
session update, and reconnect that sideband while preserving transcript
state.
- Default existing calls to realtime v1, support v1 and v3, and reject v2 or
options that would reconfigure the client-owned session.
- Encode call IDs as a single URL path segment for v3 sideband connections.
## Testing
Add protocol, app-server, core, reconnection, authentication-header, and URL
encoding coverage for existing-call attachments.
GitOrigin-RevId: 2046b70566f98efa55381bb1e461ea3cc68256c5
## What changed
- Apply the existing direct-input restriction to `thread/settings/update` for
parent-owned Multi-Agent V2 subagents.
- Document the restriction and extend the direct-input test to verify that the
request returns an invalid-request error.
GitOrigin-RevId: 2eead01c6f54ec637c68573378f3b56a7ee85652
## Why
Environment-owned permission profiles and workspace roots must remain effective
without being persisted or restored as thread-owned settings.
## What changed
- Keep thread settings snapshots and restore data separate from the effective
configuration supplied by the primary environment.
- Use resolved environment permissions, active profiles, workspace roots, and
network policy when building turn context and session events.
- Preserve environment selections when agents are evicted, resumed, or spawned,
and validate owner configuration when it becomes ready.
- Start memory initialization only after the primary environment is configured.
## Testing
- Extend remote-environment and multi-agent tests to cover settings persistence,
owner permission profiles, workspace roots, spawning, eviction, and resume.
GitOrigin-RevId: d82f126981cd0190233020bbdd61d96c70558868
## What changed
- Track app-server turn lifecycle events for OpenAI API-key sessions when OTLP telemetry export is enabled.
- Query the turn-cost endpoint after completed or aborted turns and emit `codex.turn_cost` with the estimated USD cost, interruption state, model, speed, and reasoning effort.
- Preserve provider organization and project headers, wait for all observed responses to be priced, and retry pending or transiently failed queries with bounded tracking.
- Re-probe availability after authentication changes and shut the worker down with other app-server background tasks.
## Testing
- Cover late API-key login, transient probe failures, telemetry after thread removal, and turns with multiple responses.
- Verify turn-cost requests use API-key authentication and provider scope headers and deserialize priced response data.
GitOrigin-RevId: a303162a54798b554fcb2e9e2b372e1cf9f49f39
## What changed
- Add experimental `thread/queue/add`, `list`, `update`, `delete`, `reorder`, and `start` requests for persistent queued user submissions.
- Automatically dispatch queued submissions in FIFO order after completed or failed turns, while leaving the queue paused after interruption.
- Emit `thread/queue/changed` notifications and expose paginated queue contents with stable submission and client message IDs.
- Export the new protocol types through the generated JSON Schema and TypeScript bindings.
## Testing
- Add app-server integration coverage for capability gating, CRUD, pagination, capacity limits, notifications, automatic dispatch, cold-thread resume, interruption, and explicit queue starts.
GitOrigin-RevId: 89caf5577192199bda77cf6c372a59d26cdd910e
## What changed
- Add `TurnInputRequest` and typed submission results for atomically starting a turn, steering the active turn, or declining input with a specific reason.
- Expose `start_or_steer_turn`, `start_turn_if_idle`, and `steer_turn` on `CodexThread`, and migrate Core consumers to these APIs.
- Make app-server `turn/start` steer an active regular turn and return that turn's ID. Reject incompatible output schemas and non-steerable turns without applying settings or enqueueing input.
## Testing
- Cover concurrent start-or-steer submissions, accepted and rejected settings updates, output-schema compatibility, idle-start rejection, and app-server steering.
GitOrigin-RevId: dd9b5528d76ec650c019e97af420bc13190ea86a
## Why
App-server clients need to advertise structured MCP extension settings, including supported MCP App UI MIME types, rather than only opting into OpenAI form elicitation with a boolean.
## What changed
- Add an `extensions` map to initialize capabilities and preserve `mcpServerOpenaiFormElicitation` as a legacy alias for `openai/form`.
- Capture the declared extension profile when a thread is started, resumed, or forked, propagate it to subagents, and advertise it to downstream MCP servers during initialization.
- Keep the profile stable for the lifetime of the loaded session instead of changing it on later turns or direct tool calls.
## Testing
- Cover extension conversion, downstream MCP initialization, session isolation, legacy form support, and subagent inheritance.
GitOrigin-RevId: fbcedbb74ce788e574b0f884a4c45c4cedb9de54
## What changed
- Add the optional `delegationAckFiller` field to `thread/realtime/start`.
- Forward explicit `true` or `false` values to V3 Frameless Bidi session payloads as `delegation.ack_filler`; leave the field absent when unspecified so the Realtime API default is preserved.
- Document that V1 and V2 ignore this setting.
## Testing
- Cover both boolean values in Frameless Bidi serialization and verify the WebRTC V3 session creation payload.
GitOrigin-RevId: 9fe8efd14e42738c3e86e3e9a8af81d0f4521244
## What changed
- Add optional `realtimeStartInstructions` and `realtimeEndInstructions` fields to `thread/realtime/start`.
- Apply the instructions when Codex enters and leaves realtime mode, while preserving the existing defaults when either field is omitted.
- Limit each field to 8,192 estimated tokens.
## Testing
- Cover custom entry and exit instructions across realtime state transitions.
- Cover token-limit validation for both fields.
GitOrigin-RevId: b9cfa1ff22e084d8a6b2ed56bd27d95ffdd6b6bd
## What changed
- Send the active turn ID in the `x-codex-image-turn-id` header for image generation and edit requests.
- Add `explicit_client_interrupt_requested_at_ms` to turn analytics. Record the earliest explicit interrupt request that receives a successful response, while excluding rejected requests.
- Track errors returned by `turn/interrupt` so pending analytics state is cleaned up correctly.
## Testing
- Cover turn ID headers for image generation and editing.
- Cover accepted, retried, and rejected explicit interrupt requests in analytics tests.
GitOrigin-RevId: 80fe996a161b4952b79c5c38238f75b7661b92af
## What changed
- Wrap `CodexErrorDetails` and an optional retry delay in `CodexErr`, allowing any mapped error to preserve server-provided retry timing.
- Generate the payload-free `CodexErrKind` classification alongside the error details and reuse it for analytics.
- Update error handling sites to inspect `CodexErr::details()` while preserving existing display, debug, protocol mapping, and retryability behavior.
## Testing
- Add coverage for legacy debug formatting, error-specific retryability, and retry-delay propagation through API error mapping.
GitOrigin-RevId: d3ab8a305f2a2ee21d0c0a8c8c388b06dda9c59a
## What changed
- Route realtime V3 `bemTags` handoffs using `[ANALYSIS]`, `[COMMENTARY]`, and `[FINAL]` prefixes by default.
- Add `codexResponseHandoffChannelPrefixes` to realtime start requests so clients can replace the accepted prefixes for individual channels. Each channel may accept multiple prefixes, and omitted channels retain their defaults.
- Apply the configured prefixes to both complete and streamed handoff messages, without treating empty prefixes as matches.
## Testing
- Cover request deserialization, default and custom routing, partial streamed prefixes, empty-prefix handling, and end-to-end V3 handoffs.
GitOrigin-RevId: d0aef50aa71362a5beef2c6f6d28b8633da4e529
## Why
The first session metadata record defines a rollout's history mode. If that
record uses an unsupported mode, skipping it as a parse error can cause later
metadata copied from fork history to be treated as canonical.
## What changed
- Validate session metadata history modes before deserializing rollout lines,
while continuing to tolerate unknown modes in copied fork history.
- Simplify thread status registration to pass only the thread ID instead of a
fully materialized `Thread`.
GitOrigin-RevId: 9de4313d4a76b279d32bb49b2016a4f5d07507c1
## What changed
- Add an optional `initialItems` field to `thread/realtime/start` for seeding
realtime V3 session history with `user`, `developer`, and `assistant` text.
- Encode the items in Frameless Bidi session bootstrap payloads for websocket
and WebRTC transports while preserving existing payloads when the list is
omitted or empty.
- Reject non-empty initial items for V1 and V2, more than 128 items, any item
over 8,192 estimated tokens, or a combined total over 8,192 estimated tokens.
## Testing
- Cover protocol serialization, app-server forwarding, websocket and WebRTC
payloads, empty-list compatibility, version checks, and size limits.
GitOrigin-RevId: 09af367f9a07543e3f8d59b7fa5387f932dc5a6a
## What changed
- Add `codexResponseHandoffMode` to `thread/realtime/start` for V3 sessions, with `thinking` as the default and `commentary` and `bemTags` routing modes.
- Map BEM `analysis` and `commentary` output to the commentary channel and `final` output to the speakable channel, while preserving the original BEM envelope. Unrecognized output falls back to speakable.
- Mark explicit speech appends as speakable and stop adding the legacy `"Agent Final Message"` prefix to V3 handoffs. V1 and V2 continue to ignore the new setting.
## Testing
- Cover channel selection for every routing mode, streamed BEM header parsing, fallback behavior, explicit speech, and V1 compatibility.
GitOrigin-RevId: 1974578257488f2914b252c9c1990ba38831c96b
## What changed
- Stream V3 agent message deltas to `delegation.context.append` at 200 ms intervals instead of waiting for each completed message.
- Send commentary without a prefix and label final or phase-less V1/V3 output with `"Agent Final Message":`.
- Bound streamed output while preserving both its beginning and final tail when truncation is required.
- Remove `codexResponseHandoffPrefix` from `thread/realtime/start`; unknown fields from older clients remain ignored.
## Testing
- Cover incremental V3 commentary and final output, Unicode-safe truncation, V1/V3 phase labeling, and legacy request compatibility.
GitOrigin-RevId: 633c18b5eb9a4f5756a264b63b7bfc185b073951
## Why
Multi-agent V2 spawned threads are controlled by their parent and reject direct
app-server input. The TUI should expose that ownership instead of presenting an
input flow that the server will reject.
## What changed
- Add the experimental `Thread.canAcceptDirectInput` capability for loaded
threads and populate it from the same policy used to validate `turn/start`
and `turn/steer`. Stored, unloaded threads report `null`.
- Carry the capability through TUI thread discovery, selection, startup, and
resume flows, with a compatibility fallback for older app servers.
- Present parent-owned threads as view-only, preserve drafts and queued input,
block agent-directed commands and settings shortcuts, and keep local
navigation and inspection commands available.
## Testing
- Cover V1 writable and V2 view-only spawned threads through selection and
resume.
- Cover composer submission paths, queued and initial input preservation,
allowed navigation commands, settings shortcuts, and the view-only UI.
GitOrigin-RevId: 495ad051249e5577fb0b0c1cc8c40a0998d04918
## What changed
- Allow `thread/start` to select experimental `historyMode: "paginated"` when the thread store supports turn and item pagination.
- Resume paginated threads with bounded model context and require `excludeTurns: true` instead of loading full history.
- Return `turnsBackwardsCursor` and `itemsBackwardsCursor` from `thread/resume` so clients can hydrate durable history through `thread/turns/list` and `thread/items/list` while newer records arrive through live notifications.
- Reject operations that still require full history, including `initialTurnsPage`, `thread/read` with `includeTurns: true`, `thread/rollback`, and detached review.
- Make item cursors independent of the optional `turnId` filter.
## Testing
- Cover paginated thread start, cold and live resume, backwards cursor hydration, metadata reads, unsupported operations, and stores without pagination support.
GitOrigin-RevId: c42f9b268e29d049843330f474fa3ecde12f5b8a
## Why
Detached reviews should behave like ordinary forked turns so clients get the normal steering, tool, permission, and item-stream behavior.
## What changed
- Add a bundled `$review-agent` skill with read-only, defect-first review guidance.
- Start detached reviews through `AgentRunner` with a target-specific prompt that explicitly references the bundled skill. Keep inline reviews on the existing review-mode flow.
- Continue emitting the detached review thread and turn identifiers through the app-server API.
## Testing
- Update the detached review integration test to verify the forked turn prompt, ordinary turn completion, and selection of the bundled skill when a user skill has the same name.
GitOrigin-RevId: 979664cd80800fb0d51eabdc4e3211da90c12db9
## Why
Multi-environment turns can run against different filesystems, so a single
thread-wide workspace-root list cannot accurately describe every selected
environment.
## What changed
- Add optional `runtimeWorkspaceRoots` to each app-server environment selection.
Omitted roots default to that environment's `cwd`, while an empty list selects
no roots.
- Carry environment-native roots through turn selection, model-visible context,
permission materialization, and tool sandboxing.
- Keep the top-level `runtimeWorkspaceRoots` field as a compatibility input when
constructing default environment selections; explicit selections use their
own roots.
## Testing
- Cover thread and turn API defaults, overrides, empty roots, and schema
round-tripping.
- Verify foreign roots remain model-visible and remote execution applies the
target environment's roots before sandbox selection.
GitOrigin-RevId: c6e7a62883c45c676c153055f46a55ee15a323a4
## What changed
- Pass the parent turn's effective permission profile to the memory consolidation agent, including thread-level permission and legacy sandbox overrides.
- Preserve disabled and externally enforced permission profiles instead of replacing them with a managed sandbox.
- Continue restricting consolidation to the memory root without network access when the parent uses Codex-managed permissions.
## Testing
- Add coverage for disabled, external, and managed parent permission profiles.
GitOrigin-RevId: 9ca3be0e41dc14d858053f6f70e33f4ae7c578e1
## Why
A realtime session can end after more transcript has accumulated than
was included in its last handoff. That tail already lives in core's
active transcript state, but the stop path aborted the realtime
input/fanout tasks before routing it, so the final bit of the
conversation could disappear before `thread/realtime/closed`.
This behavior is still being evaluated, so clients must opt in per
realtime session. Omitted or false leaves shutdown behavior unchanged.
## What changed
- Add optional `flushTranscriptTailOnSessionEnd` to
`thread/realtime/start`, defaulting to false in app-server.
- Expose an idempotent `take_transcript_tail()` from the existing active
transcript state using `last_handoff_entry_count`.
- When enabled, let shutdown cancel the input owner cleanly and publish
at most one final existing `<realtime_delegation>` with the remaining
text in `<transcript_delta>`.
- Have the existing fanout drain already-parsed events before routing
that final delegation, so a queued handoff wins first and is not
duplicated in the tail.
- Flush realtime shutdown before ordinary session task abort during core
cleanup.
## Validation
- `just test -p codex-app-server-protocol`
- `just test -p codex-app-server
realtime_conversation_stop_emits_closed_notification`
- `just test -p codex-core conversation_transport_close_`
- `just test -p codex-core
conversation_close_routes_only_remaining_transcript_tail_once`
- scoped `just fix` for `codex-app-server-protocol`, `codex-app-server`,
`codex-protocol`, and `codex-core`
- `just fmt`
## Why
Ultra should be one user-facing reasoning selection for work that
benefits from both maximum reasoning and proactive multi-agent
delegation. Without it, clients must coordinate maximum reasoning with
the experimental `multiAgentMode` setting, even though the inference
backend still expects its existing `max` effort value.
This change makes reasoning effort the source of truth: clients select
`ultra`, core derives proactive multi-agent behavior when the turn is
eligible for multi-agent V2, and inference requests continue to use the
backend-compatible `max` value.
## What changed
- Add `ultra` as a first-class reasoning effort and preserve
model-catalog ordering when exposing it to clients.
- Convert `ultra` to `max` at the inference request boundary, including
Responses HTTP/WebSocket requests, startup prewarm, compaction, and
memory summarization.
- Derive effective multi-agent mode per turn from effective reasoning
effort:
- eligible multi-agent V2 + `ultra` → `proactive`
- eligible multi-agent V2 + any other effort → `explicitRequestOnly`
- V1 or otherwise ineligible sessions → no multi-agent mode instruction
- Keep the derived effective mode in turn context history so successive
turns can emit a developer-message update only when the effective mode
changes.
- Remove selected multi-agent mode from core session configuration, turn
construction, thread settings, resume/fork restoration, and subagent
spawn plumbing. Subagents inherit reasoning effort and derive their own
effective mode.
- Retain the experimental app-server `multiAgentMode` fields for wire
compatibility while marking them deprecated. Request values are accepted
but ignored; compatibility response fields report `explicitRequestOnly`.
- Display Ultra in the TUI using the order supplied by `model/list`.
## Validation
- `just test -p codex-core ultra_reasoning_uses_max_for_requests`
- `just test -p codex-tui model_reasoning_selection_popup`
## Summary
Resuming a persisted thread currently deep-clones its complete rollout
history several times. `InitialHistory` is retained for the app-server
response, copied into thread persistence, and copied again by read-only
accessors. These copies scale with the complete rollout rather than the
bounded model context and add measurable latency for large sessions.
This change stores resumed rollout history in `Arc<Vec<RolloutItem>>`.
Rollout loading wraps the parsed vector once, while app-server response
construction, session initialization, and thread persistence share it
through inexpensive `Arc` clones. Read-only history access now returns a
borrowed slice, and fork paths use `Arc::unwrap_or_clone` where they
genuinely need mutable ownership. Rollout reconstruction also consumes
its temporary context instead of cloning the reconstructed model
history.
The serialized representation remains unchanged. In an artificial 123 MB
rollout benchmark, sharing resumed history reduced cold resume latency
by roughly 9–10%. The affected crates compile with their test targets,
all 80 thread-store tests pass, and the Bazel dependency lock remains
valid.
## Why
`ResponseItem::AdditionalTools` was added without updating app-server
image URL validation. The exhaustive match therefore prevents app-server
and downstream targets from compiling on `main`.
## What changed
Treat `AdditionalTools` like the other response items that cannot
contain input-image URLs.
## Stack
Stacked on #29417. Review and land that PR first.
## Summary
- reject HTTP(S) image URLs in the handlers for `turn/start` and
`turn/steer`
- validate `thread/inject_items` after its existing
JSON-to-`ResponseItem` conversion, so each item is deserialized once
- turn invalid dynamic-tool image responses into the existing
unsuccessful text fallback; the model receives the validation message as
the function output
- leave `thread/resume.history` compatible with legacy history; #29417
replaces remote images before model input
- continue accepting inline data URLs and `localImage` inputs
- keep this policy in app-server; this PR does not add a shared protocol
API or change core image preparation
## Test plan
- `just test -p codex-app-server -E
'test(/request_handlers_reject_remote_image_urls|dynamic_tool_remote_image_response_becomes_model_visible_error|dynamic_tool_call_round_trip_sends_content_items_to_model|turn_start_tracks_turn_event_analytics|standalone_image_edit_uses_recent_pathless_image/)'`
(5 passed)
- `just fix -p codex-app-server`
- `just fmt`
## Why
Once multi-agent mode can be selected per turn, clients also need to
choose the initial selection when creating a thread and observe that
selection through lifecycle and settings APIs.
The selected value is intentionally distinct from the effective
model-visible value: no client selection is represented as `null`, even
though an eligible multi-agent v2 turn derives `explicitRequestOnly` as
its effective default.
## What changed
- Add the optional experimental `thread/start.multiAgentMode` parameter
and pass it through thread creation.
- Preserve an omitted initial value as an unset selection rather than
eagerly storing `explicitRequestOnly`.
- Apply an explicit `thread/start` selection to the first turn through
the session configuration established at thread creation.
- Restore the latest persisted effective mode as the selected baseline
on cold resume when rollout history contains one.
- Inherit the optional selected mode from a loaded parent when creating
related runtime threads.
- Return the current selected `multiAgentMode` from `thread/start`,
`thread/resume`, `thread/fork`, and thread settings, using `null` when
no mode is selected.
- Keep lifecycle reporting independent from model capability and feature
eligibility; core turn construction remains responsible for calculating
and persisting the effective mode.
## Not covered
- Clearing an existing loaded-session selection back to unset through
`turn/start`; omitted or `null` currently retains the session's
selection.
- A TUI control, slash command, or `config.toml` preference.
## Verification
- `CARGO_INCREMENTAL=0 just test -p codex-app-server-protocol`
- `CARGO_INCREMENTAL=0 just test -p codex-app-server multi_agent_mode`
The focused app-server coverage verifies explicit `thread/start`
initialization, first-turn prompting, nullable reporting for an omitted
selection, and retention of selections that are not currently
runtime-eligible.
## Stack
Stacked on #28685. This PR contains only the thread initialization and
lifecycle/settings API layer.
## Why
Multi-agent v2 currently carries an explicit-request-only delegation
rule in its static usage hint. That provides a safe default, but it
prevents clients from selecting proactive delegation per turn without
changing static guidance or rewriting prior model context.
This change makes delegation mode a session selection that can be
updated through `turn/start`, while deriving the effective model-visible
mode separately for each turn. Eligible multi-agent v2 turns remain
explicit-request-only unless proactive mode is both selected and
enabled.
## What changed
- Add the experimental `turn/start.multiAgentMode` parameter with
`explicitRequestOnly` and `proactive` values. Omission retains the
loaded session's current optional selection.
- Add the default-off `features.multi_agent_mode` feature gate. Eligible
multi-agent v2 turns use the selected mode when enabled; an unset
selection or disabled gate resolves to `explicitRequestOnly`.
- Treat mode prompting as inapplicable for multi-agent v1 and other
unsupported session configurations, producing no multi-agent mode
developer message rather than rejecting the turn.
- Move the explicit-request-only rule out of the static v2 usage hint
and into a bounded, tagged developer context fragment.
- Emit the effective mode in initial context and only when that
effective mode changes on later turns.
- Persist the effective mode in `TurnContextItem` as the durable
baseline for resume and context-update comparisons.
Historical rollout items are not rewritten. Later mode developer
messages establish the current rule incrementally.
## Not covered
- Initial selection through `thread/start` and selected-mode reporting
from thread lifecycle/settings APIs; those are isolated in the stacked
#28792.
- A TUI control or slash command for selecting the mode.
- Persisting a preferred mode to `config.toml`; selection remains
session/turn scoped.
- Changes to multi-agent concurrency limits, tool availability, or model
catalog capability declarations.
- Rewriting historical rollout prompt items. Cold resume restores the
latest persisted effective mode when available while leaving historical
developer messages intact.
## Verification
- `CARGO_INCREMENTAL=0 just test -p codex-core multi_agent_mode`
- Focused app-server coverage verifies that `turn/start.multiAgentMode`
produces proactive developer instructions for an eligible v2 turn.
## Stack
Followed by #28792, which adds `thread/start` initialization and
lifecycle/settings observability.
## Summary
- Remove the realtime `architecture` selector from core protocol,
app-server protocol, config parsing, generated schemas, and callers.
- Always create WebRTC realtime calls with the AVAS query params:
`intent=quicksilver&architecture=avas`.
- Keep direct websocket realtime behavior on the existing config/default
path, while WebRTC starts without an explicit version now default to
realtime v1 because AVAS requires v1.
## Notes
- WebRTC realtime now means AVAS. If a caller explicitly asks to start
WebRTC with realtime v2, Codex rejects that request because the AVAS
WebRTC path only supports realtime v1. Websocket realtime is separate
and can still use realtime v2.
- The old `[realtime] architecture = "realtimeapi" | "avas"` config knob
is removed. Local configs that still set it will need to delete that
line.
- Some app-server tests that were only trying to exercise realtime v2
protocol behavior now use websocket transport, because WebRTC is
intentionally locked to AVAS/v1. Separate WebRTC tests cover the AVAS
query params, v1 startup, SDP flow, and sideband join.
## Validation
- Merged fresh `origin/main` at `83e6a786a2`.
- `just fmt`
- `just write-config-schema`
- `just write-app-server-schema`
- `git diff --check`
- `just test -p codex-api -p codex-core -p codex-app-server-protocol -p
codex-app-server realtime` (176 passed)
- `just test -p codex-protocol -p codex-config` (413 passed)
## What
Built on the realtime speech-control plumbing merged in #27917.
- Add optional `codexResponseHandoffPrefix` to `thread/realtime/start`.
- Apply that prefix only to automatic V1 commentary sent through
`conversation.handoff.append`; final answers remain unprefixed.
- Add opt-in `clientManagedHandoffs`. When true, core suppresses
automatic response handoffs and completion output so delivery is
controlled by explicit client append APIs.
- Preserve existing automatic behavior by default.
`codexResponsesAsItems: true` continues to select item routing when
client-managed mode is disabled.
## Why
Voice clients need two delivery policies: automatic background context
with silent commentary instructions and fully client-owned handoffs.
Phase-aware prefixing keeps routine commentary silent without
suppressing the final answer, while client-managed mode lets an app
decide exactly which updates to append.
## Validation
- `just fmt`
- `cargo test -p codex-app-server-protocol
serialize_thread_realtime_start`
- `RUST_MIN_STACK=16777216 cargo test -p codex-core --test all
conversation_handoff_persists_across_item_done_until_turn_complete`
- `RUST_MIN_STACK=16777216 cargo test -p codex-app-server --test all
webrtc_v1_client_managed_handoffs_disable_automatic_output`
- `RUST_MIN_STACK=16777216 cargo test -p codex-app-server --test all
webrtc_v1_final_automatic_handoff_omits_silent_prefix`
- `cargo build -p codex-cli --bin codex`
- Local Codex Apps compatibility check: 43 focused webview tests passed,
and a live voice session routed through the source-built app-server.
The explicit `RUST_MIN_STACK` avoids a macOS Tokio test-worker stack
overflow seen with the default test environment.
## Why
app-server may run on a different OS from the selected exec-server
environment. Parsing that environment’s cwd with the Codex host’s path
rules prevents thread startup.
## What
Carry environment cwd values as `LegacyAppPathString` at the app-server
boundary and `PathUri` internally. Existing tool-call schemas and
relative-path behavior stay host-native; remaining local-only consumers
convert explicitly and leave follow-up TODOs.
The Wine integration test verifies app-server can start a thread and
complete an ordinary turn with a Windows environment cwd from Linux.
## Validation
- `bazel test //codex-rs/core/tests/remote_env_windows:smoke-test
--test_output=errors`
- focused app-server environment-selection and protocol schema tests
- scoped Clippy for `codex-core` and `codex-app-server-protocol`
## Summary
- Add `includeStartupContext` to realtime start requests so callers can
explicitly skip Codex startup context while keeping the backend prompt
- Thread the new flag through protocol types, request processing, and
realtime session config
- Update app-server docs and coverage for the new default and opt-out
behavior
## Testing
- Added protocol serialization coverage for `includeStartupContext`
- Added realtime integration coverage for starting a session with
startup context disabled
## Why
Realtime voice harness tuning needs app-side control over what backend
Codex text is spoken. Backend orchestrator text is written for a reading
UI, so automatically speaking every preamble, progress update, or final
assistant message can make the realtime voice model too chatty.
For experimentation, clients need two simple controls: keep app/client
text-item injection on the existing item-create path, and add an
explicit speakable path that app code can call only when it wants
realtime to speak. Automatic Codex output also needs an opt-in way to
switch from the protocol's default speakable path to regular realtime
items, with a caller-provided prefix so prompt wording can be tuned
outside core.
The default remains unchanged: if a client omits the new start fields
and never calls `appendSpeech`, automatic backend output continues down
the existing speakable path for the selected realtime protocol.
## What Changed
- Adds experimental `thread/realtime/appendSpeech` for app-provided
speakable text.
- Keeps existing `thread/realtime/appendText` as the item-create API for
app-provided realtime text items.
- Adds `codexResponsesAsItems` / `codex_responses_as_items` on
`thread/realtime/start` to send automatic Codex responses with
`conversation.item.create` instead of the protocol's default speakable
output path.
- Adds `codexResponseItemPrefix` / `codex_response_item_prefix` so
clients can prepend experiment instructions to those automatic Codex
response items.
- Keeps literal `conversation.handoff.append` routing scoped to the v1
speakable path; v2 default speech uses its item/function-output plus
`response.create` behavior.
- Removes the earlier public silent-context API and hardcoded
silent-context prefix.
- Updates realtime tests to cover default automatic speakable behavior,
opt-in automatic item-create behavior, and explicit `appendSpeech`
behavior.
## Validation
- `cargo check -p codex-core -p codex-app-server -p codex-api`
- `just test -p codex-app-server realtime_conversation`
- `just test -p codex-core realtime_conversation` (50/51 passed in the
filtered parallel run; the lone failure passed when rerun in isolation)
- `just test -p codex-core
conversation_mirrors_assistant_message_text_to_realtime_handoff`
- `just test -p codex-api
e2e_connect_and_exchange_events_against_mock_ws_server`
- `just fix -p codex-core`
- `just fix -p codex-app-server`
- `cargo build -p codex-cli`
## Why
`TurnEnvironmentSelections::new` rewrote the primary environment's
explicit `cwd` to the legacy fallback cwd. For a remote-first selection,
this could replace the remote working directory with a local fallback
path and made the legacy cwd overlay authoritative over
environment-owned state.
## What changed
- Preserve every explicit environment cwd when constructing turn
environment selections.
- Keep `cwd`-only app-server updates compatible by rebuilding the
default environment selections at the requested cwd.
- Cover both explicit primary cwd preservation and cwd-only updates
reaching the model-visible execution environment.
## Testing
- `just test -p codex-core
session_update_settings_does_not_rewrite_sticky_environment_cwds`
- `just test -p codex-core
environment_settings_preserve_explicit_primary_cwd`
- `just test -p codex-app-server
thread_settings_update_cwd_retargets_default_environment`
## Why
Next slice needed to make progress on the `remote_env_windows` test is
to support passing a Windows cwd for the remote environment and using
that environment's native shell. This lets the test run a real Windows
process instead of only recording an early path or shell mismatch.
## What
- change `TurnEnvironmentSelection.cwd` from `AbsolutePathBuf` to
`PathUri`
- convert local cwd values to URIs when constructing selections
- preserve a remote primary cwd instead of replacing it with the local
legacy fallback
- prefer the selected environment's discovered shell for unified exec,
falling back to the session shell when unavailable
- convert back to a host-native absolute path at current native-only
consumer boundaries
- reject or deny unsupported foreign cwd values at the existing
request-permissions boundary, with TODOs for its future migration
- extend the hermetic Wine test to execute Windows PowerShell in
`C:\windows` and verify successful process completion
- record the current app-server rejection against the same Wine-backed
remote Windows fixture when its cwd is supplied as a native Windows path
## Summary
Add an explicit `user` or `developer` role to
`thread/realtime/appendText` and propagate it through the realtime input
queue into `conversation.item.create`. Older JSON clients that omit the
field continue to default to `user`.
This lets app-provided context such as memory retain developer authority
without bypassing app-server through a renderer-owned data channel. The
app-server schemas, API documentation, and focused protocol and
websocket coverage are updated with the new contract.
The Codex Apps consumer is tracked in
[openai/openai#1025261](https://github.com/openai/openai/pull/1025261).
## Summary
Adds a `RealtimeConversationArchitecture` option for realtime
conversation startup, with `realtimeapi` as the default and `avas` as an
opt-in architecture.
The AVAS path is limited to realtime v1 conversational WebRTC starts,
and WebRTC call creation appends `intent=quicksilver&architecture=avas`
to `/v1/realtime/calls`. The existing sideband websocket still joins by
`call_id`.
This also exposes the per-session architecture override through
app-server v2 `thread/realtime/start` params and updates the config
schema for `[realtime].architecture`.
## Validation
- `just fmt`
- `just write-config-schema`
- `just test -p codex-api sends_avas_session_call_query_params`
- `just test -p codex-core -E
'test(~conversation_webrtc_start_uses_avas_architecture_query)'`
- `just test -p codex-core -E 'test(realtime_loads_from_config_toml)'`
- `just test -p codex-app-server-protocol -E
'test(~serialize_thread_realtime_start) |
test(generated_ts_optional_nullable_fields_only_in_params)'`
- `just test -p codex-app-server -E
'test(realtime_webrtc_start_emits_sdp_notification)'`
## Why
Turn construction passed resolved environments through several layers
while leaving the environment shell unresolved. As a result,
model-visible environment context could fall back to the session shell
instead of reporting the selected remote environment's shell.
Resolve environment metadata at the turn-context boundary so each turn
carries the shell that belongs to its selected environment. Keep request
validation in app-server, where invalid selections can be returned as
straightforward JSON-RPC errors without coupling core turn construction
to that policy.
## What changed
- resolve environment selections eagerly in
`new_turn_context_from_configuration`
- store the full resolved `Shell` on each `TurnEnvironment`
- simplify the now-redundant resolved-environment constructor plumbing
- keep duplicate and unknown-environment validation as a small
app-server preflight
- add a remote-environment integration test that runs a full
`test_codex` turn and verifies the model-visible environment message
reports `bash`
## Testing
- `cargo check -p codex-core --test all -p codex-app-server`
- `remote_test_env_exposes_bash_shell_to_model` on the Linux
remote-executor harness