Commit Graph

409 Commits

  • [codex] simplify token budget context (#29295)
    ## Why
    
    The token-budget feature currently adds remaining-token messages
    whenever usage crosses the 25%, 50%, and 75% thresholds. Those periodic
    inserts create prompt churn without requiring action, while the
    near-compaction reminder and explicit `get_context_remaining` tool
    already cover actionable and on-demand budget information.
    
    The context-window lineage block is also easier to scan as plain labeled
    text than as a `<token_budget>`-wrapped fragment.
    
    ## What changed
    
    - Stop recording automatic remaining-token messages at percentage
    thresholds.
    - Render context-window lineage in `First`, `Current`, `Previous` order
    with colon-separated labels.
    - Omit the `Previous` line for the first context window.
    - Remove `<token_budget>` wrappers from newly rendered lineage,
    near-compaction reminders, and `get_context_remaining` output.
    - Keep recognizing legacy wrapped fragments so existing rollouts remain
    compatible.
    - Remove the post-sampling token snapshot that was only needed by the
    periodic threshold path.
    
    ## Testing
    
    - `just test -p codex-core token_budget` (11 tests passed)
  • [codex] add configurable token budget compaction reminder (#29255)
    ## Why
    
    The token-budget feature reports coarse remaining-context milestones,
    but it does not give the model a configurable wrap-up prompt before
    automatic compaction. A strict threshold-crossing check can also miss
    resumed or reconfigured windows that are already inside the threshold.
    
    ## What changed
    
    - Add structured `[features.token_budget]` configuration for an absolute
    `reminder_threshold_tokens` and bounded `reminder_message_template`;
    `{n_remaining}` is expanded when the reminder is delivered.
    - Compute remaining tokens against the next effective auto-compaction
    boundary, including scoped `body_after_prefix` accounting and the full
    context-window limit.
    - Make reminder delivery level-triggered before and after sampling, with
    one-shot state owned by `AutoCompactWindow` and re-armed on compaction,
    `new_context`, restore, or history replacement.
    - Leave the existing initial full-window token-budget context, 25/50/75%
    notices, and token-budget tools unchanged.
    - Persist the resolved feature configuration in the session config lock
    and regenerate the config schema.
    
    ## Validation
    
    - `just test -p codex-core token_budget`
    - `just test -p codex-core
    token_budget_reminder_emits_after_crossing_compaction_threshold`
    - `just test -p codex-core auto_compact_window`
    - `just test -p codex-core
    lock_contains_prompts_and_materializes_features`
    - `just test -p codex-features`
    - `just test -p codex-config`
  • [codex] prototype mcp_history thread hint injection (#29259)
    ## Why
    
    Prototype whether the harness can invoke the `mcp_history` MCP while
    constructing full initial context and expose its thread hint to the
    model without requiring a model-issued tool call.
    
    The prototype builds on the context-window lineage added by #29256 and
    is now based directly on `main`.
    
    ## What changed
    
    - Call `mcp_history/thread_hint` with no arguments while building the
    full `<token_budget>` context.
    - Pass the current `threadId` through MCP request metadata, matching the
    normal MCP tool-call path.
    - Serialize only the unstructured `content` result and append it inside
    `<token_budget>` when the call succeeds.
    - Omit the additional context when the MCP call or content serialization
    fails.
    
    ## Prototype limitations
    
    - The direct call bypasses the normal model-initiated MCP approval,
    lifecycle-event, telemetry, and result-sanitization path.
    - The call has no prototype-specific timeout, result-size cap, or
    per-window cache.
    - MCP latency is added to full-context construction, including
    applicable compaction paths.
    
    ## Validation
    
    - `just test -p codex-core token_budget`
  • core: add context window lineage IDs (#29256)
    ## Why
    
    The rendered `<token_budget>` fragment identifies the thread and current
    context window, but it does not expose enough lineage to identify the
    first window in the thread or the immediately preceding window. Those
    IDs also need to remain stable across compaction, resume, and rollback.
    
    ## What changed
    
    - Track first, previous, and current UUIDv7 context-window IDs in
    auto-compaction state.
    - Render `thread_id`, `first_window_id`, `previous_window_id`, and the
    current window ID in the full `<token_budget>` fragment.
    - Persist the first and previous window IDs in compacted rollout
    checkpoints and restore them during rollout reconstruction.
    - Preserve compatibility with older compacted records that do not
    contain the new optional fields.
    - Update focused state, rendering, reconstruction, rollback, and
    serialization coverage.
    
    ## Validation
    
    - `just test -p codex-core token_budget`
    - `just test -p codex-protocol compacted_item::tests`
    - `just test -p codex-core tracks_prefill_and_window_boundaries`
    - `just test -p codex-core
    reconstruct_history_uses_replacement_history_verbatim`
    - `just test -p codex-core
    thread_rollback_restores_cleared_reference_context_item_after_compaction`
  • Add config toggles for orchestrator skills and MCP (#28942)
    ## Why
    
    Orchestrator-provided skills and Codex Apps MCP tools add model-visible
    instructions, resources, and tools beyond the local workspace. Hosts
    need config-level switches to disable those orchestrator-owned surfaces
    independently, without disabling regular skills or regular MCP servers.
    
    ## What changed
    
    - Adds `[orchestrator.skills].enabled` and `[orchestrator.mcp].enabled`
    config entries, both defaulting to `true`.
    - Includes the new settings in `config.schema.json` and in the config
    lock so resolved thread configuration preserves the same orchestrator
    exposure decisions.
    - Threads `orchestrator.skills.enabled` through the app-server skills
    extension so disabled orchestrator skills do not expose the `skills`
    namespace or inject orchestrator skill context.
    - Gates Codex Apps MCP exposure, app instructions, and app auth
    eligibility on `orchestrator.mcp.enabled` while leaving non-Codex-Apps
    MCP tools available.
    - Updates the thread-manager sample config to disable both
    orchestrator-owned surfaces.
    
    ## Verification
    
    - Added config parsing, loading, defaulting, and schema coverage for the
    new settings.
    - Added MCP exposure coverage that `orchestrator.mcp.enabled = false`
    removes Codex Apps tools while preserving regular MCP tools.
    - Added app-server coverage that `orchestrator.skills.enabled = false`
    prevents orchestrator skill tools, prompts, and resource reads from
    reaching the model turn.
  • [codex] abort turns when rollout budgets expire (token budget 3/3) (#28707)
    ## Stack
    
    Depends on #28494.
    
    ## Description
    
    This PR propagates shared rollout-budget exhaustion through the existing
    `CodexErr::TurnAborted` task result.
    
    Each thread records its model usage against the same ledger. Once the
    ledger is exhausted, that usage update and all later usage updates
    return `TurnAborted`. The task wrapper emits the normal aborted-turn
    event and lifecycle instead of completing the turn.
    
    This is intentionally a soft boundary: there is no cross-thread
    `Op::Interrupt` fanout. An in-flight thread can finish its current
    response before it observes the exhausted ledger, but every thread
    aborts at its next usage-accounting boundary.
    
    ## Tests
    
    The integration coverage verifies that:
    
    - the response that exhausts the budget aborts its turn;
    - a later response also aborts because the shared ledger remains
    exhausted; and
    - sub-agent usage draws from the same shared ledger; and
    - local and remote-v2 compaction abort without retrying or emitting a
    generic error.
    
    Local checks:
    
    - `just test -p codex-core
    exhausted_budget_aborts_current_and_later_turns`
    - `just test -p codex-core subagent_usage_draws_from_the_shared_budget`
    - `just test -p codex-core
    abort_regular_task_emits_marker_before_turn_aborted`
    - `just test -p codex-core
    compaction_budget_exhaustion_aborts_without_error_or_retry`
    - `just fix -p codex-core`
    - `just fmt`
    - `git diff --check`
    
    The full workspace test suite was not run locally.
  • Expose thread-level multi-agent mode (#28792)
    ## 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.
  • Add per-turn multi-agent mode (#28685)
    ## 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.
  • [2/3] core: track starting environments in snapshots (#28683)
    ## Why
    
    Remote environments may still be resolving when Codex creates a session
    or turn. Waiting for the existing all-or-nothing environment snapshot
    can hold startup until the selected environment is usable.
    
    Behind the default-off `deferred_executor` feature, let callers take a
    useful snapshot immediately: completed environments remain available
    normally, while unfinished environments are reported without blocking
    startup. With the feature disabled, snapshots preserve the existing
    blocking behavior.
    
    Depends on #28674.
    
    ## What changed
    
    - Store one ordered list of selected environments in
    `ThreadEnvironments`. Each selection owns one shared resolution that
    produces its complete `TurnEnvironment`.
    - Start new resolutions in the background with `remote_handle()`,
    allowing snapshots and the future wait tool to share the same result
    while cancellation follows the retained handles.
    - Make `snapshot()` a read-only operation: nonblocking snapshots collect
    completed resolutions and retain handles for unfinished ones, while
    blocking snapshots await every resolution.
    - Replace completed failed resolutions from the current manager entry
    and log when failed environments are omitted.
    - Return attached and starting environments as a point-in-time view, and
    count starting environments when deciding whether a snapshot is
    local-only.
    - Keep existing consumers attached-only. `to_selections()` derives from
    attached environments, so child threads do not inherit an environment
    that is still starting.
    
    ## Test plan
    
    - `just test -p codex-core environment_selection`
    - `just test -p codex-core
    deferred_executor_reaches_model_before_remote_environment_is_ready`
    
    ## Landing note
    
    Keep `deferred_executor` disabled for slow-starting executors until
    configurable `environment/add` connection timeouts and caller support
    land. When enabled, an environment that attaches after session startup
    may remain absent from environment-derived model context, tools,
    instructions, skills, and related state until follow-up refresh work
    lands.
  • core: assign item IDs to compacted replacement history (#29012)
    ## Why
    
    Remote v2 compaction can return replacement-history items without IDs.
    Because replacement history is installed directly, those items bypass
    normal history preparation and remain ID-less in later Responses
    requests even when the `item_ids` feature is enabled.
    
    ## What changed
    
    - Pass the active `TurnContext` into `replace_compacted_history`.
    - When `item_ids` is enabled, assign missing IDs before installing and
    persisting replacement history.
    - Rebuild `CompactedItem` from the prepared history so live and
    persisted replacement histories match.
    - Add integration coverage requiring IDs on every ID-capable input item
    in the initial, remote v2 compaction, and post-compaction requests.
    
    ## Test plan
    
    - `just test -p codex-core response_item_ids`
    - `just test -p codex-core websocket_v2_test_codex_shell_chain`
    - `just test -p codex-core remote_compaction_parity_pre_turn_auto`
    - `just test -p codex-app-server
    thread_inject_items_adds_raw_response_items_to_thread_history`
  • [codex] Assign response item IDs when recording history (#28814)
    ## Why
    
    Client-created response items enter history without IDs, so their
    identity is lost across rollout persistence and resume. IDs should be
    assigned once at the history-recording boundary, while IDs returned by
    the server must remain unchanged.
    
    The Responses API validates item IDs using type-specific prefixes.
    Locally generated IDs therefore use the matching prefix plus a
    hyphenated UUIDv7, keeping them valid while distinguishable from
    server-generated IDs. Because this changes persisted history and
    provider request shapes, the behavior is opt-in behind the
    under-development `item_ids` feature. Compaction triggers remain request
    controls whose API shape does not accept an ID.
    
    ## What changed
    
    - Register the disabled-by-default `item_ids` feature and expose it in
    `config.schema.json`.
    - Make supported optional `ResponseItem` IDs serializable and expose
    them in the generated app-server schemas.
    - When `item_ids` is enabled, assign an ID during conversation-history
    preparation if an item has no ID.
    - Generate type-prefixed, hyphenated UUIDv7 IDs using the Responses API
    item conventions.
    - Preserve existing server IDs without rewriting them.
    - Persist assigned IDs in rollouts and include them in subsequent
    Responses requests.
    - Remove the unsupported ID field from `CompactionTrigger` and document
    why it has no ID.
    - Add integration coverage for enabled ID persistence, preservation of
    server IDs, and omission of generated IDs while the feature is disabled.
    
    `prepare_conversation_items_for_history` is the single response-item ID
    allocation boundary.
    
    ## Test plan
    
    - `just test -p codex-features`
    - `just test -p codex-core
    response_item_ids_persist_across_resume_and_preserve_server_ids`
    - `just test -p codex-core
    non_openai_responses_requests_omit_item_turn_metadata`
    - `just test -p codex-core
    resize_all_images_prepares_failures_before_history_insertion`
    - `just test -p codex-protocol`
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-api azure_default_store_attaches_ids_and_headers`
  • core: add UUIDv7 context window IDs (#28953)
    ## Why
    
    The token-budget context currently identifies a context window by its
    thread-local sequence number. A UUIDv7 gives the model a stable opaque
    identity that remains fixed for a window and rotates when compaction or
    `new_context` starts the next one.
    
    ## What changed
    
    - Preserve the existing monotonic value as `window_number` and add a
    UUIDv7 `window_id` to `CompactedItem`.
    - Generate and rotate the UUID with auto-compaction window state,
    persist it alongside the number, and reconstruct it on resume and
    rollback.
    - Accept legacy compacted rollout records where the numeric `window_id`
    represented the window number.
    - Use the UUID only in token-budget context; existing request headers
    and metadata continue using `thread_id:window_number`.
    
    ## Testing
    
    - `just test -p codex-protocol compacted_item::tests`
    - `just test -p codex-core token_budget`
  • [codex] Reuse parsed plugin skills during session startup (#28844)
    ## Summary
    
    - Preserve raw plugin skill-root snapshots in the matching loaded-plugin
    cache entry, keyed by the effective plugin root identity including
    namespace.
    - Pass those snapshots through `SkillsLoadInput` as an optional preload,
    so session startup reuses plugin parsing while ordinary skill loads pass
    `None`.
    - Keep plugin skill loading cohesive: the existing loaders accept the
    optional snapshots directly, and uncached or marketplace-detail paths do
    not create a cache.
    
    ## Why
    
    Plugin discovery already parses plugin skills to determine available
    capabilities. Cold session startup then scanned and parsed the same
    roots again while building the skills snapshot.
    
    This solves the same duplicate-work problem as #28623 while keeping
    ownership narrow: `PluginsManager` creates and owns
    `PluginSkillSnapshots` only for its loaded-plugin cache entry;
    `SkillsService` consumes an optional clone. Entry replacement or
    clearing naturally drops the snapshots, with no separate generation,
    capacity policy, or watcher coupling.
    
    ## Validation
    
    - `cargo clippy -p codex-core-skills --all-targets -- -D warnings`
    - `just test -p codex-core-plugins
    skills_service_reuses_skills_parsed_during_plugin_load`
    - `just test -p codex-core-skills
    namespaces_plugin_skills_using_provided_namespace`
    - `just fmt`
  • core: load AGENTS.md from foreign environments (#28958)
    ## Why
    
    Make it possible to load AGENTS.md from remote exec-servers whose OS is
    different than app-server.
    
    ## What
    
    - keep `AGENTS.md` discovery and provenance as `PathUri`, with
    root-aware parent and ancestor traversal
    - expose lifecycle instruction sources as legacy app-server path strings
    in events while retaining `PathUri` internally
    - preserve and test mixed POSIX and Windows paths in model context and
    TUI status output
    - cover remote Windows loading end to end by seeding the Wine prefix
    through host filesystem APIs
    - fix bug in `PathUri`'s parent() implementation that would erase
    Windows drive letters
  • current time reminders impl for system clock (varlatency 2/n) (#28824)
    Stacked on #28822.
    
    ## Summary
    
    - add a host-injectable current-time provider with a built-in system
    implementation
    - record UTC developer reminders in history immediately before due model
    requests
    - keep cadence state per session and force a refresh after compaction
    
    This does NOT include the app server client <-> server clock logic. This
    PR is only for the reminder message & system clock that will be used in
    prod.
    
    ## Testing
    
    - `just test -p codex-core varlatency_`
    - `just clippy -p codex-core -p codex-app-server -p codex-mcp-server -p
    codex-thread-manager-sample`
    - `just fmt`
  • Support openai/form extended form elicitations (#27500)
    # Summary
    Allow App Server clients to opt into `openai/form` MCP elicitations.
  • [codex] rollout budget implementation (varlength 2/N) (#28494)
    ## Stack
    
    Depends on #28746. This PR implements shared rollout-budget accounting
    and model-visible reminders using the configuration defined in #28746.
    
    # Description / Main changes to Core:
    
    `AgentControl` will now be the area where "rollout level" features &
    accounting will have to live. It is incorrectly named for this
    responsibility, but I think it can hold all the necessary shared state &
    features (rollout token budget, mutliple thread interruption
    responsibilitym etc)
    
    In this PR, we have one "token ledger" that each thread will subtract
    from when sampling. The "charge" will occur when response.completed() is
    done and the calculation will be done on the responses api usage
    carrier. The calculation will weigh sampling and pre-fill tokens as
    specified.
    
    Every time the budget crosses the configured reminder threshold, a
    developer message is appended before the thread's next request
    
    This remaining budget will _always_ be restated/reminded after a
    compaction event.
    
    Expiration and fan-out interruption will be in the stacked follow-up
    (and also live in Agent Control).
    
    ## Reminders
    
    "You have weighted {session_tokens_left} tokens left in the shared
    session token budget."
    
    The first request in each thread context receives the current remainder.
    Later reminders are emitted after aggregate weighted usage crosses a
    configured interval. If several intervals are crossed before a thread
    sends another request, Core inserts one reminder with the latest
    remainder.
    
    Compaction response usage is charged before the next context starts. The
    next reminder is appended after the compaction summary, leaving the
    initial context content stable.
    
    ## Tests
    
    Integration coverage verifies:
    
    - weighted output and non-cached input accounting
    - initial and periodic reminders
    - shared accounting between a root and sub-agent
    - post-compaction remainder and message placement
    
    Local checks:
    
    - `just fmt`
    - `just test -p codex-core rollout_budget`
    - `git diff --check`
    
    The full workspace test suite was not run locally.
  • Add Config for Time Reminders (varlatency 1/n) (#28822)
    ## Summary
    
    Example:
    
    > [features.current_time_reminder]
    enabled = true
    reminder_interval_model_requests = 1
    clock_source = "system"
    
    ## Testing
    
    - `just test -p codex-core varlatency`
    - `just test -p codex-core
    lock_contains_prompts_and_materializes_features`
    - `just fix -p codex-core -p codex-config -p codex-features`
  • Add turn-scoped context contributions (#28911)
    ## Summary
    - keep context injection on a single ContextContributor trait
    - split context injection into thread-scoped and turn-scoped
    contribution methods
    - wire turn-scoped fragments into initial context assembly so extensions
    can contribute context from turn-local state
  • [codex] add rollout token budget configuration (varlength 1/N) (#28746)
    ## What
    
    This PR defines the structured configuration contract for shared rollout
    token budgets (across ALL agent threads under 1 rollout).
    
    ```toml
    [features.rollout_budget]
    enabled = true
    limit_tokens = 100000
    reminder_interval_tokens = 10000
    sampling_token_weight = 1.0
    prefill_token_weight = 0.1
    ```
    
    The reminder interval defaults to 10% of the rollout limit. Sampling and
    prefill weights default to `1.0`.
    
    ## Scope
    
    This PR only defines and validates configuration. It does not track
    usage, inject reminders, or stop a rollout. Accounting and reminders are
    implemented in the stacked follow-up #28494.
    
    The existing `token_budget` feature remains unchanged. `rollout_budget`
    has its own feature key and configuration type.
    
    ## Tests
    
    The config test verifies that the structured fields resolve into
    `RolloutBudgetConfig` and do not enable the existing `token_budget`
    feature.
    
    Local checks:
    
    - `just write-config-schema`
    - `just test -p codex-core load_config_resolves_rollout_budget`
    - `cargo check -p codex-thread-manager-sample`
    - `git diff --check`
    
    The full workspace test suite was not run locally.
  • [codex] control automatic realtime handoff delivery (#27986)
    ## 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.
  • [codex] Use unique IDs for realtime-routed turns (#28826)
    ## Why
    
    A durable realtime voice orchestrator can reconnect and resume through
    multiple fresh `Session` instances. Realtime handoffs were using the
    Session-local `auto-compact-N` counter as their turn identity, but that
    counter restarts at zero for every resumed Session. The durable thread
    could therefore accumulate duplicate turn IDs, violating the uniqueness
    assumptions made by app-server and web clients. In Codex Apps, a new
    delegated response stream could be attached to an older turn with the
    same ID, placing live output higher in history and putting turn-scoped
    actions at risk.
    
    Persisted rollout and reconstructed model-context order were already
    correct because raw response items remain append-only and chronological.
    This change restores unique identity for reconstructed and live turn
    surfaces.
    
    ## What changed
    
    - Generate a UUIDv7 specifically for each realtime-routed delegation.
    - Leave the existing `auto-compact-N` identity path unchanged for actual
    internal auto-compaction turns.
    - Extend the inbound realtime handoff integration test to require a UUID
    turn ID from `turn/started`.
    
    ## Verification
    
    - `just test -p codex-core inbound_handoff_request_starts_turn`
    - `just fix -p codex-core`
    - `just fmt`
  • [codex] Add optional IDs to response items (#28812)
    ## Why
    
    `ResponseItem` variants do not have a consistent internal ID shape: some
    variants carry required IDs, some carry optional IDs, and some cannot
    represent an ID at all. The existing fields also use inconsistent serde,
    TypeScript, and JSON-schema annotations. A single enum-level access path
    is needed before history recording can assign and retain IDs.
    
    This PR establishes that internal model only. It intentionally does not
    generate or serialize IDs; allocation and wire persistence are isolated
    in the stacked follow-up.
    
    ## What changed
    
    - Give every concrete `ResponseItem` variant an `Option<String>` ID
    field.
    - Apply the same internal-only annotations to every ID field:
    `#[serde(default, skip_serializing)]`, `#[ts(skip)]`, and
    `#[schemars(skip)]`.
    - Add `ResponseItem::id()` and `ResponseItem::set_id()` as the shared
    accessors.
    - Preserve IDs when history items are rewritten for truncation.
    - Adapt consumers that previously assumed reasoning and image-generation
    IDs were required.
    - Regenerate app-server schemas so the hidden fields are represented
    consistently.
    
    The serde catch-all `ResponseItem::Other` remains ID-less because it
    must remain a unit variant.
    
    ## Test plan
    
    - `cargo check --tests -p codex-core -p codex-api -p codex-rollout-trace
    -p codex-image-generation-extension`
    - `just test -p codex-protocol`
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-api -p codex-rollout-trace -p
    codex-image-generation-extension`
    - `just test -p codex-core event_mapping`
  • Scope command approvals by execution environment (#28738)
    ## Why
    
    Command approval cache keys included the command and working directory,
    but not the execution environment. An approval for `/workspace` locally
    could therefore be reused for the same command and path on an executor.
    
    ## What changed
    
    - Include the selected environment ID in shell and unified-exec approval
    cache keys.
    - Carry that ID through the normal command approval request so clients
    can show which environment is being approved.
    - Expose the environment through app-server as a required nullable
    `environmentId` and show it in the inline TUI approval prompt.
    - Keep older recorded approval events compatible when the environment is
    absent.
    
    For example, `echo ok` in local `/workspace` and `echo ok` in executor
    `/workspace` now produce different approval keys and separate prompts.
    
    ## Scope
    
    This PR does not change network approvals, Guardian review actions, MCP
    elicitation, full-screen TUI rendering, or environment-ID validation.
    Remote `shell_command` execution itself remains in #28722; this PR only
    makes its approval key environment-aware.
  • Run fs helper through Windows sandbox wrapper (#28359)
    ## Why
    
    This is the final PR in the Windows fs-helper sandbox stack and contains
    the actual bug fix.
    
    The exec-server filesystem helper is a direct-spawn path: it asks
    `SandboxManager` for a `SandboxExecRequest`, then launches the returned
    argv itself. That works on macOS and Linux because the transformed argv
    is already a self-contained sandbox wrapper. On Windows, the transformed
    request carried `WindowsRestrictedToken` metadata, but the direct-spawn
    fs-helper runner still launched the helper argv directly.
    
    That means Windows filesystem built-ins backed by the fs-helper could
    run with the parent Codex process permissions instead of the configured
    Windows sandbox. This PR makes the direct-spawn transform produce a
    self-contained Windows wrapper argv before fs-helper launches it.
    
    ## What Changed
    
    - Added `SandboxManager::transform_for_direct_spawn()` for callers that
    launch the returned argv themselves.
    - Wrapped Windows restricted-token direct-spawn requests with `codex.exe
    --run-as-windows-sandbox` and then marked the outer request as
    unsandboxed, matching the macOS/Linux wrapper argv shape.
    - Updated `exec-server/src/fs_sandbox.rs` to use the direct-spawn
    transform for fs-helper launches.
    - Materialized the inner `codex.exe --codex-run-as-fs-helper` executable
    into `.sandbox-bin` so the sandboxed user can run it.
    - Carried runtime workspace roots through `FileSystemSandboxContext` as
    `PathUri` values so `:workspace_roots` policies resolve correctly
    without sending native client paths over exec-server JSON.
    - Preserved wrapper setup identity environment needed by Windows sandbox
    setup without changing the serialized inner helper environment.
    
    ## Verification
    
    - `just bazel-lock-update`
    - `just bazel-lock-check`
    - `just test -p codex-sandboxing transform_for_direct_spawn_windows`
    - `just test -p codex-exec-server fs_sandbox::tests`
    - `just fix -p codex-windows-sandbox -p codex-sandboxing -p
    codex-exec-server -p codex-core -p codex-file-system`
    
    Local note: `just fmt` completed Rust formatting, but this workstation
    still fails the non-Rust formatter phases because uv cannot open its
    cache and the local buildifier/dotslash path is missing.
  • Replace SkillsManager with SkillsService (#28705)
    ## Why
    
    Host skill discovery was still exposed as a manager even though it is a
    process-owned service shared by sessions, the app-server catalog, and
    file-watcher invalidation. The skills extension also consumed an ad hoc
    loaded-skills wrapper instead of a named immutable snapshot.
    
    ## What changed
    
    - replace `SkillsManager` with concrete `SkillsService`
    - make the service cache and return immutable `HostSkillsSnapshot`
    values
    - migrate the skills extension host provider to the snapshot boundary
    - migrate app-server catalog, watcher, and invalidation paths to the
    service
    
    This keeps the service limited to host discovery, caching, roots, and
    invalidation. Catalog rendering and invocation remain extension
    responsibilities for the next stacked change.
  • Add join key for MAv2 inter-agent messages (#28561)
    ## Summary
    This keeps inter-agent communication on the existing raw response item
    path and adds a join key for MAv2 tool calls.
    
    MAv2 `spawn_agent`, `send_message`, and `followup_task` now stamp the
    originating tool call id into `ResponseItemMetadata.source_call_id` on
    the raw `ResponseItem::AgentMessage`. App-server clients can join that
    raw item back to the existing tool/activity event by call id, while
    using the raw agent message's existing sender, receiver, and content
    fields.
    
    No new app-server `ThreadItem` or notification type is added.
    
    ## Tests
    - `just fmt`
    - `just write-app-server-schema`
    - `just test -p codex-protocol`
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-core
    multi_agent_v2_spawn_returns_path_and_send_message_accepts_relative_path`
    - `just test -p codex-core
    multi_agent_v2_followup_task_completion_notifies_parent_on_every_turn`
    - `just fix -p codex-protocol`
    - `just fix -p codex-app-server-protocol`
    - `just fix -p codex-core`
  • core: remove redundant TurnContext and Prompt fields (#28638)
    ## Why
    
    `TurnContext` had accumulated dead fields and cached projections of
    values already owned by its per-turn `Config` or `ModelInfo`. Keeping
    both copies made ownership unclear and allowed artificial split-brain
    states, such as a compatibility hash differing from the model metadata
    it came from.
    
    `Prompt` similarly carried a write-only personality after personality
    selection had already been materialized into its base instructions.
    
    This makes the canonical owner explicit: configuration-backed values
    come from `config`, model-derived values come from `model_info`, and
    prompts contain only data consumed by request construction.
    
    ## What changed
    
    - Remove the unused `ghost_snapshot`, `codex_self_exe`, and
    `thread_source` fields.
    - Remove duplicate `comp_hash`, `truncation_policy`, `features`,
    `shell_environment_policy`, `codex_linux_sandbox_exe`, `compact_prompt`,
    and `tool_mode` fields.
    - Read those values directly from `TurnContext::config` or
    `TurnContext::model_info` at their consumers.
    - Remove the write-only `Prompt::personality` field and its constructor
    assignments.
    - Preserve review-turn inheritance of the parent turn's shell policy,
    Linux sandbox executable, and compact prompt through the review config.
    
    ## Testing
    
    - `cargo check -p codex-core --tests`
  • [codex] core: restore absolute turn context cwd (#28629)
    ## Why
    
    #28152 jumped the gun on moving the rollout format to store URIs, and
    would likely break compat with some features that don't go through the
    same types as the core logic.
    
    ## What
    
    Make `TurnContextItem.cwd` an `AbsolutePathBuf` again, remove test added
    for `PathUri` serialization in rollouts. Also drops a bunch of error
    paths that are no longer needed.
  • core: render remote environment cwd natively (#28152)
    ## Why
    
    Model-visible `<environment_context>` should match the environment of
    the executor, not of the app server.
    
    Stacked on #28146.
    
    ## What
    
    - Keep selected environment cwd values as `PathUri` while building
    environment context.
    - Render cwd text using the path convention represented by the URI, with
    the canonical URI as a fallback.
    - Preserve compatibility with legacy `TurnContextItem.cwd` values when
    reconstructing and diffing context.
    - Extend the Wine-backed remote Windows test to assert that the model
    sees `powershell` and `C:\windows`.
  • [codex] [3/4] Activate endpoint plugin recommendations (#27704)
    Summary\n- Await endpoint recommendation selection while constructing
    each authenticated turn, removing the first-turn cache race.\n- Snapshot
    and filter endpoint candidates once per turn, then use that same set for
    the bounded contextual user fragment, tool exposure, and exact install
    validation.\n- Keep recommendation selection ephemeral: do not persist
    recommendation state in or gate resumed threads on prior context.\n-
    Hide the legacy list tool in endpoint mode and preserve legacy discovery
    unchanged when the endpoint is disabled or unavailable.\n- Keep remote
    plugin and connector app identities out of model-visible context and
    attach them only to Codex-owned elicitation metadata.\n\nStack\n- 3/4,
    based on #28400.\n- Endpoint client and cache: #28399.\n- Generalized
    suggestion presentation: #28400.\n- Install-schema follow-up:
    #28403.\n\nValidation\n- \n- \n- \n- \n- Full : 2,649 passed and 88
    environment-dependent tests failed because this sandbox cannot write ,
    nest Seatbelt, or locate auxiliary test binaries.
  • [codex] [2/4] Generalize plugin suggestion presentation (#28400)
    Summary
    - Add list-backed and developer-context presentations for plugin
    suggestion candidates.
    - Let tool planning, install validation, and request-tool copy follow
    the selected presentation.
    - Keep every production caller on the existing list-backed presentation,
    preserving the current list tool, request schema, connector behavior,
    and model-visible copy.
    - Leave developer-context presentation latent until the final PR in the
    stack.
    
    Stack
    - 2/3, based on #28399.
    - Follow-up: #27704 activates endpoint recommendations.
    
    Validation
    - `just test -p codex-core request_plugin_install`
    - `just test -p codex-core spec_plan`
    - `just fix -p codex-core`
    - `just fmt`
    - `git diff --check`
  • app-server: preserve target-native environment cwd (#28146)
    ## 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`
  • core: surface terminal subagent errors to parent agents (#28375)
    ## Why
    
    When a subagent exhausts its retries, it emits an `Error`, but the
    generic task lifecycle then emits `TurnComplete(None)`. That completion
    used to overwrite the subagent's `Errored` status with
    `Completed(None)`, so the parent received an empty completion
    notification.
    
    This made a failed child look indistinguishable from a child that
    completed without an answer. In unattended or long-running multi-agent
    work, the root could silently continue without knowing that delegated
    work failed or how to restart it.
    
    ## Behavior
    
    Before, a terminal stream failure was reduced to an empty completion:
    
    ```text
    <subagent_notification>
    {"agent_path":"/root/worker","status":{"completed":null}}
    </subagent_notification>
    ```
    
    Now the parent receives the actual terminal error, bounded to 1,000
    tokens, together with an actionable recovery hint:
    
    ```text
    <subagent_notification>
    {
      "agent_path": "/root/worker",
      "status": {
        "errored": "stream disconnected before completion: stream closed before response.completed"
      },
      "next_action": "This agent's turn failed. If you still need this agent, use `followup_task` to give it another task."
    }
    </subagent_notification>
    ```
    
    The notification remains queue-only: it does not wake the root or replay
    the failed request. The root sees it at the next sampling boundary and
    can use `followup_task` to start a new turn for that agent.
    
    ## What changed
    
    - Added terminal-error precedence to the [agent status
    reducer](https://github.com/openai/codex/blob/e95fcfe2bb6a02f1a75650afa20048859f556511/codex-rs/core/src/agent/status.rs#L23-L34),
    so a closing `TurnComplete` cannot erase an immediately preceding
    `Errored` status.
    - Made MultiAgentV2 completion forwarding use the retained session
    status instead of re-deriving `Completed(None)` from the final event.
    - Extended the [subagent notification
    fragment](https://github.com/openai/codex/blob/e95fcfe2bb6a02f1a75650afa20048859f556511/codex-rs/core/src/context/subagent_notification.rs#L6-L60)
    with a `next_action` for terminal errors and a hard cap on model-visible
    error text.
    - Kept successful completions and interrupted turns unchanged.
    
    ## Verification
    
    - Added a status-reducer test proving that `Errored` survives the
    trailing `TurnComplete`.
    - Added an integration test that exhausts a subagent's stream retries
    and verifies the exact `agent_message` delivered to the parent,
    including the error and `followup_task` guidance.
    - Re-ran the existing successful-completion and interrupted-turn
    notification tests.
  • feat: render typed envelopes for multi-agent v2 messages (#28368)
    ## Why
    
    Multi-agent v2 messages need a consistent, model-visible envelope that
    identifies what kind of interaction occurred, who sent it, and which
    agent it targets. Previously, encrypted deliveries exposed only
    `encrypted_content`, while child completion used the legacy
    `<subagent_notification>` shape. That meant the client could not
    consistently present `NEW_TASK`, `MESSAGE`, and `FINAL_ANSWER` using the
    same format.
    
    This change adds the routing envelope as plaintext while keeping task
    and message payloads encrypted. No new Responses API field is required:
    an encrypted delivery is represented as an `input_text` header
    immediately followed by its existing `encrypted_content` item.
    
    Every envelope now follows this shape:
    
    ```text
    Message Type: <NEW_TASK | MESSAGE | FINAL_ANSWER>
    Task name: <recipient agent path>
    Sender: <author agent path>
    Payload:
    <message payload>
    ```
    
    ## Message types
    
    ### `NEW_TASK`
    
    `NEW_TASK` is used when the recipient should begin a new turn, including
    an initial `spawn_agent` task and a later `followup_task`.
    
    For a root agent spawning `/root/worker`, the request contains a
    plaintext envelope followed by the encrypted task:
    
    ```json
    {
      "type": "agent_message",
      "author": "/root",
      "recipient": "/root/worker",
      "content": [
        {
          "type": "input_text",
          "text": "Message Type: NEW_TASK\nTask name: /root/worker\nSender: /root\nPayload:\n"
        },
        {
          "type": "encrypted_content",
          "encrypted_content": "<encrypted task payload>"
        }
      ]
    }
    ```
    
    Conceptually, the model receives:
    
    ```text
    Message Type: NEW_TASK
    Task name: /root/worker
    Sender: /root
    Payload:
    Review the authentication changes and report any regressions.
    ```
    
    ### `MESSAGE`
    
    `MESSAGE` is used for a queued `send_message` delivery. It communicates
    with an existing agent without starting a new turn.
    
    For `/root/worker` reporting progress to the root agent, the request
    contains:
    
    ```json
    {
      "type": "agent_message",
      "author": "/root/worker",
      "recipient": "/root",
      "content": [
        {
          "type": "input_text",
          "text": "Message Type: MESSAGE\nTask name: /root\nSender: /root/worker\nPayload:\n"
        },
        {
          "type": "encrypted_content",
          "encrypted_content": "<encrypted message payload>"
        }
      ]
    }
    ```
    
    Conceptually, the model receives:
    
    ```text
    Message Type: MESSAGE
    Task name: /root
    Sender: /root/worker
    Payload:
    The protocol tests pass; I am checking the resume path now.
    ```
    
    ### `FINAL_ANSWER`
    
    `FINAL_ANSWER` is emitted when a child agent reaches a terminal state
    and reports its result to its parent. Completion payloads are already
    available locally, so the complete envelope is represented as plaintext
    rather than as a plaintext header plus encrypted content.
    
    For `/root/worker` completing work for the root agent, the request
    contains:
    
    ```json
    {
      "type": "agent_message",
      "author": "/root/worker",
      "recipient": "/root",
      "content": [
        {
          "type": "input_text",
          "text": "Message Type: FINAL_ANSWER\nTask name: /root\nSender: /root/worker\nPayload:\nNo regressions found."
        }
      ]
    }
    ```
    
    The model-visible form is:
    
    ```text
    Message Type: FINAL_ANSWER
    Task name: /root
    Sender: /root/worker
    Payload:
    No regressions found.
    ```
    
    Errored, shut down, and missing agents also use `FINAL_ANSWER`, with a
    terminal-status description in the payload.
    
    ## What changed
    
    - Render `NEW_TASK` or `MESSAGE` in
    `InterAgentCommunication::to_model_input_item`, based on whether the
    encrypted delivery starts a turn.
    - Replace the multi-agent v2 `<subagent_notification>` completion
    payload with a model-visible `FINAL_ANSWER` envelope.
    - Document `Task name`, `Sender`, and `Payload` consistently in the
    multi-agent developer instructions.
    - Prevent local-only history projections from treating an encrypted
    message's plaintext header as the complete assistant message.
    - Preserve rollout-trace interaction edges when an agent message
    contains both plaintext and encrypted content.
    
    Legacy multi-agent behavior remains unchanged.
    
    ## Verification
    
    - `just test -p codex-protocol`
    - `just test -p codex-rollout-trace`
    - `just test -p codex-web-search-extension`
    - `just test -p codex-core
    encrypted_multi_agent_v2_spawn_sends_agent_message_to_child`
    - `just test -p codex-core
    plaintext_multi_agent_v2_completion_sends_agent_message`
    - `just test -p codex-core
    multi_agent_v2_followup_task_completion_notifies_parent_on_every_turn`
    - `just test -p codex-core
    multi_agent_v2_completion_queues_message_for_direct_parent`
  • [codex] Use local environment for user shell commands (#28163)
    ## Why
    
    User shell commands still read the legacy turn cwd and session shell
    even though execution context is now owned by selected turn
    environments. App-server also defines `thread/shellCommand` as a
    local-host escape hatch, so it must use an available local environment
    even when a remote environment is primary.
    
    ## What changed
    
    - Add `ResolvedTurnEnvironments::local()` to find the selected local
    environment.
    - Resolve the user shell command cwd and shell from that local
    `TurnEnvironment`.
    - Emit the standard `shell is unavailable in this session` error when no
    selected local environment or resolved local shell is available.
    - Add an integration test covering `/shell` without a local environment.
    
    ## Test plan
    
    - `just test -p codex-core
    user_shell_command_without_local_environment_emits_error`
  • [codex] Bind shell snapshots to retained thread environments (#28421)
    ## Why
    
    Shell snapshots are currently session-scoped even though shell and cwd
    are properties of a selected turn environment. That makes snapshot
    refresh depend on separate session-cwd plumbing, prevents retained
    environments from retaining their snapshot work, and can make snapshot
    construction use a different shell than command execution.
    
    This follows #27955 by making the retained thread-environment service
    own environment snapshot lifecycles. Session configuration remains the
    requested selection state, while `ThreadEnvironments` remains the source
    of successfully resolved environments.
    
    ## What changed
    
    - Configure the shell-snapshot builder before initial environment
    resolution.
    - Start each local environment snapshot task when its `TurnEnvironment`
    is built and retain that shared task while environment ID and cwd still
    match.
    - Inherit retained environment snapshots into spawned child threads.
    - Carry the selected `TurnEnvironment` through shell runtimes so
    snapshot construction and command execution use the same
    environment-specific shell and cwd.
    - Load project instructions and warm plugins/skills after initial
    environment resolution.
    - Continue decoding invalid UTF-8 instruction files lossily without
    emitting a startup warning.
    - Keep requested selections in `SessionConfiguration`; failed or
    duplicate resolutions only affect the resolved environment snapshot.
    
    ## Validation
    
    - `cargo check -p codex-core --tests`
    - `just test -p codex-home instructions` (6 passed)
    - Focused environment, instruction, shell-snapshot, and user-shell tests
    (84 passed)
    - Focused shell-snapshot, user-shell, and unified-exec tests (126
    passed; two event-timing tests passed on retry)
  • Use PathUri in filesystem permission paths for exec-server (#28165)
    ## Why
    
    Progress towards letting app-server and exec-server run on different
    platforms, specifically for sandbox configuration.
    
    ## What
    
    - Make the filesystem path containment hierarchy generic, defaulting to
    `AbsolutePathBuf` for now.
    - Have clients specify `AbsolutePathBuf` or `PathUri` directly where
    needed.
    - Use `PathUri` throughout exec-server filesystem protocol and trait
    boundaries.
    - Implement `From` for conversion to path URIs and `TryFrom` for
    fallible conversion to absolute paths through the generic type
    hierarchy.
  • Add realtime speech append control (#27917)
    ## 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`
  • [codex] retain resolved environments across turns (#27955)
    ## Why
    
    Selected execution environments are thread-scoped resources, but startup
    and turn construction repeatedly resolved their IDs and working
    directories. That discarded existing environment handles and shell
    metadata even when a selection had not changed.
    
    Session configuration updates also need to affect future turns without
    changing the resolved environment set already captured by a running
    turn.
    
    ## What changed
    
    - Create a `ThreadEnvironments` service inside `Codex` from the spawned
    `EnvironmentManager` and raw environment selections, then store it on
    `SessionServices`.
    - Split service construction from `update_selections`, allowing session
    configuration updates to mutate the resolved set in place.
    - Retain an existing `TurnEnvironment` when its environment ID and
    working directory match; resolve only added or changed selections and
    remove selections that are no longer present.
    - Normalize duplicate IDs by keeping the first selection and skip
    individual selections that fail to resolve instead of rejecting the
    entire update.
    - Give each `TurnContext` a cloned `TurnEnvironmentSnapshot`, so later
    session configuration updates affect future turns without rewriting an
    active turn.
    - Reuse the service-owned environment manager and resolved snapshot for
    startup work, MCP initialization, and child-thread spawning instead of
    flowing resolved environments through spawn arguments.
    
    ## Test plan
    
    - `cargo check -p codex-core --tests`
    - `just test -p codex-core environment_selection`
    - `just test -p codex-core turn_environments`
    - `just test -p codex-core
    session_update_settings_does_not_rewrite_sticky_environment_cwds`
    - `just test -p codex-core
    default_turn_does_not_overlay_legacy_fallback_cwd_onto_stored_thread_environments`
  • feat(core): add metadata field to ResponseItem (#28355)
    ## Description
    
    This PR adds an optional `metadata` field to `ResponseItem` for
    Responses API calls. Only mechanical plumbing, no actual values
    populated and sent yet. Turns out just adding a new field to
    `ResponseItem` has quite a large blast radius already.
    
    This change is backwards compatible because `metadata` is optional and
    omitted when absent, so existing response items and rollout history
    without it still deserialize and requests that do not set it keep the
    same wire shape. For provider compatibility, we strip out `metadata`
    before non-OpenAI Responses requests so Azure and AWS Bedrock never see
    this field.
    
    My followup PR here will actually make use of it to start storing and
    passing along `turn_id`: https://github.com/openai/codex/pull/28360
    
    ## What changed
    
    - Added `ResponseItemMetadata` with optional `turn_id`, plus optional
    `metadata` on Responses API item variants and inter-agent communication.
    - Preserved item metadata through response-item rewrites such as
    truncation, missing tool-output synthesis, compaction history
    rebuilding, visible-history conversion, rollout/resume, and generated
    app-server schemas/types.
    - Strip item metadata from non-OpenAI Responses requests while
    preserving it for OpenAI-shaped requests.
    - Updated the mechanical fixture/test construction churn required by the
    new optional field.
  • core: cache the tool search handler per session (#27258)
    ## Why
    
    Tool router construction rebuilds the deferred-tool BM25 index during
    session initialization and before each sampling continuation, even when
    the searchable tool metadata is unchanged. Local profiling measured
    `append_tool_search_executor` at roughly 113 ms per continuation, making
    repeated index construction the largest measured router-building cost.
    
    ## What changed
    
    - Add a session-scoped `ToolSearchHandlerCache` so continuations and
    user turns can reuse the existing handler.
    - Key reuse on the complete ordered `Vec<ToolSearchInfo>`, rebuilding
    when searchable text, loadable tool specs, source metadata, or ordering
    changes.
    - Build handlers outside the cache lock and recheck before publishing
    them, avoiding holding the mutex during index construction.
    
    ## Verification
    
    - `cache_reuses_identical_search_infos_and_rebuilds_changed_inputs`
    covers exact cache reuse and invalidation when the ordered search
    metadata changes.
    - Local rollout profiling showed the initial router build populating the
    cache and unchanged later continuations reusing it:
      - uncached: 118 ms median across 14 spans from 3 rollouts
      - cached: 4 ms median across 12 spans from 3 rollouts
  • core: let steer interrupt wait_agent (#28341)
    ## Why
    
    `wait_agent` can block for a long timeout while waiting for sub-agent
    mailbox activity. Although same-turn user steer is accepted during that
    tool call, the input remains pending until the wait returns, so an
    explicit request to change direction can appear unresponsive.
    
    ## What changed
    
    - Notify active `wait_agent` calls when user input is steered into the
    current turn.
    - Check for already-pending steer input when subscribing so input that
    races with tool startup is not missed.
    - Distinguish mailbox activity, steered input, and timeout outcomes,
    returning `Wait interrupted by new input.` for the steer path.
    - Update the `wait_agent` tool description to document the early-return
    behavior.
    
    ## Testing
    
    - `just test -p codex-core input_queue_`
    - `just test -p codex-core wait_agent`
    
    The coverage includes steer notification before and after subscription,
    plus an end-to-end test that verifies the interrupted wait result and
    steered user input are both included exactly once in the follow-up model
    request.
  • guardian: isolate review context from skills and memories (#28285)
    ## Why
    
    Guardian reviews embed the parent session transcript as untrusted
    evidence. Skill or plugin mentions in that transcript must not be
    interpreted as requests to inject more instructions into the Guardian
    request, and memory context adds unrelated model-visible context to an
    approval decision.
    
    Keeping those sources out of the nested review session makes the request
    smaller and preserves the trust boundary around the transcript being
    assessed.
    
    ## What changed
    
    - Skip skill and plugin discovery when building turns for Guardian
    reviewer sessions.
    - Disable memory context and dedicated memory tools in the derived
    Guardian configuration.
    - Extend the Guardian request-layout coverage to verify that a `$skill`
    mention remains visible only as transcript evidence while neither the
    skill body nor memory context is injected.
    - Expand the Guardian configuration test to cover the disabled memory
    settings.
    
    ## Testing
    
    - Updated the Guardian review request snapshot and assertions for skill
    and memory isolation.
    - Extended the Guardian session configuration test to cover memories.
  • [codex] preserve explicit environment cwd (#27995)
    ## 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`
  • avoid cloning sampling request input (#28306)
    ## Why
    
    Every model request cloned the full prepared input just to keep it for
    the legacy after-agent hook. That copy gets more expensive as the
    conversation grows.
    
    ## What
    
    Move the prepared input into the sampling loop and return it with the
    result. If the request retries, keep the first input so the hook still
    sees the same data as before.
    
    This removes one `O(n)` clone per sampling request, where `n` is the
    size of the prepared input. It saves `O(n)` copy work and `O(n)`
    temporary memory.
    
    No behavior change is intended.
    
    ## Performance
    
    Local rollout traces show turns reaching roughly 260k input tokens. On
    turns of that size, this removes the only unconditional full
    prepared-input clone on the happy path. That avoids one request-sized
    allocation/copy per sampling attempt for large conversations, and the
    savings scale linearly with request size.
    
    ## Testing
    
    - `just test -p codex-core continue_after_stream_error`
    - `just fix -p codex-core`
  • [codex] simplify shell snapshot ownership (#27756)
    ## Why
    
    Shell snapshot lifecycle state was split between `Shell` and
    `SessionServices`: `Shell` carried the receiver while session code
    exposed and forwarded the raw sender. That coupled shell identity to
    mutable snapshot state and made refresh, inheritance, and file lifetime
    harder to reason about.
    
    ## What changed
    
    - make each `Arc<ShellSnapshot>` represent one cwd-specific snapshot
    generation
    - store the active generation in `SessionServices` with `ArcSwapOption`
    - have construction start the background build and expose only a
    cwd-validated snapshot path
    - use `ShellSnapshotFile` ownership to delete snapshot files
    automatically
    - pass snapshot paths explicitly to shell runtimes instead of storing
    snapshot state on `Shell`
    - preserve inherited and in-flight generations by pinning their `Arc`
    while they are in use
    
    ## Test plan
    
    - `cargo check -p codex-core --lib`
    - `just test -p codex-core 'shell_snapshot::tests'`
    - `just test -p codex-core
    shell_command_snapshot_still_intercepts_apply_patch`
    - `just test -p codex-core
    shell_snapshot_deleted_after_shutdown_with_skills`
  • skills: hide orchestrator skills with a local executor (#28333)
    ## Why
    
    App-server threads without a local executor need orchestrator-owned
    skills from the hosted `codex_apps` MCP server. Threads with the local
    executor already discover installed skills from the local filesystem.
    
    After the orchestrator skill provider was enabled for every app-server
    thread, local-executor threads also received the hosted skill catalog
    and the `skills.list` and `skills.read` tools. This changed the existing
    local behavior and could expose a second hosted copy of a skill that was
    already installed locally.
    
    ## What changed
    
    - Expose the thread's selected execution environments to extensions at
    thread startup.
    - Enable orchestrator skills only when the reserved local environment is
    not selected.
    - Apply that decision consistently to hosted skill catalog discovery,
    explicit skill injection, and the `skills.list` and `skills.read` tools.
    
    ## Verification
    
    - The existing no-executor app-server test continues to verify hosted
    skill discovery, invocation, and child-resource reads.
    - A new app-server test verifies that local-executor threads do not
    receive hosted skill context or `skills.*` tools.
  • [codex] exec-server honors remote environment cwd and shell (#28122)
    ## 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