Commit Graph

168 Commits

  • 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.
  • [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`
  • [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
  • [codex] add latency tracing spans (#27710)
    ## Why
    
    We have some large gaps in our thread start, resume, and pre-sampling
    traces that make it hard to tell where latency is coming from.
    
    ## What Changed
    
    - Added coarse spans around thread start/resume, turn context
    construction, rollout reconstruction, skill/plugin loading, and tool
    preparation.
    - Added a breakdown of discoverable-tool preparation across connector
    loading, plugin discovery, and local plugin details.
    
    ## Testing
    
    - `cargo check -p codex-app-server -p codex-core -p codex-core-skills -p
    codex-core-plugins`
    - Built the app-server locally and exercised thread start, first turn,
    follow-up turn, server restart, thread resume, and a resumed turn.
  • feat: use encrypted local secrets for MCP OAuth (#27541)
    ## Summary
    
    - store MCP OAuth credentials in the configured auth credential backend
    - support encrypted-local OAuth storage, including legacy keyring
    migration
    - propagate the credential backend through MCP refresh, session, CLI,
    and app-server paths
    
    ## Stack
    
    1. #27504 — config and feature flag
    2. #27535 — auth-specific secret namespaces
    3. #27539 — encrypted CLI auth storage
    4. this PR — encrypted MCP OAuth storage
    
    This is a parallel review stack; the original #17931 remains unchanged.
    
    ## Tests
    
    - `just test -p codex-rmcp-client` (the transport round-trip test passed
    after building the required `codex` binary and retrying)
    - `just test -p codex-mcp`
    - `just test -p codex-app-server
    refresh_config_uses_latest_auth_keyring_backend`
    - `just test -p codex-core
    refresh_mcp_servers_is_deferred_until_next_turn`
    - `just test -p codex-cli mcp`
    - `just fix -p codex-rmcp-client -p codex-mcp -p codex-core -p codex-cli
    -p codex-app-server -p codex-protocol`
    - `just bazel-lock-check`
  • Support plaintext agent messages (#27830)
    ## Why
    
    Multi-agent v2 `send_message` deliveries already reach the receiving
    model as typed `agent_message` items with encrypted content.
    Child-completion notifications are generated by Codex itself, so their
    content is plaintext and previously fell back to a serialized JSON
    envelope inside an assistant message.
    
    With plaintext `input_text` supported for `agent_message`, both delivery
    paths can use the same model-visible type while preserving explicit
    author and recipient metadata.
    
    ## What changed
    
    - add plaintext `input_text` support to `AgentMessageInputContent` and
    regenerate the affected app-server schemas
    - preserve `InterAgentCommunication` as structured mailbox input instead
    of converting it to assistant text
    - record delivered communications as typed `agent_message` history items
    - persist a dedicated rollout item so local delivery metadata such as
    `trigger_turn` remains available without leaking into the Responses
    request
    - reconstruct typed agent messages on resume and preserve fork-turn
    truncation behavior
    - remove request-time assistant-content parsing
    - preserve plaintext and encrypted inter-agent deliveries in stage-one
    memory inputs
    - normalize and link plaintext and encrypted agent messages in rollout
    traces without treating inbound messages as child results
    - cover the real MultiAgent V2 child-completion path end to end with
    deterministic mailbox synchronization
    
    ## Verification
    
    - `just test -p codex-core
    plaintext_multi_agent_v2_completion_sends_agent_message`
    - `just test -p codex-core input_queue_drains_mailbox_in_delivery_order
    record_initial_history_reconstructs_typed_inter_agent_message
    fork_turn_positions_use_inter_agent_delivery_metadata`
    - `just test -p codex-memories-write
    serializes_inter_agent_communications_for_memory`
    - `just test -p codex-rollout-trace
    agent_messages_preserve_routing_and_content
    sub_agent_started_activity_creates_spawn_edge`
    - `just test -p codex-rollout-trace
    agent_result_edge_falls_back_to_child_thread_without_result_message`
    - `just test -p codex-protocol -p codex-rollout -p
    codex-app-server-protocol`
  • Make MCP server contributions thread-scoped (#27670)
    ## Why
    
    `selectedCapabilityRoots` belongs to one thread, but MCP contributors
    previously received only the global Codex config. That left no clean way
    for a selected executor capability to contribute MCP servers to its own
    thread.
    
    ## What this PR does
    
    - Gives MCP contributors a small context containing the config and, for
    a running thread, its frozen host-seeded inputs.
    - Uses the same thread inputs during startup, status queries, refreshes,
    and skill dependency checks.
    - Keeps threadless MCP operations and the existing hosted Apps behavior
    unchanged.
    - Adds coverage showing that two threads resolve independent
    registrations and that later lifecycle mutations do not change the
    frozen MCP inputs.
    
    This PR does not discover plugin manifests, add MCP servers, or launch
    anything new. It only establishes the thread-scoped registration
    boundary.
    
    ## Follow-ups
    
    - Resolve selected executor plugin roots through their owning
    environment filesystem.
    - Convert their stdio MCP declarations into environment-bound
    registrations and add an executor MCP end-to-end test.
    
    ## Verification
    
    - `just fmt`
    - `cargo check --tests -p codex-protocol -p codex-extension-api -p
    codex-mcp-extension -p codex-core -p codex-app-server`
    
    Tests and Clippy were not run.
  • [codex] Load AGENTS.md from all bound environments (#27696)
    ## Why
    
    We already have the machinery to support multiple environments on a
    single thread, but we only show the model the contents of `AGENTS.md`
    files in the primary environment.
    
    We should show the model all of the relevant project instructions when
    we know there's more than one environment.
    
    ## Known Gaps
    
    As discussed in the RFC, this implementation:
    
    1. doesn't handle environments being added/removed to/from the thread
    after its creation
    2. it doesn't enforce an aggregate context budget across environments,
    and instead applies the configured project maximum independently to each
    environment
    
    ## Implementation
    
    - Discover project instructions in environment order with an independent
    byte budget per environment and preserve source provenance/order.
    - Keep the legacy fragment byte-for-byte when exactly one environment
    contributes project instructions; use environment-labeled sections when
    two or more environments contribute.
    - Freeze the complete rendered fragment in `LoadedAgentsMd`, insert it
    directly into requests, and recognize both layouts in contextual and
    memory filtering.
    - Add exact rendering, independent-budget, source-order,
    creation-snapshot, and consumer coverage without changing app-server
    schemas.
  • Add request_user_input auto-resolution window contract (#27256)
    ## Why
    
    `request_user_input` is moving beyond its original plan-mode-only
    workflow, and future default/goal-mode usage needs a way for the model
    to ask helpful but non-blocking questions without forcing the turn to
    wait forever. This PR adds an explicit `autoResolutionMs` contract so a
    later client/runtime change can auto-resolve unanswered prompts after a
    bounded window while leaving truly blocking questions unchanged.
    
    This is contract plumbing only; it does not implement the client-side
    timer or auto-selection behavior, and the model-facing description
    treats the field as reserved unless the current runtime explicitly
    supports auto-resolution.
    
    ## What Changed
    
    - Added optional `autoResolutionMs` to the model-facing
    `request_user_input` args and core `RequestUserInputEvent`.
    - Added model-facing schema text for `autoResolutionMs` while marking it
    reserved for runtimes that explicitly support auto-resolution.
    - Bounds `autoResolutionMs` to `60_000..=240_000` ms during argument
    normalization by clamping out-of-range model-provided values.
    - Propagated the field through app-server v2
    `ToolRequestUserInputParams`, app-server request forwarding, generated
    TypeScript, and JSON schema fixtures.
    - Updated app-server, core, protocol, and TUI call sites/tests so
    omitted values preserve existing `None`/`null` behavior and coverage
    verifies a `Some(60_000)` round trip.
    
    ## Verification
    
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-core request_user_input`
    - `just test -p codex-app-server request_user_input_round_trip`
    - `just test -p codex-tui request_user_input`
    - `just test -p codex-protocol`
  • Add spans to turn lifecycle gaps (#27623)
    ## Why
    Codex app-server latency traces do not granularly cover turn task
    startup and inter-request handoffs. These spans help attribute time
    across task execution, startup prewarm, in-flight tool completion, and
    rollout persistence.
    
    ## What changed
    - Add `session_task.run` spans around task execution and
    `session_task.flush_rollout` around flushing pending conversation
    transcript writes to durable storage
    - Add `regular_task.prepare_run_turn` around regular-turn startup (Send
    the `TurnStarted` event, reset turn-specific reasoning state, and
    resolve any startup prewarm)
    - Add `startup_prewarm.resolve` around waiting for background session
    prewarming to finish, fail, time out, or be cancelled
    - Add a function-level trace span around draining in-flight tool calls
    (Wait for tool calls to complete, record tool result in conversation
    history, and other bookkeeping)
    
    ## Verification
    Trigger Codex rollout and observe new spans are included
  • Include thread id in token budget context (#27663)
    ## Why
    
    The token budget full-context fragment identifies the current context
    window, but not the thread that owns that window. Including the thread
    id makes the initial context-window metadata self-contained, and
    `get_context_remaining` also needs to be usable from Code Mode without
    forcing callers to parse the model-facing fragment string.
    
    ## What changed
    
    - Include the session thread id in the initial `<token_budget>` context
    fragment.
    - Expose `get_context_remaining` as a Code Mode nested tool while
    keeping `new_context` direct-model-only.
    - Keep direct model-facing `get_context_remaining` output as the
    existing `<token_budget>` text fragment.
    - Return only `tokens_left` from the Code Mode structured result for
    `get_context_remaining`.
    - Update token-budget integration tests and add Code Mode coverage for
    the structured result.
    
    ## Verification
    
    - `just test -p codex-core token_budget`
    - `just test -p codex-core
    code_mode_get_context_remaining_returns_structured_result`
    - `just test -p core_test_support redacted_text_mode_normalizes_uuids`
  • [codex] Load user instructions through an injected provider (#27101)
    ## Why
    
    We want to remove implicit use of `$CODEX_HOME` from `codex-core` and
    make embedders responsible for supplying user-level instructions. This
    also ensures user instructions load when no primary environment is
    selected.
    
    ## What changed
    
    Stacked on #27415, which makes `codex exec` surface thread-scoped
    runtime warnings.
    
    - Added `UserInstructionsProvider` to `codex-extension-api`, with
    absolute source attribution and recoverable loading warnings.
    - Added `codex-home` with the filesystem-backed provider for
    `AGENTS.override.md` and `AGENTS.md`, preserving precedence, fallback,
    trimming, lossy UTF-8 handling, and the existing uncapped global
    instruction size.
    - Removed global instruction loading from `Config` and require
    `ThreadManager` callers to inject a provider.
    - Load provider instructions once for each fresh root runtime, including
    runtimes without a primary environment. Running sessions retain their
    snapshot, while child agents inherit the parent snapshot without
    invoking the provider.
    - Keep provider instructions separate while loading project `AGENTS.md`,
    then assemble the model-visible instructions with the existing ordering,
    source attribution, warning, and turn-context behavior.
    - Wired the Codex home provider through the CLI, app server, MCP server,
    core facade, and thread-manager sample.
    
    ## Validation
    
    - `just test -p codex-home -p codex-extension-api`
    - `just test -p codex-core agents_md`
    - `just test -p codex-core guardian`
    - `just test -p codex-app-server
    thread_start_without_selected_environment_includes_only_global_instruction_source`
    - `just test -p codex-exec warning`
    - `just bazel-lock-check`
  • skills: make backend plugin skills invocable without an executor (#27387)
    ## Why
    
    #27198 made the extension-owned `codex_apps` MCP connection the hosted
    plugin runtime, but its `mcp/skill` resources still bypassed the skills
    extension. App-server could list and read those resources through
    generic MCP APIs, but a thread with no selected environment did not
    expose them in the model's skills catalog or load their `SKILL.md`
    through `$skill`.
    
    Hosted skills should stay remote while using the same typed catalog,
    source authority, deduplication, bounded contextual catalog, and
    selected-skill prompt injection as host and executor skills. They should
    not be downloaded or exposed as ambient filesystem paths.
    
    ## What changed
    
    - Add a session-scoped `McpResourceClient` over the replaceable MCP
    connection manager so resource list/read calls follow startup and
    refresh replacements.
    - Add a `BackendSkillProvider` that pages `codex_apps` resources,
    accepts bounded and validated `mcp/skill` entries, and reads a selected
    skill's `SKILL.md` through the same MCP connection.
    - Register the remote provider in app-server and include it in the
    skills catalog even when a thread has no selected capability roots or
    executor.
    - Contribute hosted skill metadata through the bounded
    `AvailableSkillsInstructions` developer-context path, exclude remote
    entries from per-turn catalog injection, and classify `<skills>`
    messages as contextual developer content so rollback can trim and
    rebuild them correctly.
    
    ## Testing
    
    - Extend the app-server MCP resource integration test with
    `environments: []` to exercise two-page discovery, filter a
    non-`mcp/skill` resource, verify the escaped developer catalog entry and
    user-role `<skill>` fragment containing the fetched `SKILL.md`, and
    preserve generic MCP resource reads.
    - Add core event-mapping coverage that classifies `<skills>` developer
    messages as contextual history.
  • [codex] Compact when comp_hash changes (#27520)
    ## Summary
    - snapshot `comp_hash` into `TurnContext` when the turn is created and
    use that snapshot as the downstream source of truth
    - persist the turn hash in rollout context and recover it into
    previous-turn settings during resume and fork replay
    - compact existing history with the previous model only when both
    adjacent turns provide hashes and the values differ
    - record `comp_hash_changed` as the compaction reason
    - cover ordinary transitions, resume, and missing-hash compatibility
    with end-to-end tests
    
    ## Why
    History produced under one compaction-compatible model configuration may
    not be safe to carry directly into another. Compacting at the turn
    boundary converts that history before context updates and the new user
    message are added. Persisting the turn snapshot in `TurnContextItem`
    makes the same protection work after resuming a rollout.
    
    A missing hash is not treated as evidence of incompatibility. `None →
    Some`, `Some → None`, and `None → None` do not trigger compaction; only
    `Some(previous) → Some(current)` with unequal values does.
    
    ## Stack
    - depends on #27532
    - #27532 is based directly on `main`
    
    ## Testing
    - `just test -p codex-core pre_sampling_compact_` — 6 passed
    - `just test -p codex-core
    turn_context_item_uses_turn_context_comp_hash_snapshot` — passed
    - `just fix -p codex-core -p codex-protocol -p codex-analytics -p
    codex-models-manager`
  • [codex] Add new context window tool (#27488)
    ## Why
    
    The token budget feature tells the model how much room remains in the
    current context window. When the model decides the current window is no
    longer useful, it needs a way to ask Codex to start over with a fresh
    context window without spending tokens on a compaction summary.
    
    This PR adds that model-requestable escape hatch on top of #27438.
    
    ## What changed
    
    - Added a direct-model-only `new_context` tool behind
    `Feature::TokenBudget`.
    - Stores the tool request on `AutoCompactWindow` and consumes it after
    sampling so the next follow-up request in the same turn starts in the
    new window.
    - Starts the new window as a no-summary compaction checkpoint that
    contains only fresh initial context, not preserved conversation history.
    - Keeps the new window aligned with token-budget startup context,
    including the `Current context window Z` message.
    - Added integration coverage and a snapshot showing the same-turn
    `new_context` flow into a fresh full-context follow-up request.
    
    ## Validation
    
    - `just test -p codex-core token_budget`
  • [codex] Add token budget context feature (#27438)
    ## Why
    
    The model should be able to see bounded context-window budget metadata
    when the `token_budget` feature is enabled. The full-window message is
    only injected with full context, while normal turns get a smaller
    follow-up only when reported usage first crosses a budget threshold.
    
    ## What changed
    
    - Added the `TokenBudget` feature flag.
    - Added `<token_budget>` developer fragments for full context-window
    metadata and current-window remaining tokens.
    - Inserted the threshold message during normal turn handling by
    comparing token usage before and after sampling, avoiding persistent
    threshold bookkeeping.
    - Added core integration coverage for full-context-only metadata and
    25/50/75 percent threshold messages.
    
    ## Verification
    
    - `just test -p codex-core token_budget`
    - `git diff --check`
  • core: resize all history images behind a feature flag (#27247)
    ## Summary
    
    Adds complete client-side image preparation behind the default-off
    `resize_all_images` feature flag.
    
    When enabled, local image producers defer decoding and resizing. Images
    are prepared centrally before insertion into conversation history,
    covering user input, `view_image`, and structured tool-output images.
    
    ## Behavior
    
    - Processes base64 `data:` images in messages and function/custom tool
    outputs.
    - Leaves non-data URLs, including HTTP(S) URLs, unchanged.
    - Applies image-detail budgets:
      - `high` and omitted: 2048px maximum dimension and 2.5K 32px patches.
      - `original`: 6000px maximum dimension and 10K 32px patches.
      - `auto`: uses the same 2048px / 2.5K-patch budget as high.
      - `low`: unsupported and replaced with an actionable placeholder.
    - Preserves original image bytes when no resize or format conversion is
    needed.
    - Enforces the shared 1 GiB encoded and decoded data-URL sanity limits.
    - Replaces only an image that fails preparation, preserving sibling
    content and tool-output metadata.
    - Uses bounded placeholders distinguishing generic processing failures,
    oversized images, and unsupported `low` detail.
    - Prepares resumed and forked history before installing it as live
    history without modifying persisted rollouts.
    
    ## Flag-Off Behavior
    
    When `resize_all_images` is disabled:
    
    - Existing local user-input and `view_image` processing remains
    unchanged.
    - Existing decoding and error behavior remains unchanged.
    - Arbitrary tool-output images are not processed.
    - HTTP(S) image URLs continue to be forwarded unchanged.
    
    
    #### [git stack](https://github.com/magus/git-stack-cli)
    -  `1` https://github.com/openai/codex/pull/27245
    - 👉 `2` https://github.com/openai/codex/pull/27247
    -  `3` https://github.com/openai/codex/pull/27246
    -  `4` https://github.com/openai/codex/pull/27266
  • Forward standalone assistant output to realtime (#27319)
    ## Why
    
    When a realtime session is open without an active frontend-model
    handoff, completed Codex assistant messages are currently dropped. That
    prevents the frontend model from hearing orchestrator preambles and
    final responses produced by typed turns or other non-handoff work, which
    makes the two models present as disconnected personas.
    
    Active handoffs already forward each completed assistant message,
    including preambles. This change leaves those V1 and V2 paths intact and
    fills only the no-active-handoff gap.
    
    ## What changed
    
    - Send standalone V1 assistant messages through
    `conversation.handoff.append` with a stable synthetic handoff ID
    - Send standalone V2 assistant messages as normal `[BACKEND]`
    `conversation.item.create` message items, then enqueue `response.create`
    so the frontend model responds
    - Preserve the existing active V1 and V2 transport and completion
    behavior
    - Continue excluding user messages from realtime mirroring
    - Skip empty output and cap each complete context injection, including
    its V2 prefix, at 1,000 tokens
    - Add end-to-end coverage for both wire formats, V2 response creation,
    preambles, final responses, and truncation
    
    ## Test plan
    
    - CI
  • [codex] Store compact window id in rollout (#27264)
    ## Why
    
    Compaction window identity is part of session history, not model-client
    transport state. Persisting it with the compacted rollout item lets
    resumed threads continue from the reconstructed window without keeping
    mutable window state on `ModelClient`.
    
    ## What changed
    
    - Added `window_id` to `CompactedItem` and stamp it when
    `replace_compacted_history` installs compacted history.
    - Moved auto-compact window id ownership into `AutoCompactWindow` /
    `SessionState`; `ModelClient` now receives the request window id from
    callers instead of storing it.
    - Returned `window_id` from rollout reconstruction for resume.
    Reconstruction uses the newest surviving compacted item's stored
    `window_id` when present, and falls back to the legacy compacted-item
    count when it is absent.
    - Kept fork startup at the fresh default window id and updated direct
    model-client tests to pass explicit test window ids.
    
    ## Validation
    
    - `cargo check -p codex-core --tests`
  • Use latest-wins MCP manager replacement (#27259)
    ## Summary
    
    We originally addressed startup prewarming holding the read side of
    `RwLock<McpConnectionManager>` by snapshotting tool-list state. Review
    feedback identified the broader ownership problem: the outer
    synchronization should only publish or retrieve the current manager,
    while MCP operations rely on the manager's internal synchronization. A
    follow-up preserved operation retirement with a separate gate, but
    further review questioned whether that synchronization was actually
    required and whether we could support latest-wins replacement instead.
    
    This PR now stores the current MCP manager in `ArcSwap`. Each operation
    uses `load_full()` to obtain an owned `Arc<McpConnectionManager>`, then
    performs MCP I/O without retaining the publication mechanism. Refresh
    cancels obsolete startup work, constructs a replacement, and atomically
    publishes it. New operations see the latest manager, while operations
    that already loaded the previous manager retain a valid handle. Refresh
    happens at a turn boundary, so there should be no active user tool calls
    to drain.
    
    Git history supports dropping the outer `RwLock`. It was introduced in
    `03ffe4d595` on November 17, 2025 for non-blocking MCP startup: the
    session published an empty manager, startup initialized that same object
    while holding the write lock, and readers waited for initialization.
    `7cd2e84026` on February 19, 2026 removed that two-phase initialization
    in favor of constructing a fresh manager and swapping it in, explicitly
    noting that `Option` or `OnceCell` could replace the placeholder design.
    Hot reload later reused the existing lock to publish a replacement, but
    I found no indication that the lock was introduced to guarantee
    in-flight tool calls finish before refresh or shutdown.
    
    Terminal shutdown remains separate from refresh: it aborts startup
    prewarming and active tasks before shutting down the current manager, so
    tool calls may be interrupted and no model WebSocket work continues
    after shutdown. Focused regression coverage exercises pending tool-list
    cancellation, deferred refresh, and startup-prewarm shutdown.
  • Add spans to run_turn (#27107)
    ## Why
    Codex app-server latency traces do not granularly cover turn
    orchestration, sampling-request preparation, and tool-loading work.
    These spans help separate local coordination/setup costs from model
    streaming and tool execution.
    
    ## What changed
    - Add `run_turn.*` spans around sampling-request input preparation and
    post-sampling state collection
    - Add function-level trace spans around turn setup, hook execution,
    compaction, prompt construction, and MCP tool exposure
    - Add `built_tools.*` spans around plugin loading and discoverable-tool
    loading
    
    ## Verification
    Trigger Codex rollout and observe new spans are included
  • Stop mirroring Codex user input into realtime (#27116)
    ## Why
    
    The realtime frontend model and the backing Codex thread should present
    one coherent assistant. Raw typed messages, steers, and worker reports
    belong to the orchestrator; the frontend model should receive the
    orchestrator's user-facing result rather than a second copy of those
    inputs.
    
    Today normal `turn/start` input is automatically inserted into the
    realtime conversation, while `turn/steer` is not. Besides creating
    inconsistent context, this can make the frontend model react
    independently before Codex has produced the response it should speak.
    
    ## What changed
    
    - Remove automatic accepted-user-input mirroring into realtime
    - Remove the mirror-only echo-suppression flag and dead V2 prefix helper
    - Preserve explicit app-to-realtime text injection and FEM-to-Codex
    delegation
    - Replace the positive mirror tests and obsolete snapshots with a
    negative routing regression test
    
    ## Test plan
    
    - `cargo test -p codex-core
    conversation_user_text_turn_is_not_sent_to_realtime`
    - `cargo test -p codex-core
    conversation_startup_context_is_truncated_and_sent_once_per_start`
    - `cargo test -p codex-core inbound_handoff_request_starts_turn`
  • Load selected executor skills through extensions (#27184)
    ## Why
    
    CCA is moving toward a split runtime where the orchestrator may not have
    a filesystem, while executors can expose preinstalled plugins and
    skills. A thread therefore needs to select capabilities without asking
    app-server or core to interpret executor-owned paths through the
    orchestrator's filesystem.
    
    The longer-term model is broader than executor skills:
    
    - A plugin is a bundle of skills, MCP servers, connectors/apps, and
    hooks.
    - A plugin root can be local, executor-owned, or hosted by a backend.
    - Components inside one plugin can use different access and execution
    mechanisms. A skill may be read from a filesystem or through backend
    tools; an HTTP MCP server can run without an executor; a stdio MCP
    server or hook needs an execution environment.
    - Core should carry generic extension initialization data. The extension
    that owns a component should discover it, expose it to the model, and
    invoke it through the appropriate runtime.
    
    This PR establishes that architecture through one complete vertical:
    selecting a root on an executor, discovering the skills beneath it,
    exposing those skills to the model, and reading an explicitly invoked
    `SKILL.md` through the same executor.
    
    ## Contract
    
    `thread/start` gains an experimental `selectedCapabilityRoots` field:
    
    ```json
    {
      "selectedCapabilityRoots": [
        {
          "id": "deploy-plugin@1",
          "location": {
            "type": "environment",
            "environmentId": "workspace",
            "path": "/opt/codex/plugins/deploy"
          }
        }
      ]
    }
    ```
    
    The root is intentionally not classified as a "plugin" or "skill" in the
    API. It can point at a standalone skill, a directory containing several
    skills, or a plugin containing skills and other components. This PR only
    teaches the skills extension how to consume it; later extensions can
    resolve MCP, connector, and hook components from the same selection.
    
    The platform-supplied `id` is stable selection identity. The location
    says which runtime owns the root and gives that runtime an opaque path.
    App-server does not inspect or canonicalize the path.
    
    ## What changed
    
    ### Generic thread extension initialization
    
    App-server converts selected roots into `ExtensionDataInit`. Core
    carries that generic initialization value until the final thread ID is
    known, then creates thread-scoped `ExtensionData` before lifecycle
    contributors run.
    
    This keeps `Session` and core independent of the capability-selection
    contract. The initialization value is consumed during construction; it
    is not retained as another long-lived `Session` field.
    
    ### Executor-backed skills
    
    The skills extension now owns an `ExecutorSkillProvider` that:
    
    - resolves the selected environment through `EnvironmentManager`
    - discovers, canonicalizes, and reads skills through that environment's
    `ExecutorFileSystem`
    - contributes the bounded selected-skill catalog as stable developer
    context
    - reads an explicitly invoked skill body through the authority that
    listed it
    - warns when an environment or root is unavailable
    - never falls back to the orchestrator filesystem for an executor-owned
    root
    
    Skill catalog and instruction fragments have hard byte bounds, which
    also bound them below the 10K-token per-item context limit. If a
    selected executor skill has the same name as a legacy local skill, the
    executor selection owns that invocation and the local body is not
    injected a second time.
    
    Existing local and bundled skill loading remains in place. Omitting
    `selectedCapabilityRoots` therefore preserves current local-only
    behavior.
    
    ## Current semantics
    
    - Only environment-owned locations are represented in this first
    contract.
    - Roots are resolved by the destination extension, not by app-server or
    core.
    - An unavailable executor or invalid root produces a warning and no
    capabilities from that root; it does not trigger a local-filesystem
    fallback.
    - Selection applies to a newly started active thread.
    - MCP servers, connectors, and hooks beneath a selected plugin root are
    not activated yet.
    - Selection is not yet persisted or inherited across resume, fork, or
    subagent creation. Existing local capabilities continue to behave as
    they do today in those flows.
    
    ## Planned vertical follow-ups
    
    1. **Hosted HTTP MCP:** add an extension-backed HTTP MCP source that
    works without an executor, then replace the special-purpose MCP plugins
    loader with that implementation.
    2. **Executor MCP:** register and execute stdio MCP servers through the
    environment that owns the selected plugin root.
    3. **Backend skills:** add a hosted skill source whose catalog and
    bodies are accessed through extension tools rather than a filesystem.
    4. **Connectors and hooks:** activate those components through their
    owning extensions, using the same selected-root boundary and
    component-specific runtime.
    5. **Durable selection:** define the desired-selection lifecycle,
    persist it, and make resume, fork, and subagent inheritance explicit
    rather than accidental.
    6. **Local convergence:** incrementally route existing local plugin,
    skill, and MCP loading through the same extension model while preserving
    current local behavior.
    
    Each follow-up remains reviewable as an end-to-end capability. The
    platform selects roots, generic thread extension data carries the
    selection, and the owning extension resolves and operates its component.
    
    ## Verification
    
    Coverage added for:
    
    - app-server end-to-end discovery and explicit invocation of a skill
    inside an executor-selected plugin root
    - exclusive invocation when a selected executor skill collides with a
    local skill name
    - executor filesystem authority for discovery, canonicalization, and
    reads
    - thread extension initialization before lifecycle contributors run
    - stable executor catalog context, explicit invocation, context
    rebuilding, hidden skills, and preserved host/remote catalog behavior
    
    Targeted protocol, core-skills, skills-extension, core lifecycle, and
    app-server executor-skill tests were run during development.
  • [codex] Remove remote compaction failure log (#27106)
    ## Why
    
    `log_remote_compact_failure` was the only consumer of the
    compact-request logging payload and most of the token-usage breakdown
    fields. Once that failure log is removed, keeping the surrounding
    carrier types leaves dead plumbing in the compaction path and context
    manager.
    
    ## What changed
    
    - Remove `log_remote_compact_failure`, `CompactRequestLogData`, and the
    v2 wrapper that only fed that log.
    - Let both remote compaction implementations return the original
    compaction error directly.
    - Replace `TotalTokenUsageBreakdown` with a narrow helper that returns
    only the remaining value needed by compaction analytics.
    - Keep `estimate_response_item_model_visible_bytes` private to the
    context manager implementation.
    
    ## Validation
    
    - `cargo check -p codex-core`
  • Pair thread environment settings (#26687)
    ## Why
    
    Thread cwd and environment selections are a single logical setting in
    core: updating one without the other can silently desynchronize the
    next-turn execution context. This change makes that relationship
    explicit in the internal thread settings flow while preserving the
    existing app-server public API shape.
    
    ## What changed
    
    - Moved the cwd/environment pair through internal
    `ThreadSettingsOverrides.environment_settings` instead of a top-level
    internal `cwd` field.
    - Kept `thread/settings/update` public params unchanged, with app-server
    translating top-level `cwd` into the paired internal settings shape.
    - Moved `Op::UserInput` environment overrides into thread settings so
    user turns and settings updates use the same core path.
    - Updated core, app-server, MCP, memories, sample, and test callsites to
    construct the paired settings shape.
    
    ## Verification
    
    - `git diff --check`
    - Local test run starting after PR creation.
  • Require absolute cwd in thread settings (#26532)
    ## Why
    
    Thread settings cwd overrides are expected to be resolved before they
    enter core. Keeping this boundary as a plain `PathBuf` made it easy for
    core/session code to keep fallback normalization and relative-path
    resolution logic in places that should only receive an already-resolved
    cwd.
    
    This is intentionally the absolute-cwd-only slice: it does not change
    environment selection stickiness or cwd-to-default-environment fallback
    behavior.
    
    ## What changed
    
    - Changes `ThreadSettingsOverrides.cwd`,
    `CodexThreadSettingsOverrides.cwd`, and `SessionSettingsUpdate.cwd` to
    use `AbsolutePathBuf`.
    - Removes core-side cwd normalization/resolution from session settings
    updates.
    - Updates affected core/app-server test helpers and callsites to pass
    existing absolute cwd values or use `abs()` helpers.
    
    ## Validation
    
    Opening as draft so CI can start while local validation continues.
  • [codex] Forward turn moderation metadata through app-server (#25710)
    ## Why
    First-party backends can supply turn-scoped moderation metadata that
    app-server clients need for client-side presentation. Exposing this as
    an experimental typed notification lets opted-in clients consume it
    without interpreting raw Responses API events.
    
    ## What changed
    - forward `response.metadata.openai_chatgpt_moderation_metadata` from
    Responses API SSE and WebSocket streams as turn-scoped moderation
    metadata
    - emit the experimental app-server v2 `turn/moderationMetadata`
    notification with `{ threadId, turnId, metadata }`
    - add app-server integration coverage for the typed moderation metadata
    notification
    
    ## Testing
    - `just test -p codex-core
    build_ws_client_metadata_includes_window_lineage_and_turn_metadata`
    - `just test -p codex-core` (fails locally: 46 failures and 1 timeout,
    primarily missing `test_stdio_server` and shell snapshot timeouts)
    - `just test -p codex-app-server-protocol`
    - `just test -p codex-app-server
    turn_moderation_metadata_emits_typed_notification_v2`
    - `just test -p codex-app-server` (fails locally: 792 passed, 10 failed,
    and 5 timed out; failures are in existing environment-sensitive tests,
    primarily because nested macOS `sandbox-exec` is not permitted)
    - `just write-app-server-schema --experimental --schema-root
    /tmp/codex-app-server-schema-experimental`
  • [codex] Support model-defined reasoning efforts (#26444)
    ## Summary
    - accept non-empty model-defined reasoning effort values while
    preserving built-in effort behavior
    - propagate the non-Copy effort type through core, app-server, TUI,
    telemetry, and persistence call sites
    - preserve string wire encoding and expose an open-string schema for
    clients
    - update model selection and shortcut behavior for model-advertised
    effort values
    
    ## Root cause
    `ReasoningEffort` gained a string-backed custom variant, so it could no
    longer implement `Copy` or rely on derived closed-enum serialization.
    Existing consumers still moved effort values from shared references and
    assumed a fixed built-in value set.
    
    ## Validation
    - `just fmt`
    - Local tests and compilation were not run per request; relying on CI.
  • Route AGENTS.md loading through environment filesystems (#26205)
    ## Why
    
    Workspace-specific `AGENTS.md` loading needs to use the selected
    environment filesystem so remote workspaces and child agents read
    instructions from their actual environment instead of the host
    filesystem. The app-server should report the same instruction sources
    the initialized thread actually loaded, rather than independently
    rescanning configuration and filesystem state.
    
    ## What changed
    
    - Introduce `LoadedAgentsMd` to retain ordered user, project, and
    internal instructions with their provenance.
    - Load and canonicalize workspace `AGENTS.md` paths through the primary
    `EnvironmentManager` environment, then render the loaded instructions
    when constructing turn context.
    - Expose cached loaded instruction sources from initialized threads and
    use them for app-server start, resume, and fork responses.
    - Preserve global `CODEX_HOME` loading and separator behavior while
    excluding empty project files that did not supply model-visible
    instructions.
    - Add integration coverage for CLI injection, selected-environment
    provenance and rendering, empty environment selection, and cached
    sources on loaded-thread resume.
    
    ## Validation
    
    - `just test -p codex-core agents_md`
    - `just test -p codex-core
    selected_environment_sources_match_model_visible_instructions`
    - `just test -p codex-exec agents_md`
    - `just test -p codex-app-server instruction_sources`
    - `just test -p codex-app-server --status-level fail`
  • [codex-analytics] emit forked thread id on initialization (#26248)
    ## Why
    - Thread initialization analytics do not identify the source thread for
    forked threads.
    - The session viewer needs this lineage to construct thread trees.
    - Depends on openai/openai#987854. Do not release this change before
    that backend schema change is deployed.
    
    ## What Changed
    - Adds optional `forked_from_thread_id` to `codex_thread_initialized`.
    - Populates it from the existing thread fork lineage for app-server and
    in-process subagent initialization paths.
    - Keeps it null for non-forked threads.
    
    ## Verification
    - `just fmt`
    - `just test -p codex-analytics`
    - `just test -p codex-app-server
    thread_fork_tracks_thread_initialized_analytics`
  • feat: catalog multi-agent v2 config (#26254)
    ## Why
    
    Model metadata can now select multi-agent v2 even when a user has not
    enabled `features.multi_agent_v2` in their config. Some existing configs
    still set the legacy `agents.max_threads` knob for v1 multi-agent
    behavior, so treating every v2 runtime as incompatible with
    `agents.max_threads` would break users whose only v2 signal came from
    the model catalog.
    
    The incompatible configuration is specifically enabling
    `features.multi_agent_v2` while also setting `agents.max_threads`.
    Catalog-forced v2 should use the v2 concurrency setting and ignore the
    legacy v1 cap instead of rejecting the config.
    
    ## What changed
    
    - Split config validation from runtime concurrency calculation:
    `effective_agent_max_threads` now just returns the effective cap for the
    resolved multi-agent runtime.
    - Added explicit validation for `features.multi_agent_v2` +
    `agents.max_threads` at session startup.
    - Preserved catalog-selected v2 behavior when `features.multi_agent_v2`
    is disabled, so existing configs with `agents.max_threads` keep
    starting.
    - Updated model-runtime selector coverage so a catalog v2 model still
    exposes v2 tools even when `agents.max_threads` is set and the config
    flag is disabled.
    
    ## Validation
    
    - `cargo check -p codex-core --lib`
    - `just test -p codex-core --lib -E
    "test(multi_agent_v2_feature_rejects_agents_max_threads) |
    test(catalog_v2_allows_agents_max_threads_when_feature_disabled)"`
  • chore: mechanical rename (#26156)
    Rename `Session::conversation_id` to `Session::thread_id` with an auto
    refactor in RustRover
  • Propagate permission approval environment id (#25862)
    ## Stack
    
    1. #25850 - Key request-permission grants by environment: stores and
    applies sticky permission grants per environment id.
    2. #25858 - Add `environmentId` to `request_permissions`: lets the model
    target a selected environment and resolves relative permission paths
    against it.
    3. This PR (#25862) - Propagate permission approval environment id:
    carries the selected environment id through approval events, app-server
    requests, TUI prompts, and delegate forwarding.
    4. #25867 - Add remote request permissions integration coverage:
    verifies the selected remote environment across request, approval, grant
    reuse, and exec.
    
    This PR is stacked on #25858, and #25867 is stacked on this PR.
    
    ## Why
    
    PR2 lets the model bind a `request_permissions` call to a selected
    environment, but the approval event and client-facing request still
    needed to carry that binding. For CCA, the user-facing prompt and
    delegated approval path should know which environment the grant applies
    to instead of relying on cwd alone.
    
    ## What Changed
    
    - Added optional `environmentId` to `RequestPermissionsEvent`.
    - Emit the selected environment id from core permission approval events.
    - Preserve the environment id through delegate forwarding, including
    cwd-based delegated requests.
    - Added `environmentId` to app-server permission approval params,
    generated schema/TypeScript artifacts, and README examples.
    - Preserve and display the environment id in TUI permission approval
    prompts.
    - Updated focused core, app-server protocol, and TUI conversion
    coverage.
    
    ## Testing
    
    Not run locally per instruction. Performed read-only `git diff --check`.
  • Add environmentId to request_permissions (#25858)
    ## Stack
    
    1. #25850 - Key request-permission grants by environment: stores and
    applies sticky permission grants per environment id.
    2. This PR (#25858) - Add `environmentId` to `request_permissions`: lets
    the model target a selected environment and resolves relative permission
    paths against it.
    3. #25862 - Propagate permission approval environment id: carries the
    selected environment id through approval events, app-server requests,
    TUI prompts, and delegate forwarding.
    4. #25867 - Add remote request permissions integration coverage:
    verifies the selected remote environment across request, approval, grant
    reuse, and exec.
    
    This PR is stacked on #25850; #25862 and #25867 are stacked on this PR.
    
    ## Why
    
    PR1 made request-permission grants internally environment-keyed, but the
    model-facing `request_permissions` tool could still only target the
    primary environment. For CCA and multi-environment turns, the tool needs
    an explicit way to bind a permission request to a selected attached
    environment before resolving relative paths.
    
    ## What Changed
    
    - Added optional `environmentId` to `RequestPermissionsArgs`, with
    `environment_id` accepted as an alias.
    - Exposed `environmentId` in the `request_permissions` tool schema and
    description.
    - Resolve the selected environment before parsing filesystem permission
    paths, so relative paths bind to the selected environment cwd.
    - Route validated tool calls through
    `request_permissions_for_environment` directly instead of duplicating
    environment lookup in `Session::request_permissions`.
    - Reject unknown environment ids with a model-facing error.
    - Updated focused request-permissions and Guardian call sites for the
    new optional field.
    
    ## Testing
    
    Not run locally per instruction.
  • [codex-analytics] Track CodexErr details in turn analytics (#25707)
    ## Summary
    - add analytics-only `CodexErr` telemetry to `codex_turn_event` while
    leaving existing `turn_error` unchanged
    - record terminal `CodexErr` facts from core immediately before the
    existing turn error event is sent
    - emit source-truth `codex_error_*` fields for downstream analytics,
    including the raw `CodexErr::InvalidRequest(String)` message as
    `codex_error_subreason`
    
    ## Validation
    - `just test -p codex-analytics`
    - attempted `just test -p codex-core`, but the local run timed out
    across unrelated integration suites in this environment and is not being
    used as validation
  • Key request-permission grants by environment (#25850)
    ## Stack
    
    1. This PR (#25850) - Key request-permission grants by environment:
    stores and applies sticky permission grants per environment id.
    2. #25858 - Add `environmentId` to `request_permissions`: lets the model
    target a selected environment and resolves relative permission paths
    against it.
    3. #25862 - Propagate permission approval environment id: carries the
    selected environment id through approval events, app-server requests,
    TUI prompts, and delegate forwarding.
    4. #25867 - Add remote request permissions integration coverage:
    verifies the selected remote environment across request, approval, grant
    reuse, and exec.
    
    #25858, #25862, and #25867 are stacked on this PR and should be reviewed
    after it.
    
    ## Why
    
    Multi-environment CCA turns can attach both local and remote executors,
    but request-permission grants were still effectively cwd-only. Pending
    permission requests tracked a cwd, while stored turn/session grants had
    no environment identity, so sticky grants could be reused through the
    wrong executor context.
    
    This makes the first permission-grant step environment-aware without
    changing the external `request_permissions` payload shape: omitted
    environment targeting remains bound to the primary turn environment.
    
    ## What Changed
    
    - Store turn- and session-scoped request-permission grants by
    `environment_id`.
    - Keep the selected `TurnEnvironmentSelection` with pending
    `request_permissions` calls so approval responses normalize and record
    grants against the same environment.
    - Resolve relative `request_permissions` file paths against the primary
    turn environment cwd instead of deprecated `turn.cwd`.
    - Apply sticky grants in `shell`, `exec_command`, and `apply_patch` by
    selected environment id while still using the actual tool cwd for
    cwd-relative permission materialization.
    - Update Guardian and request-permissions coverage for the
    environment-keyed grant behavior.
    
    ## Testing
    
    Not run locally. Added or updated focused coverage for:
    
    - `request_permission_grants_are_environment_keyed`
    -
    `request_permissions_tool_resolves_relative_paths_against_primary_environment`
    - related Guardian/request-permissions sticky grant tests
  • Reduce stack pressure in session startup and config rebuilds (#25844)
    ## Why
    
    `/clear` starts a fresh thread with `InitialHistory::Cleared`, which
    re-enters the thread/session startup path. That path now builds large
    async futures through `ThreadManagerState::spawn_thread_with_source`,
    `Codex::spawn`, and `Session::new`. Separately, TUI config rebuilds for
    cwd and permission-profile changes build a similarly heavy
    `ConfigBuilder::build()` future inside the app task. In debug and Bazel
    runs, those call chains can put enough state on the caller stack to
    abort before startup or config refresh completes.
    
    This change keeps the behavior the same while moving the heaviest future
    frames off the caller stack.
    
    ## What changed
    
    - Box `Codex::spawn(...)` in `codex-rs/core/src/thread_manager.rs`
    before awaiting it from `spawn_thread_with_source`.
    - Box `Session::new(...)` in `codex-rs/core/src/session/mod.rs` before
    awaiting it from `Codex::spawn_internal`.
    - Route `ConfigBuilder::build()` through a small `tokio::spawn` helper
    in `codex-rs/tui/src/app/config_persistence.rs` so cwd and
    permission-profile config rebuilds run on a runtime worker stack while
    preserving error context.
    
    ## Verification
    
    CI is running on the PR.
    
    No new targeted tests were added. This is a mechanical stack-pressure
    reduction that keeps the existing behavior and error propagation intact.
  • Resolve per-thread multi-agent runtime (#25722)
    Stack split from #25708. Original PR intentionally left open. This third
    PR resolves the effective per-thread multi-agent runtime from persisted
    metadata, inherited runtime, and current model selection.
  • app-server: remove experimental persist_extended_history bool flag (#25712)
    ## Summary
    
    Remove the dead experimental `persistExtendedHistory` app-server flag
    and collapse rollout persistence to the single policy app-server already
    used.
    
    ## What Changed
    
    - Removed `persistExtendedHistory` from v2 thread start/resume/fork
    params and deleted its deprecation notice path.
    - Removed the persistence-mode enums and plumbing through core, rollout,
    and thread-store.
    - Made rollout filtering mode-free, keeping the existing limited
    persisted-history behavior.
    
    ## Test Plan
    
    - `just write-app-server-schema`
    - `cargo nextest run --no-fail-fast -p codex-app-server-protocol
    schema_fixtures`
    - `cargo nextest run --no-fail-fast -p codex-app-server
    thread_shell_command_history_responses_exclude_persisted_command_executions`
    - `cargo nextest run --no-fail-fast -p codex-rollout -p
    codex-thread-store`
    - final `rg` for removed flag/type names
  • feat: gate unified exec zsh fork composition (#24979)
    ## Why
    
    `shell_zsh_fork` and unified exec need to remain independently
    controllable for enterprise rollouts, but we also need a third mode that
    composes them. That composed mode is intended to preserve unified exec
    command lifecycle support while letting the zsh fork provide more
    accurate `execv(2)` interception.
    
    Enabling `unified_exec_zsh_fork` by itself is intentionally not
    sufficient. It is a composition gate, not a dependency-enabling
    shortcut:
    
    - `unified_exec` selects the PTY-backed unified exec tool.
    - `shell_zsh_fork` opts into the zsh fork backend.
    - `unified_exec_zsh_fork` only allows those two already-enabled modes to
    be composed so local zsh unified exec commands can launch through the
    zsh fork.
    
    This separation is deliberate. Enterprises and staged rollouts must be
    able to enable or disable unified exec and zsh-fork independently. If
    `unified_exec_zsh_fork` implied either dependency, then enabling one
    under-development composition flag would silently activate a shell
    backend that the configured feature set left disabled.
    
    This PR introduces only the configuration and planning gate for that
    composition. Existing `shell_zsh_fork` behavior continues to use the
    standalone shell tool unless the new composition feature is explicitly
    enabled alongside both dependencies.
    
    ## What Changed
    
    - Added the under-development feature flag `unified_exec_zsh_fork`.
    - Added `UnifiedExecFeatureMode` so the three input feature flags
    collapse into `Disabled`, `Direct`, or `ZshFork` mode before tool
    planning.
    - Updated tool selection so zsh-fork composition requires
    `unified_exec`, `shell_zsh_fork`, and `unified_exec_zsh_fork`.
    - Kept the existing standalone zsh-fork shell tool behavior when only
    `shell_zsh_fork` is enabled.
    - Updated config schema output for the new feature flag.
    
    ## Verification
    
    - Added feature and tool-config coverage for the new gate.
    - Added planner coverage proving `shell_zsh_fork` remains standalone
    until composition is explicitly enabled.
    - Ran focused tests for `codex-features`, `codex-tools`, and the
    affected `codex-core` planner case.
    
    
    
    
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/24979).
    * #24982
    * #24981
    * #24980
    * __->__ #24979
  • store and expose parent_thread_id on Threads (#25113)
    ## Why
    
    This PR
    https://github.com/openai/codex/pull/24161#discussion_r3325692763
    revealed a subagent data modeling issue, where we overloaded
    `forked_from_id` to also mean `parent_thread_id`. That's incorrect since
    guardian and review subagents can be a subagent and NOT fork the main
    thread's history.
    
    The solution here is to explicitly store a new `parent_thread_id` on
    `SessionMeta`, alongside `forked_from_id` which already exists. While
    we're at it, also expose it in the app-server protocol on the `Thread`
    object.
    
    A thread->subagent relationship and a fork of thread history are
    orthogonal concepts.
    
    ## What Changed
    
    - Added top-level `parent_thread_id` persistence on `SessionMeta` and
    runtime/session plumbing through `SessionConfiguredEvent`,
    `CodexSpawnArgs`, `SessionConfiguration`, `ThreadConfigSnapshot`,
    `TurnContext`, and `ModelClient`.
    - Made turn metadata, request headers, analytics, and subagent-start
    events read the separate runtime/top-level parent field instead of
    deriving general parent lineage from `SessionSource` or
    `forked_from_thread_id`.
    - Passed parent lineage separately at delegated subagent, review,
    guardian, agent-job, and multi-agent spawn construction sites;
    copied-history fork lineage remains derived only from `InitialHistory`.
    - Persisted and exposed parent lineage through rollout/thread-store
    projections and app-server v2 `Thread.parentThreadId`.
    - Updated app-server README text and regenerated app-server schema
    fixtures for the additive `parentThreadId` response field.
  • [codex] Add user input client ids (#24653)
    ## Summary
    
    Adds an optional `clientId` field to app-server v2 `UserInput` and
    carries it through the core `UserInput` model so clients can correlate
    echoed user input items without relying on payload equality.
    
    ## Details
    
    - Adds `client_id: Option<String>` to core `UserInput` variants.
    - Exposes the v2 app-server field as `clientId` on the wire and in
    generated TypeScript.
    - Preserves the id when converting between app-server v2 and core
    protocol types.
    - Regenerates app-server schema fixtures.
    
    ## Validation
    
    - `just fmt`
    - `just write-app-server-schema`
    - `cargo test -p codex-app-server-protocol`
    - `cargo test -p codex-protocol`
    - `just fix -p codex-app-server-protocol`
    - `just fix -p codex-protocol`
    - `git diff --check`
  • [codex] Remove redundant SQLite dynamic tool storage (#24819)
    ## Why
    
    Dynamic tools are defined at thread start and already stored in rollout
    `SessionMeta`, which restores resumed and forked sessions. Persisting
    the same tools through SQLite creates a second runtime persistence path
    that is unnecessary prework for the explicit namespace refactor.
    
    ## What changed
    
    - Restore missing thread-start dynamic tools directly from rollout
    history, including when SQLite is enabled.
    - Remove SQLite dynamic-tool reads, writes, backfill, and thread
    metadata patch plumbing.
    - Add SQLite-enabled resume integration coverage that verifies a
    rollout-defined dynamic tool is still sent after resume.
    
    ## Compatibility
    
    The existing `thread_dynamic_tools` table is intentionally not dropped
    even though it's now unused. Older Codex binaries are allowed to open
    databases migrated by newer binaries and still reference this table;
    dropping it would break that mixed-version path. See
    [here](https://github.com/openai/codex/blob/main/codex-rs/state/src/migrations.rs#L10-L11).
    
    ## Verification
    
    - `just test -p codex-state -p codex-rollout -p codex-thread-store`
    - `just test -p codex-core --test all
    resume_restores_dynamic_tools_from_rollout_with_sqlite_enabled`
  • [codex-analytics] add grouped session id to runtime events (#24655)
    ## Why
    - Runtime analytics events report `thread_id`, which identifies the
    individual thread emitting an event
    - They don't report `session_id`, which identifies the shared session
    for a root thread and its subagent threads
    - Emitting both identifiers allows analytics to group related activity
    
    ## What Changed
    - Adds `session_id` to relevant analytics events (thread_initalized,
    turn, turn_steer, compaction, guardian_review)
    - Tracks each thread's session ID in the analytics reducer so subsequent
    thread scoped events emit the same value
    - Carries the shared session ID through subagent initialization
    
    ## Verification
    - `just test -p codex-analytics` validates event payloads and subagent
    session grouping.
    - Focused `codex-app-server` tests validate session IDs for thread,
    turn, and steer events.
    - Focused `codex-core` tests validate root and subagent session ID
    propagation.
  • Add forked_from_thread_id turn metadata (#24160)
    ## Why
    
    When Codex calls responsesapi, we currently send `session_id`,
    `thread_id`, and `turn_id` among other things as
    `client_metadata["x-codex-turn-metadata"]`. This PR adds
    `forked_from_thread_id` which helps explain the "lineage" of a forked
    thread.
    
    ## What's changed
    
    - Track the immediate history source copied into a forked thread through
    thread/session creation, including subagent and review turn metadata
    paths.
    - Include `forked_from_thread_id` in Codex turn metadata while
    preventing turn-scoped Responses API client metadata from overwriting
    Codex-owned lineage fields.
    - Add coverage for fork lineage in turn metadata and the app-server
    Responses API request path.
  • Add experimental turn additional context (#24154)
    ## Summary
    
    Adds experimental `additionalContext` support to `turn/start` and
    `turn/steer` so clients can provide ephemeral external context, such as
    browser or automation state, without turning that plumbing into a
    visible user prompt or triggering user-prompt lifecycle behavior.
    
    ## API Shape
    
    The parameter shape is:
    
    ```ts
    additionalContext?: Record<string, {
      value: string
      kind: "untrusted" | "application"
    }> | null
    ```
    
    Example:
    
    ```json
    {
      "additionalContext": {
        "browser_info": {
          "value": "Active tab is CI failures.",
          "kind": "untrusted"
        },
        "automation_info": {
          "value": "CI rerun is in progress.",
          "kind": "application"
        }
      }
    }
    ```
    
    The keys are opaque and caller-defined.
    
    ## Context Injection
    
    When provided, accepted entries are inserted into model context as
    hidden contextual message items, not as visible thread user-message
    items.
    
    `kind: "untrusted"` entries are inserted with role `user`:
    
    ```text
    <external_${key}>${value}</external_${key}>
    ```
    
    `kind: "application"` entries are inserted with role `developer`:
    
    ```text
    <${key}>${value}</${key}>
    ```
    
    Values are not escaped. Each value is truncated to 1k approximate tokens
    before wrapping.
    
    For `turn/start`, accepted additional context is inserted before normal
    user input. For `turn/steer`, additional context is merged only when the
    steer includes non-empty user input; context-only steers still reject as
    empty input.
    
    ## Dedupe Strategy
    
    `AdditionalContextStore` lives on session state and stores the latest
    complete additional-context map.
    
    Each `turn/start` or non-empty `turn/steer` treats its
    `additionalContext` as the current complete set of values. Entries are
    injected only when the key is new or the exact entry for that key
    changed, including `value` or `kind`. After merging, the store is
    replaced with the provided map, so omitted keys are removed from the
    retained set and can be injected again later if reintroduced.
    
    Omitting `additionalContext`, passing `null`, or passing an empty object
    resets the store to empty and injects nothing.
    
    ## What Changed
    
    - Threads experimental v2 `additionalContext` through app-server into
    core turn start and steer handling.
    - Adds separate contextual fragment types for untrusted user-role
    context and application developer-role context.
    - Uses pending response input items so additional context can be
    combined with normal user input without treating it as prompt text.
    - Adds integration coverage for start/steer flow, role routing,
    dedupe/reset behavior, deletion/re-add behavior, hook-blocked input
    behavior, empty context-only steer rejection, external-fragment marker
    matching, and truncation.
  • [codex] Make active turn task singular (#24105)
    ## Why
    
    `ActiveTurn` already runs at most one task: starting a task requires
    that no task is present, and replacement aborts existing work first.
    Representing that state as an `IndexMap` leaves a multi-task shape for a
    single-task invariant and makes each lifecycle lookup operate like a
    collection lookup.
    
    The slot remains optional because goal continuation uses an empty active
    turn as a reservation while deciding whether to start continuation work.
    
    ## What changed
    
    - Replace `ActiveTurn.tasks` with `task: Option<RunningTask>`.
    - Update task abort/completion, session lookup and steering, input-queue
    matching, goal reservation, and network-approval lookup to operate on
    the singular slot.
    - Mutate the singular task slot directly instead of retaining
    collection-era add/remove/take helpers.
    - Record token usage on the completing active task span without a
    regular-task-only opt-in flag.
    
    ## Validation
    
    - `cargo test -p codex-core --lib session::tests::steer_input`
    - `cargo test -p codex-core --lib
    session::tests::abort_empty_active_turn_preserves_pending_input`
    - `cargo test -p codex-core --lib
    session::tests::queued_response_items_for_next_turn_move_into_next_active_turn`
    - `cargo test -p codex-core --lib
    session::tests::active_goal_continuation_runs_again_after_no_tool_turn`
    - `cargo test -p codex-core --lib
    session::tests::abort_regular_task_emits_turn_aborted_only`
    - `cargo test -p codex-core --lib session::input_queue::tests`
  • Remove plugin hooks feature flag (#22552)
    # Why
    
    This is a follow-up stacked on top of the `plugin_hooks` default-on
    change. Once we are comfortable making plugin hooks part of the normal
    plugin behavior, the separate feature flag stops buying us much and
    leaves extra branching/cache state behind.
    
    # What
    
    - remove the `PluginHooks` feature and generated config-schema entries
    - make plugin hook loading/listing follow plugin enablement directly
    - drop plugin-manager cache/state that only existed to distinguish
    hook-flag toggles
    - remove tests and fixtures that modeled `plugin_hooks = true/false`
  • Route MCP servers through explicit environments (#23583)
    ## Summary
    - route each configured MCP server through an explicit per-server
    `environment_id` instead of a manager-wide remote toggle
    - default omitted `environment_id` to `local`, resolve named ids through
    `EnvironmentManager`, and fail only the affected MCP server when an
    explicit id is unknown
    - keep local stdio on the existing local launcher path for now, while
    named-environment stdio uses the selected environment backend and
    requires an absolute `cwd`
    - allow local HTTP MCP servers to keep using the ambient HTTP client
    when no local `Environment` is configured; named-environment HTTP MCPs
    use that environment's HTTP client
    
    ## Validation
    - devbox Bazel build: `bazel build --bes_backend= --bes_results_url=
    //codex-rs/cli:codex //codex-rs/rmcp-client:test_stdio_server
    //codex-rs/rmcp-client:test_streamable_http_server`
    - devbox app-server config matrix with real `config.toml` /
    `environments.toml` files covering omitted local, explicit local,
    omitted local under remote default, explicit remote stdio, local HTTP
    without local env, explicit remote HTTP, local stdio without local env,
    unknown explicit env, and remote stdio without `cwd`