Commit Graph

118 Commits

  • [codex] Add auth mode to plugin manager constructor (#27652)
    ## Context
    
    Plugins can expose more than one way for Codex to use them: App
    connectors for ChatGPT/SIWC-backed sessions and MCP servers for API key
    login sessions. The broader goal is to make `PluginsManager` the place
    that understands which plugin surfaces should be visible for the current
    auth route, so callers do not each have to make that decision
    themselves.
    
    This PR is the small setup step for that work. It lets the plugin
    manager be created with the current `AuthMode`, which gives the followup
    auth routing PRs the information they need without relying on setter
    injection.
    
    ## Stack
    
    - PR1: #27652 seed plugin manager auth at construction.
    - PR2: #27459 route plugin surfaces by auth mode.
    - PR3: #27607 dedupe plugin MCP servers by App declaration name.
    - PR4: #27602 preserve plugin Apps in connector listings.
    - PR5: #27461 skip install-time plugin MCP OAuth for matching App
    routes.
    
    ## Summary
    
    - Let `PluginsManager::new_with_restriction_product` accept an initial
    `AuthMode`.
    - Keep `PluginsManager::new` behavior unchanged for ordinary callers.
    
    ## Validation
    
    ```bash
    cargo test -p codex-core-plugins plugins_manager_tracks_auth_mode
    cargo test -p codex-core list_tool_suggest_discoverable_plugins
    git diff --check
    ```
    
    ---------
    
    Co-authored-by: Xin Lin <xl@openai.com>
  • [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.
  • [codex] resolve environment shell metadata eagerly (#27709)
    ## Why
    
    Turn construction passed resolved environments through several layers
    while leaving the environment shell unresolved. As a result,
    model-visible environment context could fall back to the session shell
    instead of reporting the selected remote environment's shell.
    
    Resolve environment metadata at the turn-context boundary so each turn
    carries the shell that belongs to its selected environment. Keep request
    validation in app-server, where invalid selections can be returned as
    straightforward JSON-RPC errors without coupling core turn construction
    to that policy.
    
    ## What changed
    
    - resolve environment selections eagerly in
    `new_turn_context_from_configuration`
    - store the full resolved `Shell` on each `TurnEnvironment`
    - simplify the now-redundant resolved-environment constructor plumbing
    - keep duplicate and unknown-environment validation as a small
    app-server preflight
    - add a remote-environment integration test that runs a full
    `test_codex` turn and verifies the model-visible environment message
    reports `bash`
    
    ## Testing
    
    - `cargo check -p codex-core --test all -p codex-app-server`
    - `remote_test_env_exposes_bash_shell_to_model` on the Linux
    remote-executor harness
  • [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`
  • [codex] Pass auth mode to plugin manager (#27517)
    ## Summary
    - Add auth mode state to `PluginsManager`.
    - Sync the plugin manager auth mode when `ThreadManager` is created and
    when account auth changes.
    - Route plugin load outcomes through an auth-aware projection hook so
    follow-up plugin filtering can stay inside `core-plugins`.
    
    ## Motivation
    This prepares plugin capability loading to be configured by auth mode,
    such as hiding or exposing app/MCP-backed plugin surfaces based on
    whether the user is using ChatGPT auth or API-key auth, without leaking
    those details outside the plugin manager.
    
    ## Tests
    - `just fmt`
    - `just test -p codex-core-plugins`
    - `env -u CODEX_SANDBOX_NETWORK_DISABLED -u CODEX_SANDBOX just test -p
    codex-core thread_manager::tests`
    - `env -u CODEX_SANDBOX_NETWORK_DISABLED -u CODEX_SANDBOX just test -p
    codex-app-server`
  • Add app-server thread/delete API (#25018)
    ## Why
    
    Clients can archive and unarchive threads today, but there is no
    app-server API for permanently removing a thread. Deletion also needs to
    cover the full session tree: deleting a main thread should remove
    spawned subagent threads and the related local metadata instead of
    leaving orphaned rollout files, goals, or subagent state behind.
    
    ## What
    
    - Adds the v2 `thread/delete` request and `thread/deleted` notification,
    with the response shape kept consistent with `thread/archive`.
    - Implements local hard delete for active and archived rollout files.
    - Deletes the requested thread's state DB row as the commit point, then
    best-effort cleans associated state including spawned descendants,
    goals, spawn edges, logs, dynamic tools, and agent job assignments.
    - Updates app-server API docs and generated protocol schema/TypeScript
    fixtures.
  • Route hosted Apps MCP through extensions (#27191)
    ## Stack
    
    - Base: #27184
    - This PR is the second vertical and should be reviewed against
    `jif/external-plugins-1`, not `main`.
    
    ## Why
    
    CCA is moving toward a split runtime where the orchestrator may have no
    filesystem or executor, but it still needs to activate remotely hosted
    plugin components. HTTP MCP servers are the simplest complete example:
    they need configuration and host authentication, but they do not need an
    executor process.
    
    The Apps MCP endpoint is currently synthesized by a special-purpose
    loader inside the MCP runtime. That works locally, but it leaves hosted
    MCP activation outside the extension model being established in #27184.
    It also makes the Apps path a poor foundation for plugins whose skills,
    MCP servers, connectors, and hooks may come from different sources or
    execute in different places.
    
    This PR moves that one behavior behind an extension-owned contribution
    while preserving the existing local fallback. It deliberately does not
    introduce a generic plugin activation framework.
    
    ## What changed
    
    ### MCP extension contribution
    
    `codex-extension-api` gains an ordered `McpServerContributor` contract.
    A contributor returns typed `Set` or `Remove` overlays for MCP server
    configuration; later contributors win for the names they own.
    
    The contract stays at the existing MCP configuration boundary.
    Extensions do not create a second connection manager or transport
    abstraction.
    
    ### Hosted Apps MCP extension
    
    A new `codex-mcp-extension` contributes the reserved `codex_apps` server
    from the existing Apps feature, ChatGPT base URL, path override, and
    product SKU configuration.
    
    When `apps_mcp_path_override` is enabled for `https://chatgpt.com`, the
    resulting streamable HTTP endpoint is
    `https://chatgpt.com/backend-api/ps/mcp`. The existing ChatGPT-auth gate
    remains authoritative, so this server can run in an orchestrator-only
    process without being exposed for API-key sessions.
    
    ### One resolved runtime view
    
    `McpManager` now distinguishes three views:
    
    - **configured:** config- and plugin-backed servers before extension
    overlays;
    - **runtime:** configured servers plus host-installed extension
    contributions;
    - **effective:** runtime servers after auth gating and compatibility
    built-ins.
    
    App-server installs the hosted MCP extension and uses the runtime view
    for thread startup, refresh, status, threadless resource reads,
    connector discovery, and MCP OAuth lookup. This keeps
    `mcpServer/oauth/login` consistent with the servers exposed by the other
    MCP APIs. The hosted Apps server itself continues to use existing
    ChatGPT host authentication rather than MCP OAuth.
    
    ## Compatibility
    
    Hosts that do not install the MCP extension retain the existing Apps MCP
    synthesis path. This preserves current local-only, CLI, and
    standalone-host behavior while app-server exercises the extension path.
    
    Disabling Apps removes the reserved `codex_apps` entry, and losing
    ChatGPT auth removes it from the effective runtime view. Executor
    availability is not consulted for this HTTP transport.
    
    ## Follow-ups
    
    The next vertical will resolve a manifest-declared stdio MCP server from
    an executor-selected plugin root and execute it in the environment that
    owns that root. Later verticals can add backend-owned skills, connector
    metadata, hooks, durable selection semantics, and incremental local
    convergence without changing the component-specific runtime boundaries
    introduced here.
    
    ## Verification
    
    Focused coverage was added for:
    
    - contributing the hosted Apps MCP at `/backend-api/ps/mcp` without an
    executor;
    - requiring ChatGPT auth in the effective runtime view;
    - removing a reserved configured Apps server when the Apps feature is
    disabled.
    
    `cargo check -p codex-app-server -p codex-mcp-extension -p
    codex-extension-api -p codex-mcp` passed. Tests and Clippy were not run
    locally under the current development instruction; CI provides the full
    validation pass.
  • 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.
  • [2 of 2] Finish moving goal runtime to extension (#26548)
    ## Stack
    
    1. [#26547](https://github.com/openai/codex/pull/26547) - [1 of 2] Align
    goal extension with core behavior
    2. [#26548](https://github.com/openai/codex/pull/26548) - [2 of 2] Move
    goal runtime to extension
    
    ## Why
    
    This PR completes the switch of the goal behavior to the
    extension-backed runtime and removes the old core goal implementation.
    
    ## What Changed
    
    - Installs the goal extension for app-server `ThreadManager` sessions.
    - Routes app-server thread goal `get`, `set`, and `clear` through
    `GoalService`.
    - Uses thread-idle lifecycle emission after goal resume and snapshot
    ordering so the extension can decide whether to continue the goal.
    - Forwards extension goal updates through a FIFO async app-server
    notification path so backpressure does not drop them or reorder updates.
    - Keeps review turns from enabling goal runtime behavior.
    - Plans extension tools before dynamic tools so built-in goal tool names
    keep their old precedence when goals are enabled.
    - Removes the old core goal runtime, core goal tool handlers, and core
    goal tool specs.
    - Updates tests that were coupled to the core-owned goal runtime while
    leaving the legacy `<goal_context>` compatibility path in core for old
    threads.
    - Removes the stale cargo-shear ignore now that `codex-goal-extension`
    is used by the workspace.
    - Keeps realtime event matching exhaustive after removing the old
    goal-specific realtime text path.
    
    
    ## Validation
    
    - Ran manual `/goal` runs in TUI. Validated time accounting matched
    wall-clock time and goal lifecycle state transitions.
  • 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
  • Compress cold local rollouts (#25089)
    ## Rollout compression stack
    
    This stack splits #24941 into reviewable steps for local rollout
    compression. The design is intentionally staged:
    
    1. Teach readers, listing, search, and lookup to understand compressed
    rollouts.
    2. Make append and resume paths materialize compressed rollouts back to
    plain JSONL before writing.
    3. Add a disabled-by-default worker that can compress cold archived
    rollouts behind `local_thread_store_compression`.
    
    The key invariant is that writers append to plain `.jsonl`. A
    `.jsonl.zst` file is a cold/read representation; if a write is needed,
    the compressed file is materialized back to plain JSONL first. Readers
    prefer plain `.jsonl` when both forms exist and can fall back to the
    compressed sibling during transitions.
    
    The worker is deliberately the last PR and remains behind an
    under-development feature flag. It currently scans only
    `archived_sessions`, not active `sessions`, because active sessions have
    the highest resume/append race risk. That means this stack does not yet
    compress most unarchived local history.
    
    ## Known race / follow-up
    
    The remaining unresolved design question is writer/compressor
    coordination. Even for archived rollouts, a resume or metadata update
    can append while the worker is replacing the plain file with
    `.jsonl.zst`; the current double-stat checks narrow but do not fully
    eliminate the window where a writer has opened the plain file before
    unlink. Do not treat the worker PR as production-ready until we either:
    
    - prevent append/resume paths from racing archived compression, or
    - introduce a shared representation/append lock or equivalent
    coordination.
    
    The first two PRs are useful independently: they make compressed
    rollouts readable and make append paths safely recover back to plain
    JSONL. The third PR isolates the worker behavior so that coordination
    issue is reviewable separately.
    
    ## Validation
    
    Focused local validation for the stack includes:
    
    - `just test -p codex-rollout`
    - `just test -p codex-thread-store` where thread-store paths were
    touched
    - `just test -p codex-features` for the feature flag slice
    - `just bazel-lock-check` after dependency graph changes
    - scoped `just fix -p ...` passes for changed crates
    
    CI is still the source of truth for the full platform matrix.
    
    ## This PR in the stack
    
    This is PR 3/3, based on #25088. It adds the under-development feature
    flag and starts the best-effort background worker when enabled. The
    worker currently compresses only cold archived rollouts, skips active
    sessions, verifies compressed output, preserves mtime and permissions,
    keeps a store-level lock heartbeat, and cleans stale temp files.
    
    Stack order:
    
    1. #25087: read compressed local rollouts.
    2. #25088: materialize compressed rollouts before append.
    3. This PR: add the disabled local compression worker.
  • 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.
  • Add subagent lineage metadata for responsesapi (#24161)
    ## Why
    
    We recently added `forked_from_thread_id` which lets us trace where a
    thread's _context_ comes from, but we also want to understand subagent
    lineage (e.g. which parent thread spawned this subagent? what kind of
    subagent is it?) which is orthogonal.
    
    This PR adds `parent_thread_id` and `subagent_kind` to the
    `x-codex-turn-metadata` header sent to ResponsesAPI.
    
    ## What changed
    
    - Adds `parent_thread_id` and `subagent_kind` to core-owned
    `x-codex-turn-metadata`.
    - Restores persisted `SessionSource` and `ThreadSource` from resumed
    session metadata so cold-resumed subagent threads keep their lineage on
    later Responses API requests.
    - Centralizes parent-thread extraction on `SessionSource` /
    `SubAgentSource` and reuses it in the Responses client, analytics, agent
    control, and state parsing paths.
    - Extends reserved-key, git-enrichment, thread-spawn, and app-server v2
    metadata coverage for the new lineage fields.
    
    ## Verification
    
    - Not run locally per request.
    - Added focused coverage in `core/src/turn_metadata_tests.rs` and
    `app-server/tests/suite/v2/client_metadata.rs`.
  • 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.
  • Avoid config snapshots in live agent subtree traversal (#24057)
    ## Why
    `/feedback` asks `ThreadManager` for the selected agent subtree before
    it uploads logs. The previous live subtree path reconstructed
    parent-child links by iterating every loaded thread and awaiting each
    thread config snapshot, so unrelated loaded-thread state could stall
    feedback subtree enumeration.
    
    The loaded-thread set already belongs to
    [`ThreadManagerState`](https://github.com/openai/codex/blob/50e6644c9425df2dcbfe52f65fd60bd7f15a8ea2/codex-rs/core/src/thread_manager.rs).
    Reading thread-spawn parents from the captured `CodexThread` session
    sources at that boundary keeps unload and resume behavior manager-owned
    while avoiding per-session config inspection.
    
    ## What Changed
    - expose parent-child thread-spawn edges for loaded, non-internal
    threads from `ThreadManagerState`
    - build the live child map from those edges while keeping agent metadata
    lookup and ordering in `AgentControl`
    - add regression coverage for live subtree enumeration when no state DB
    is available
    
    ## Validation
    - `git diff --check`
    - local Rust tests not run per request
  • Make extension lifecycle hooks async (#23291)
    ## Why
    
    Extension lifecycle hooks sit on the host/extension boundary, but the
    current trait surface only allows synchronous callbacks. That forces
    extensions that need to seed, rehydrate, observe, or flush
    extension-owned state during thread and turn transitions to either block
    inside the callback or move async work into separate host plumbing.
    
    This PR makes those lifecycle callbacks awaitable so extension
    implementations can perform async work directly at the lifecycle point
    where the host already has the relevant session, thread, or turn stores
    available.
    
    ## What changed
    
    - Makes `ThreadLifecycleContributor` and `TurnLifecycleContributor`
    async in `codex-extension-api`.
    - Awaits thread start/resume/stop and turn start/stop/abort lifecycle
    callbacks from `codex-core`.
    - Updates the guardian and memories extensions to implement the async
    lifecycle trait surface.
    - Updates the existing lifecycle tests to use async contributor
    implementations.
    - Adds `async-trait` to the crates that now expose or implement these
    async object-safe lifecycle traits.
    
    ## Testing
    
    - Existing `codex-core` lifecycle tests were updated to cover async
    implementations for thread stop and turn abort ordering.
  • feat: add thread lifecycle contributor hooks (#22476)
    ## Why
    
    Extensions that need thread-scoped state currently only get a start-time
    callback. That is enough for seeding stores, but it leaves the host
    without a shared extension seam for later thread rehydrate and flush
    work as thread ownership evolves. This PR turns that start-only seam
    into a host-owned thread lifecycle contributor contract so
    extension-private state can stay behind the extension API instead of
    leaking extra orchestration through core.
    
    ## What changed
    
    - Replaced `ThreadStartContributor` with `ThreadLifecycleContributor`
    and added typed lifecycle inputs for thread start, resume, and stop. The
    contract lives in
    [`contributors/thread_lifecycle.rs`](https://github.com/openai/codex/blob/d0e9211f70e58d6b07ef07e84f359d1b9aa25955/codex-rs/ext/extension-api/src/contributors/thread_lifecycle.rs#L1-L64).
    - Kept the existing start-time behavior intact by routing session
    construction through `on_thread_start`.
    - Invoked `on_thread_stop` during session shutdown before thread-scoped
    extension state is dropped, while isolating contributor failures behind
    warning logs.
    - Migrated `git-attribution` and `guardian` onto the lifecycle
    registration path.
    - Renamed the extension registry plumbing from start-specific
    contributors to lifecycle-specific contributors.
    
    ## Notes
    
    `on_thread_resume` is introduced at the API boundary here so extensions
    can target the final lifecycle shape; host resume dispatch can be wired
    where that runtime path is finalized.
  • Unify thread metadata updates above store (#22236)
    - make ThreadStore::update_thread_metadata accept a broad range of
    metadata patches
    - keep ThreadStore::append_items as raw canonical history append (no
    metadata side effects)
    - in the local store, write these metadata updates to a combination of
    sqlite and rollout jsonl files for backwards-compat. It special cases
    which fields need to go into jsonl vs sqlite vs whatever, confining the
    awkwardness to just this implementation
    - in remote stores we can simply persist the metadata directly to a
    database, no special casing required.
    - move the "implicit metadata updates triggered by appending rollout
    items" from the RolloutRecorder (which is local-threadstore-specific) to
    the LiveThread layer above the ThreadStore, inside of a private helper
    utility called ThreadMetadataSync. LiveThread calls ThreadStore
    append_items and update_metadata separately.
    - Add a generic update metadata method to ThreadManager that works on
    both live threads and "cold" threads
    - Call that ThreadManager method from app server code, so app server
    doesn't need to worry about whether the thread is live or not
  • feat: guardian as an extension (contributors part) (#22216)
    Part 1 of guardian as extension. This bind all the logic to spawn
    another agent from an extension and it adds `ThreadId` in the start
    thread collaborator
  • extension: wire extension registries into sessions (#21737)
    ## Why
    
    [#21736](https://github.com/openai/codex/pull/21736) introduces the
    typed extension API, but the runtime does not yet carry a registry
    through thread/session startup or give contributors host-owned stores to
    read from. This PR wires that host-side path so later feature migrations
    can move product-specific behavior behind typed contributions without
    adding another bespoke seam directly to `codex-core`.
    
    ## What changed
    
    - Thread `ExtensionRegistry<Config>` through `ThreadManager`,
    `CodexSpawnArgs`, `Session`, and sub-agent spawn paths.
    - Wire `ThreadStartContributor` and `ContextContributor`
    - Expose the small supporting surface needed by non-core callers that
    construct threads directly, including `empty_extension_registry()`
    through `codex-core-api`.
    
    This PR lands the host plumbing only: the app-server registry is still
    empty, and concrete feature migrations are intended to follow
    separately.
  • Reapply "Move skills watcher to app-server" (#21652)
    ## Why
    
    PR #21460 reverted the earlier move of skills change watching from
    `codex-core` into app-server. This reapplies that boundary change so
    app-server owns client-facing `skills/changed` notifications and core no
    longer carries the watcher.
    
    ## What
    
    - Restore the app-server `SkillsWatcher` and register it from thread
    listener setup.
    - Remove the core-owned skills watcher and its core live-reload
    integration surface.
    - Restore app-server coverage for `skills/changed` notifications after a
    watched skill file changes.
    
    ## Validation
    
    - `cargo test -p codex-app-server --test all
    suite::v2::skills_list::skills_changed_notification_is_emitted_after_skill_change
    -- --exact --nocapture`
    - `cargo test -p codex-core --lib --no-run`
  • [codex] request desktop attestation from app (#20619)
    ## Summary
    
    TL;DR: teaches `codex-rs` / app-server to request a desktop-provided
    attestation token and attach it as `x-oai-attestation` on the scoped
    ChatGPT Codex request paths.
    
    ![DeviceCheck attestation
    interface](https://raw.githubusercontent.com/openai/codex/dev/jm/devicecheck-diagram-assets/pr-assets/devicecheck-attestation-interface.png)
    
    ## Details
    
    This PR teaches the Codex app-server runtime how to request and attach
    an attestation token. It does not generate DeviceCheck tokens directly;
    instead, it relies on the connected desktop app to advertise that it can
    generate attestation and then asks that app for a fresh header value
    when needed.
    
    The flow is:
    
    1. The Codex desktop app connects to app-server.
    2. During `initialize`, the app can advertise that it supports
    `requestAttestation`.
    3. Before app-server calls selected ChatGPT Codex endpoints, it sends
    the internal server request `attestation/generate` to the app.
    4. app-server receives a pre-encoded header value back.
    5. app-server forwards that value as `x-oai-attestation` on the scoped
    outbound requests.
    
    The code in this repo is mostly protocol and runtime plumbing: it adds
    the app-server request/response shape, introduces an attestation
    provider in core, wires that provider into Responses / compaction /
    realtime setup paths, and covers the intended scoping with tests. The
    signed macOS DeviceCheck generation remains owned by the desktop app PR.
    
    ## Related PR
    
    - Codex desktop app implementation:
    https://github.com/openai/openai/pull/878649
    
    ## Validation
    
    <details>
    <summary>Tests run</summary>
    
    ```sh
    cargo test -p codex-app-server-protocol
    cargo test -p codex-core attestation --lib
    cargo test -p codex-app-server --lib attestation
    ```
    
    Also ran:
    
    ```sh
    just fix -p codex-core
    just fix -p codex-app-server
    just fix -p codex-app-server-protocol
    just fmt
    just write-app-server-schema
    ```
    
    </details>
    
    <details>
    <summary>E2E DeviceCheck validation</summary>
    
    First validated the signed desktop app boundary directly: launched a
    packaged signed `Codex.app`, sent `attestation/generate`, decoded the
    returned `v1.` attestation header, and validated the extracted
    DeviceCheck token with `personal/jm/verify_devicecheck_token.py` using
    bundle ID `com.openai.codex`. Apple returned `status_code: 200` and
    `is_ok: true`.
    
    Then ran the fuller app + app-server flow. The packaged `Codex.app`
    launched a current-branch app-server via `CODEX_CLI_PATH`, and a local
    MITM proxy intercepted outbound `chatgpt.com` traffic. The app-server
    requested `attestation/generate` from the real Electron app process, and
    the intercepted `/backend-api/codex/responses` traffic included
    `x-oai-attestation` on both routes:
    
    ```text
    GET  /backend-api/codex/responses  Upgrade: websocket  x-oai-attestation: present
    POST /backend-api/codex/responses  Upgrade: none       x-oai-attestation: present
    ```
    
    The captured header decoded to a DeviceCheck token that also validated
    with Apple for `com.openai.codex` (`status_code: 200`, `is_ok: true`,
    team `2DC432GLL2`).
    
    </details>
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • [codex] Remove remote thread store implementation (#21596)
    Remove the remote thread-store backend and checked-in protobuf
    artifacts. We've moved these into another crate that link against this
    one.
    
    Also remove the config settings for thread store backend selection,
    since we'll instead pass an instantiated thread store into the core-api
    crate's main entrypoint.
  • Route ThreadManager rollout path reads through thread store (#21265)
    - Route ThreadManager rollout-path resume/fork through ThreadStore
    history reads.
    - Add in-memory store coverage proving path-addressed reads are used.
    
    This isn't strictly necessary for the ThreadStore migration, since these
    ThreadManager methods _only_ work for path-based lookups, but I'm trying
    to migrate all the rollout recorder callsites to use the threadstore
    were possible for consistency.
  • Revert state DB injection and agent graph store (#21481)
    ## Why
    
    Reverts #20689 to restore the previous optional state DB plumbing. The
    conflict resolution keeps the newer installation ID and session/thread
    identity changes that landed after #20689, while removing the mandatory
    state DB and agent graph store dependency from ThreadManager
    construction.
    
    ## What changed
    
    - Restored `Option<StateDbHandle>` through app-server, MCP server,
    prompt debug, and test entry points.
    - Removed the `codex-core` dependency on `codex-agent-graph-store` and
    reverted descendant lookup back to the existing state DB path when
    available.
    - Kept newer `installation_id` forwarding by passing it beside the
    optional DB handle.
    - Kept local thread-name updates working when the optional state DB
    handle is absent.
    
    ## Validation
    
    - `git diff --check`
    - `cargo test -p codex-thread-store`
    - `cargo test -p codex-state -p codex-rollout -p
    codex-app-server-protocol`
    - Attempted `env CARGO_INCREMENTAL=0 cargo test -p codex-core -p
    codex-app-server -p codex-app-server-client -p codex-mcp-server -p
    codex-thread-manager-sample -p codex-tui`; blocked locally by a rustc
    ICE while compiling `v8 v146.4.0` with `rustc 1.93.0 (254b59607
    2026-01-19)` on `aarch64-apple-darwin`.
  • Move skills watcher to app-server (#21287)
    ## Why
    
    Skills update notifications are app-server API behavior, but the watcher
    lived in `codex-core` and surfaced through
    `EventMsg::SkillsUpdateAvailable`. Moving the watcher out keeps core
    focused on thread execution and lets app-server own both cache
    invalidation and the `skills/changed` notification.
    
    ## What changed
    
    - Added an app-server-owned skills watcher that watches local skill
    roots, clears the shared skills cache, and emits `skills/changed`
    directly.
    - Registers skill watches from the common app-server thread listener
    attach path, including direct starts, resumes, and app-server-observed
    child or forked threads.
    - Stores the `WatchRegistration` on `ThreadState`, so listener
    replacement, thread teardown, idle unload, and app-server shutdown
    deregister by dropping the RAII guard.
    - Removed `EventMsg::SkillsUpdateAvailable`, the core watcher, and the
    old core live-reload test.
    - Extended the app-server skills change test to verify a cached skills
    list is refreshed after a filesystem change without forcing reload.
    
    ## Validation
    
    - `cargo check -p codex-core -p codex-app-server -p codex-mcp-server -p
    codex-rollout -p codex-rollout-trace`
    - `cargo test -p codex-app-server
    skills_changed_notification_is_emitted_after_skill_change`
  • Move installation ID resolution out of core startup (#21182)
    ## Summary
    
    - resolve or inject the installation ID before core startup and pass it
    through `ThreadManager`, `CodexSpawnArgs`, and `Session` as a plain
    `String`
    - keep child sessions on the parent installation ID instead of
    rediscovering it inside core
    - propagate installation ID startup failures in `mcp-server` instead of
    panicking
    
    ## Why
    
    Core was still touching the filesystem on the session startup path to
    discover `installation_id`. This moves that work to the outer host
    boundary so core no longer depends on `codex_home` reads during session
    construction.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Preserve session MCP config on refresh (#21055)
    # Overview
    MCP refreshes were rebuilding active threads from fresh disk-backed
    config only, which dropped thread-start session overlays such as
    app-injected MCP servers. This keeps refreshes current with disk config
    while preserving the thread-local config that only the active thread
    knows about.
    
    # Changes
    - Rebuild refreshed config per active thread using that thread's current
    `cwd`, rather than fanning out one app-server config to every thread.
    - Preserve each thread's `SessionFlags` layer while replacing reloadable
    config layers with freshly loaded config, then derive the MCP refresh
    payload from the rebuilt result.
    - Move MCP refresh orchestration into app-server so manual refreshes
    fail loudly while background refreshes remain best-effort, and route
    plugin-triggered refreshes through the same per-thread reload path.
    - Add regression coverage for session overlays, fresh project config,
    plugin-derived MCP config, current requirements, and strict vs
    best-effort refresh behavior.
    
    # Verification
    - Passed focused Rust coverage for the thread-config rebuild behavior
    and deferred MCP refresh flow, plus `cargo test -p codex-app-server
    --lib`.
    - Verified end to end in the Codex dev app against the locally built
    CLI: registered an MCP via thread config, verified that it could be used
    successfully before refresh, manually triggered MCP refresh, and
    verified that it continued to be available afterward.
  • [codex-analytics] rework thread_source for thread analytics (#20949)
    ## Summary
    - make `thread_source` an explicit optional thread-level field on
    `thread/start`, `thread/fork`, and returned thread payloads
    - persist `thread_source` in rollout/session metadata so resumed live
    threads retain the original value
    - replace the old best-effort `session_source` -> `thread_source`
    mapping with an explicit caller-supplied analytics classification
    
    ## Why
    Before this change, analytics `thread_source` was populated by a
    best-effort mapping from `session_source`. `session_source` describes
    the runtime/client surface, not the actual thread-level origin, so that
    projection was not accurate enough to distinguish cases such as `user`,
    `subagent`, `memory_consolidation`, and future thread origins reliably.
    
    Making `thread_source` explicit keeps one thread-level analytics field
    while letting callers provide the real classification directly instead
    of recovering it indirectly from `session_source`.
    
    ## Impact
    For new analytics events, `thread_source` now reflects the explicit
    thread-level classification supplied by the caller rather than an
    inferred value derived from `session_source`. Existing protocol fields
    remain optional; callers that omit `threadSource` now produce `null`
    instead of a best-effort inferred value.
    
    ## Validation
    - `just write-app-server-schema`
    - `cargo test -p codex-analytics -p codex-core -p
    codex-app-server-protocol --no-run`
    - `cargo test -p codex-app-server-protocol
    generated_ts_optional_nullable_fields_only_in_params`
    - `cargo test -p codex-analytics
    thread_initialized_event_serializes_expected_shape`
    - `cargo test -p codex-core
    resume_stopped_thread_from_rollout_preserves_thread_source`
  • Inject state DB, agent graph store (#20689)
    ## Why
    
    We want the agent graph store to be passed down the stack as a real
    dependency, the same way we already treat the thread store.
    
    This will let us inject the agent graph store as a real dependency and
    support implementations other than the local SQLite-backed one. Right
    now most code instantiates a state DB and an agent graph store
    just-in-time. Ideally, we would not depend on the state DB directly but
    only read through the higher-level interfaces.
    
    This change makes the dependency boundaries explicit and moves state DB
    initialization to process bootstrap instead of hiding it inside local
    store implementations.
    
    ## What changed
    
    - `ThreadManager` now requires a `StateDbHandle` and an
    `AgentGraphStore` at construction time instead of treating them as
    optional internals.
    - The local store constructors no longer lazily initialize SQLite.
    Callers now initialize the state DB once per process and use that shared
    handle to build:
      - `LocalThreadStore`
      - `LocalAgentGraphStore`
    - App bootstraps (`app-server`, `mcp-server`, `prompt_debug`, and the
    thread-manager sample) now initialize the state DB up front and inject
    the resulting handle down the stack.
    - `app-server` now consistently uses its process-scoped state DB handle
    instead of reopening SQLite or trying to recover it from loaded threads.
    - Device-key storage now reuses the shared state DB handle instead of
    maintaining its own lazy opener.
    - The thread archive / descendant traversal paths now use the injected
    `AgentGraphStore` instead of reaching through local
    thread-store-specific state.
    
    ## Verification
    
    - `cargo check -p codex-core -p codex-thread-store -p codex-app-server
    -p codex-mcp-server -p codex-thread-manager-sample --tests`
    - `cargo test -p codex-thread-store`
    - `cargo test -p codex-core
    thread_manager_accepts_separate_agent_graph_store_and_thread_store --
    --nocapture`
    - `cargo test -p codex-app-server
    thread_archive_archives_spawned_descendants -- --nocapture`
  • state: pass state db handles through consumers (#20561)
    ## Why
    
    SQLite state was still being opened from consumer paths, including lazy
    `OnceCell`-backed thread-store call sites. That let one process
    construct multiple state DB connections for the same Codex home, which
    makes SQLite lock contention and `database is locked` failures much
    easier to hit.
    
    State DB lifetime should be chosen by main-like entrypoints and tests,
    then passed through explicitly. Consumers should use the supplied
    `Option<StateDbHandle>` or `StateDbHandle` and keep their existing
    filesystem fallback or error behavior when no handle is available.
    
    The startup path also needs to keep the rollout crate in charge of
    SQLite state initialization. Opening `codex_state::StateRuntime`
    directly bypasses rollout metadata backfill, so entrypoints should
    initialize through `codex_rollout::state_db` and receive a handle only
    after required rollout backfills have completed.
    
    ## What Changed
    
    - Initialize the state DB in main-like entrypoints for CLI, TUI,
    app-server, exec, MCP server, and the thread-manager sample.
    - Pass `Option<StateDbHandle>` through `ThreadManager`,
    `LocalThreadStore`, app-server processors, TUI app wiring, rollout
    listing/recording, personality migration, shell snapshot cleanup,
    session-name lookup, and memory/device-key consumers.
    - Remove the lazy local state DB wrapper from the thread store so
    non-test consumers use only the supplied handle or their existing
    fallback path.
    - Make `codex_rollout::state_db::init` the local state startup path: it
    opens/migrates SQLite, runs rollout metadata backfill when needed, waits
    for concurrent backfill workers up to a bounded timeout, verifies
    completion, and then returns the initialized handle.
    - Keep optional/non-owning SQLite helpers, such as remote TUI local
    reads, as open-only paths that do not run startup backfill.
    - Switch app-server startup from direct
    `codex_state::StateRuntime::init` to the rollout state initializer so
    app-server cannot skip rollout backfill.
    - Collapse split rollout lookup/list APIs so callers use the normal
    methods with an optional state handle instead of `_with_state_db`
    variants.
    - Restore `getConversationSummary(ThreadId)` to delegate through
    `ThreadStore::read_thread` instead of a LocalThreadStore-specific
    rollout path special case.
    - Keep DB-backed rollout path lookup keyed on the DB row and file
    existence, without imposing the filesystem filename convention on
    existing DB rows.
    - Verify readable DB-backed rollout paths against `session_meta.id`
    before returning them, so a stale SQLite row that points at another
    thread's JSONL falls back to filesystem search and read-repairs the DB
    row.
    - Keep `debug prompt-input` filesystem-only so a one-off debug command
    does not initialize or backfill SQLite state just to print prompt input.
    - Keep goal-session test Codex homes alive only in the goal-specific
    helper, rather than leaking tempdirs from the shared session test
    helper.
    - Update tests and call sites to pass explicit state handles where DB
    behavior is expected and explicit `None` where filesystem-only behavior
    is intended.
    
    ## Validation
    
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo check -p
    codex-rollout -p codex-thread-store -p codex-app-server -p codex-core -p
    codex-tui -p codex-exec -p codex-cli --tests`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-rollout state_db_`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-rollout find_thread_path`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-rollout find_thread_path -- --nocapture`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-rollout try_init_ -- --nocapture`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-rollout`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo clippy -p
    codex-rollout --lib -- -D warnings`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-thread-store
    read_thread_falls_back_when_sqlite_path_points_to_another_thread --
    --nocapture`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-thread-store`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p codex-core
    shell_snapshot`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p codex-core
    --test all personality_migration`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p codex-core
    --test all rollout_list_find`
    - `RUST_MIN_STACK=8388608 CODEX_SKIP_VENDORED_BWRAP=1
    CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p codex-core
    --test all rollout_list_find::find_prefers_sqlite_path_by_id --
    --nocapture`
    - `RUST_MIN_STACK=8388608 CODEX_SKIP_VENDORED_BWRAP=1
    CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p codex-core
    --test all rollout_list_find -- --nocapture`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p codex-core
    interrupt_accounts_active_goal_before_pausing`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-app-server get_auth_status -- --test-threads=1`
    - `CODEX_SKIP_VENDORED_BWRAP=1
    CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo test -p
    codex-app-server --lib`
    - `CODEX_SKIP_VENDORED_BWRAP=1
    CARGO_TARGET_DIR=/tmp/codex-target-state-db cargo check -p codex-rollout
    -p codex-app-server --tests`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db just fix -p codex-rollout
    -p codex-thread-store -p codex-core -p codex-app-server -p codex-tui -p
    codex-exec -p codex-cli`
    - `CODEX_SKIP_VENDORED_BWRAP=1
    CARGO_TARGET_DIR=/tmp/codex-target-state-db just fix -p codex-rollout -p
    codex-app-server`
    - `CARGO_TARGET_DIR=/tmp/codex-target-state-db just fix -p
    codex-rollout`
    - `CODEX_SKIP_VENDORED_BWRAP=1
    CARGO_TARGET_DIR=/tmp/codex-target-state-db just fix -p codex-core`
    - `just argument-comment-lint -p codex-core`
    - `just argument-comment-lint -p codex-rollout`
    
    Focused coverage added in `codex-rollout`:
    
    - `recorder::tests::state_db_init_backfills_before_returning` verifies
    the rollout metadata row exists before startup init returns.
    - `state_db::tests::try_init_waits_for_concurrent_startup_backfill`
    verifies startup waits for another worker to finish backfill instead of
    disabling the handle for the process.
    -
    `state_db::tests::try_init_times_out_waiting_for_stuck_startup_backfill`
    verifies startup does not hang indefinitely on a stuck backfill lease.
    -
    `tests::find_thread_path_accepts_existing_state_db_path_without_canonical_filename`
    verifies DB-backed lookup accepts valid existing rollout paths even when
    the filename does not include the thread UUID.
    -
    `tests::find_thread_path_falls_back_when_db_path_points_to_another_thread`
    verifies DB-backed lookup ignores a stale row whose existing path
    belongs to another thread and read-repairs the row after filesystem
    fallback.
    
    Focused coverage updated in `codex-core`:
    
    - `rollout_list_find::find_prefers_sqlite_path_by_id` now uses a
    DB-preferred rollout file with matching `session_meta.id`, so it still
    verifies that valid SQLite paths win without depending on stale/empty
    rollout contents.
    
    `cargo test -p codex-app-server thread_list_respects_search_term_filter
    -- --test-threads=1 --nocapture` was attempted locally but timed out
    waiting for the app-server test harness `initialize` response before
    reaching the changed thread-list code path.
    
    `bazel test //codex-rs/thread-store:thread-store-unit-tests
    --test_output=errors` was attempted locally after the thread-store fix,
    but this container failed before target analysis while fetching `v8+`
    through BuildBuddy/direct GitHub. The equivalent local crate coverage,
    including `cargo test -p codex-thread-store`, passes.
    
    A plain local `cargo check -p codex-rollout -p codex-app-server --tests`
    also requires system `libcap.pc` for `codex-linux-sandbox`; the
    follow-up app-server check above used `CODEX_SKIP_VENDORED_BWRAP=1` in
    this container.
  • Prepare selected environment plumbing (#20669)
    ## Why
    This is a prep PR in the multi-environment process-tool stack. It
    separates ownership/config cleanup from the behavior change that teaches
    process tools to route by selected environment, so the follow-up PR can
    focus on model-facing `environment_id` behavior.
    
    ## Stack
    1. https://github.com/openai/codex/pull/20646 - `EnvironmentContext`
    rendering for selected environments
    2. https://github.com/openai/codex/pull/20669 - selected-environment
    ownership and tool config prep (this PR)
    3. https://github.com/openai/codex/pull/20647 - process-tool
    `environment_id` routing
    
    ## What Changed
    - keep the resolved turn environment list wrapped in
    `ResolvedTurnEnvironments` through `TurnContext` instead of unwrapping
    it back to a raw `Vec`
    - add `TurnContext::resolve_path_against` so cwd-relative path
    resolution has one shared helper
    - replace the old tool config boolean with `ToolEnvironmentMode::{None,
    Single, Multiple}`
    
    ## Testing
    - Tests not run locally; this prep refactor is covered by GitHub CI for
    the stack.
    
    Co-authored-by: Codex <noreply@openai.com>
  • Use selected turn environments for runtime context (#20281)
    ## Summary
    - make selected turn environments the source of truth for session
    runtime cwd and MCP runtime environment selection
    - keep local/no-selection fallback behavior intact
    - add coverage for duplicate selected environments, cwd resolution, and
    MCP runtime environment selection
    
    ## Validation
    - git diff --check
    - rustfmt was run on touched Rust files during the implementation
    workflow
    
    CI should provide the full Bazel/test signal.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Make thread store process-scoped (#19474)
    - Build one app-server process ThreadStore from startup config and share
    it with ThreadManager and CodexMessageProcessor.
    - Remove per-thread/fork store reconstruction so effective thread config
    cannot switch the persistence backend.
    - Add params to ThreadStore create/resume for specifying thread
    metadata, since otherwise the metadata from store creation would be used
    (incorrectly).
  • [codex] Migrate thread turns list to thread store (#19280)
    - migrate `thread/turns/list` to ThreadStore. Uses ThreadStore for most
    data now but merges in the in-memory state from thread manager
    - keep v2 `thread/list` pathless-store friendly by converting
    `StoredThread` directly to API `Thread`
    - add regression coverage for pathless store history/listing
  • Reduce the surface of collaboration modes (#20149)
    Collaboration modes were slightly invasive both into ThreadManager
    construction and ModelProvider
  • Add ThreadManager sample crate (#20141)
    Summary:
    - Add codex-thread-manager-sample, a one-shot binary that starts a
    ThreadManager thread, submits a prompt, and prints the final assistant
    output.
    - Pass ThreadStore into ThreadManager::new and expose
    thread_store_from_config for existing callsites.
    - Build the sample Config directly with only --model and prompt inputs.
    
    Verification:
    - just fmt
    - cargo check -p codex-thread-manager-sample -p codex-app-server -p
    codex-mcp-server
    - git diff --check
    
    Tests: Not run per request.
  • feat: split memories part 2 (#19860)
    Keep extracting memories out of core and moving the write trigger in the
    app-server
    This is temporary and it should move at the client level as a follow-up
    This makes core fully independant from `codex-memories-write`
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Add goal core runtime (4 / 5) (#18076)
    Adds the core runtime behavior for active goals on top of the model
    tools from PR 3.
    
    ## Why
    
    A long-running goal should be a core runtime concern, not something
    every client has to implement. Core owns the turn lifecycle, tool
    completion boundaries, interruptions, resume behavior, and token usage,
    so it is the right place to account progress, enforce budgets, and
    decide when to continue work.
    
    ## What changed
    
    - Centralized goal lifecycle side effects behind
    `Session::goal_runtime_apply(GoalRuntimeEvent::...)`.
    - Starts goal continuation turns only when the session is idle; pending
    user input and mailbox work take priority.
    - Accounts token and wall-clock usage at turn, tool, mutation,
    interrupt, and resume boundaries; `get_thread_goal` remains read-only.
    - Preserves sub-second wall-clock remainder across accounting boundaries
    so long-running goals do not drift downward over time.
    - Treats token budget exhaustion as a soft stop by marking the goal
    `budget_limited` and injecting wrap-up steering instead of aborting the
    active turn.
    - Suppresses budget steering when `update_goal` marks a goal complete.
    - Pauses active goals on interrupt and auto-reactivates paused goals
    when a thread resumes outside plan mode.
    - Suppresses repeated automatic continuation when a continuation turn
    makes no tool calls.
    - Added continuation and budget-limit prompt templates.
    
    ## Verification
    
    - Added focused core coverage for continuation scheduling, accounting
    boundaries, budget-limit steering, completion accounting, interrupt
    pause behavior, resume auto-activation, and wall-clock remainder
    accounting.
  • [codex] add non-local thread store regression harness (#19266)
    - Add an integration test that guarantees nothing gets written to codex
    home dir or sqlite when running a rollout with a non-local ThreadStore
    - Add an in-memory "spy" ThreadStore for tests like this
    
    Note I could not find a good way to also ensure there were no filesystem
    _reads_ that didn't go through threadstore. I explored a more elaborate
    sandboxed-subprocess approach but it isn't platform portable and felt
    like it wasn't (yet) worth it.
  • Migrate fork and resume reads to thread store (#18900)
    - Route cold thread/resume and thread/fork source loading through
    ThreadStore reads instead of direct rollout path operations
    - Keep lookups that explicitly specify a rollout-path using the local
    thread store methods but return an invalid-request error for remote
    ThreadStore configurations
    - Add some additional unit tests for code path coverage
  • Add agents.interrupt_message for interruption markers (#19351)
    ## Why
    
    Agent interruptions currently always persist a model-visible
    interrupted-turn marker before emitting `TurnAborted`. That marker is
    useful by default because it gives the next model turn context about a
    deliberately interrupted task, but some deployments need to suppress
    that history injection entirely while still keeping the client-visible
    interruption event.
    
    ## What changed
    
    - Add `[agents] interrupt_message = false` to disable the model-visible
    interrupted-turn marker.
    - Resolve the setting into `Config::agent_interrupt_message_enabled`,
    defaulting to `true` so existing behavior is unchanged.
    - Apply the setting to both live interrupted turns and interrupted fork
    snapshots.
    - Keep emitting `TurnAborted` even when the history marker is disabled.
    - Regenerate `core/config.schema.json` for the new
    `agents.interrupt_message` field.
    
    ## Testing
    
    - `cargo test -p codex-core load_config_resolves_agent_interrupt_message
    -- --nocapture`
    - `cargo test -p codex-core
    disabled_interrupted_fork_snapshot_appends_only_interrupt_event --
    --nocapture`
    - `cargo test -p codex-core
    multi_agent_v2_interrupted_marker_uses_developer_input_message --
    --nocapture`
    - `cargo test -p codex-core
    multi_agent_v2_followup_task_can_disable_interrupted_marker --
    --nocapture`
    - `cargo test -p codex-core
    multi_agent_v2_followup_task_interrupts_busy_child_without_losing_message
    -- --nocapture`
    - `cargo check -p codex-core`
  • Make MultiAgentV2 interruption markers assistant-authored (#19124)
    ## Why
    
    `MultiAgentV2` follow-up messages are delivered to agents as
    assistant-authored `InterAgentCommunication` envelopes. When
    `followup_task` used `interrupt: true`, the interrupted-turn guidance
    was still persisted as a contextual user message, so model-visible
    history made a system-generated interruption boundary look
    user-authored.
    
    This keeps interruption guidance consistent with the rest of the v2
    inter-agent message stream while preserving the legacy marker shape for
    non-v2 sessions.
    
    ## What changed
    
    - Make `interrupted_turn_history_marker` feature-aware.
    - Record the interrupted-turn marker as an assistant `OutputText`
    message when `Feature::MultiAgentV2` is enabled.
    - Keep the existing user contextual fragment for non-v2 sessions.
    - Apply the same feature-aware marker to interrupted fork snapshots.
    - Add coverage for the live `followup_task` interrupt path and the
    helper-level v2 marker shape.
    
    ## Testing
    
    - `cargo test -p codex-core
    multi_agent_v2_followup_task_interrupts_busy_child_without_losing_message
    -- --nocapture`
    - `cargo test -p codex-core
    multi_agent_v2_interrupted_marker_uses_assistant_output_message --
    --nocapture`
    - `cargo test -p codex-core interrupted_fork_snapshot -- --nocapture`
  • feat: let model providers own model discovery (#18950)
    ## Why
    
    `codex-models-manager` had grown to own provider-specific concerns:
    constructing OpenAI-compatible `/models` requests, resolving provider
    auth, emitting request telemetry, and deciding how provider catalogs
    should be sourced. That made the manager harder to reuse for providers
    whose model catalog is not fetched from the OpenAI `/models` endpoint,
    such as Amazon Bedrock.
    
    This change moves provider-specific model discovery behind
    provider-owned implementations, so the models manager can focus on
    refresh policy, cache behavior, picker ordering, and model metadata
    merging.
    
    ## What Changed
    
    - Introduced a `ModelsManager` trait with separate `OpenAiModelsManager`
    and `StaticModelsManager` implementations.
    - Added `ModelsEndpointClient` so OpenAI-compatible HTTP fetching lives
    outside `codex-models-manager`.
    - Moved `/models` request construction, provider auth resolution,
    timeout handling, and request telemetry into `codex-model-provider` via
    `OpenAiModelsEndpoint`.
    - Added provider-owned `models_manager(...)` construction so configured
    OpenAI-compatible providers use `OpenAiModelsManager`, while
    static/catalog-backed providers can return `StaticModelsManager`.
    - Added an Amazon Bedrock static model catalog for the GPT OSS Bedrock
    model IDs.
    - Updated core/session/thread manager code and tests to depend on
    `Arc<dyn ModelsManager>`.
    - Moved offline model test helpers into
    `codex_models_manager::test_support`.
    ## Metadata References
    
    The Bedrock catalog metadata is based on the official Amazon Bedrock
    OpenAI model documentation:
    
    - [Amazon Bedrock OpenAI
    models](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-openai.html)
    lists the Bedrock model IDs, text input/output modalities, and `128,000`
    token context window for `gpt-oss-20b` and `gpt-oss-120b`.
    - [Amazon Bedrock `gpt-oss-120b` model
    card](https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-openai-gpt-oss-120b.html)
    lists the `bedrock-runtime` model ID `openai.gpt-oss-120b-1:0`, the
    `bedrock-mantle` model ID `openai.gpt-oss-120b`, text-only modalities,
    and `128K` context window.
    - [OpenAI `gpt-oss-120b` model
    docs](https://developers.openai.com/api/docs/models/gpt-oss-120b)
    document configurable reasoning effort with `low`, `medium`, and `high`,
    plus text input/output modality.
    
    The display names, default reasoning effort, and priority ordering are
    Codex-local catalog choices.
    
    ## Test Plan
    - Manually verified app-server model listing with an AWS profile:
    
    ```shell
    CODEX_HOME="$(mktemp -d)" cargo run -p codex-app-server-test-client -- \
      --codex-bin ./target/debug/codex \
      -c 'model_provider="amazon-bedrock"' \
      -c 'model_providers.amazon-bedrock.aws.profile="codex-bedrock"' \
      -c 'model_providers.amazon-bedrock.aws.region="us-west-2"' \
      model-list
    ```
    
    The response returned the Bedrock catalog with `openai.gpt-oss-120b-1:0`
    as the default model and `openai.gpt-oss-20b-1:0` as the second listed
    model, both text-only and supporting low/medium/high reasoning effort.
  • Add sticky environment API and thread state (#18897)
    ## Summary
    - add sticky environment selections to app-server v2 thread/start and
    turn/start request flow
    - carry thread-level selections through core session/thread state
    - add app-server coverage for sticky selections and turn overrides
    
    ## Stack
    1. This PR: API and thread persistence
    2. #18898: config.toml named environment loading
    3. #18899: downstream tool/runtime consumers
    
    ## Validation
    - Not run locally; split only.
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>