## Why
Failed cloud discovery previously retried before each model step. Apply a cooldown across steps and turns to avoid repeated discovery requests while a provider remains unavailable.
## What changed
- Share per-thread retry backoff logic between cloud skill and plugin catalogs, starting at 30 seconds and doubling to a maximum of 5 minutes.
- Scope cooldowns to authentication and MCP resource generations so account changes and reconnects permit immediate recovery.
- Clear backoff after successful discovery or configuration resets, while allowing cancelled refreshes to retry immediately.
## Testing
Update lifecycle and concurrency tests to cover shared cooldowns, reconnect recovery, and cancellation. Use paused time to verify exponential delays, the 5-minute cap, and backoff across turns.
GitOrigin-RevId: 30dab5ceba51e8ad64c0794c1729719e99f577ce
## What changed
- Build executor skill catalogs from discovery V2 inventories without reopening host paths or falling back to legacy discovery.
- Merge Cloud and executor skills by remote plugin identity and skill name, retaining Cloud metadata and accepted provider read routes. Apply plugin selection and disablement before merging.
- Share a stable catalog across each step's rendering, list/read tools, and explicit and implicit skill invocations.
- Preserve unmatched executor entries when Cloud catalogs are unavailable or partial; only complete Cloud catalogs disable unmatched remote entries.
- Bind discovery caches to captured environments and executor sessions, and refresh MCP when V2 inventories change. Preserve pending invalidations when forced MCP refreshes fail.
## Testing
Add regression coverage for V2 inventory sharing and refresh, environment isolation, catalog merging, partial Cloud results, disabled plugins, and catalog consistency across steps and read routes.
GitOrigin-RevId: 842bdcbc9e19364dd69a9abf9e48330b7425a76c
## Why
Contextual fragments and extension-owned world-state sections currently return strings, limiting them to text content.
## What changed
- Return `ContentItem` from fragment bodies and rendering, preserving classification and attribution. Apply text markers only to `InputText`; pass other content through unchanged.
- Allow extension prompt and world-state contributions to carry typed content, and preserve media in context-window hints.
- Hash non-text world-state fragments by their serialized content and role while retaining the existing text hashing behavior.
- Add shared content-to-tool-output conversion that preserves image references, image detail, and audio.
## Testing
Add coverage for typed world-state hashing and preservation of image content and attribution. Expand code-mode output tests to cover input text, output text, file-backed and inline images, and audio.
GitOrigin-RevId: 2ed00d466a3f169b151202f3eef71e667ea64be1
## What changed
- Upload generated images to the attachment store and expose `fileId` in image-generation items when available, with inline `result` fallback on upload or decoding failure.
- Preserve file references through legacy events, persisted rollouts, thread history, and task output summaries. Keep model-facing image preparation separate.
- Add attachment request tags and mark generated-image uploads with `usage=image_gen`, preserving the thread's ephemeral setting.
- Update public schemas and SDK types to accept either image representation, retain the Python `ImageGenerationThreadItem` alias, and prefer `fileId` when reading records containing both fields.
## Testing
Add coverage for file-backed generation and rollout persistence, legacy history replay, inline and transitional serialization, task output visibility, and Python SDK alias compatibility.
GitOrigin-RevId: f7a2bb5707e2f277348a547a988bc33c8e96d891
## What changed
- Return qualified skill names such as `team:deploy` and parsed `dependencies` and `policy` instead of a separate namespace and raw metadata file. Share `agents/openai.yaml` parsing and validation between skill loaders and executor discovery.
- Include each standalone skill's search-root ID and path in `discoveryRoot`, while accepting responses from older executors that omit it.
- Add plugin `displayName`, falling back to the manifest name or installation directory.
- Materialize inline MCP declarations in `mcpConfig` with the manifest as their source path, and report incomplete discovery when declared MCP or Apps configuration files have no returned contents.
## Testing
Add coverage for JSON round trips, omitted `discoveryRoot`, optional and invalid skill metadata, qualified names, display-name fallbacks, and inline MCP configuration. Verify standalone search roots survive cached discovery and sandboxed RPC responses.
GitOrigin-RevId: 4124972444ad7c2fb8c6418acbf9a589d797fad2
## Why
Environment changes and overlapping catalog refreshes can cause a step to use another step's selected plugins or executor skills. Model context and skill tools need to agree on the capabilities captured for that step.
## What changed
- Resolve selected plugins from each step's ready capability roots, honoring disabled plugins, and retain the snapshot with the step's extension data.
- Use that snapshot for skill attribution in World State and the first step's active-plugin telemetry.
- Return executor skill catalogs directly from refresh so overlapping requests render their own results while retaining thread caches.
## Testing
Add regression coverage for overlapping executor catalogs and environment changes during step preparation. Verify that `skills.list` retains the original step's skills, the next step advertises the new skills, and disabled executor and plugin skills remain absent from model context.
GitOrigin-RevId: 37d3cbe8c7e062fd06290aa985ada4d3b3462d43
## Why
Shadow skill selection runs synchronously during turn input construction, making turn preparation wait for experimental rankings.
## What changed
Run ranking on background blocking workers with a process-wide limit of two concurrent rankings. Wait for capacity in the background and hold each slot until its blocking worker finishes, even if the async evaluation is cancelled.
Freeze catalog, query, and relevance history at turn start. Record eligible invocations immediately for future turns, then score queued observations against the frozen rankings once they are ready, including observations queued before the turn state is dropped.
## Testing
Add tests for turn preparation and history updates while ranking is blocked, deferred invocation scoring against each turn's history, and shared ranking capacity that remains occupied until cancelled workers finish. Update existing metric assertions to collect asynchronous observations across snapshots.
GitOrigin-RevId: f7776e9aaeb45d3a792de167abbe35f7ec5a513e
## Why
Changes in executor availability can alter cloud skill catalog rendering under a shared budget and repeat an unchanged executor catalog when it reconnects.
## What changed
- Cap cloud skills at three quarters of the metadata budget initially, leaving the remaining budget and unused cloud allowance for filesystem skills. Reduce the cloud cap only when doing so allows all skill entries to fit; description truncation alone does not trigger rebalancing.
- Persist the allocation across disconnects, compaction, and thread resume. Recompute it when the cloud inventory or total budget changes.
- Emit a short availability update when an unchanged selected-environment catalog returns and its full text remains in history. Reinject the full catalog when that text is missing.
## Testing
Add coverage for allocation stability, catalog and budget changes, omission handling, and full catalog reinjection after compaction and resume. Extend the selected capability stack test to verify reconnection avoids repeating the catalog.
GitOrigin-RevId: 839fe98024e00c4082a46e6a40675e15fd2c16c6
## What changed
- Replace the built-in orchestrator skill provider with a host-supplied cloud provider, using `cloud` authority and `c` root aliases in skill tools and prompts.
- Add `cloud.skills.enabled`, defaulting to true but requiring a supplied provider. Keep `orchestrator.skills` accepted as a legacy no-op setting, and remove the app server's automatic orchestrator provider registration.
- Discover cloud skills once per turn during cancellable startup. Serve catalog snapshots to prompts and tools without triggering discovery or retries.
- Scope cached catalogs and resources to authentication state. Preserve them on refresh failures within the same scope, invalidate them when that scope changes, and reject late discovery or read results from replaced cache generations.
- Reuse executor discovery catalogs only when successful discovery inputs and bundled-skill settings match.
## Testing
Add coverage for per-turn refresh, skill removal, same-account failures and reconnects, authentication changes, and stale in-flight results. Update configuration and tool tests for cloud authority, provider gating, and resource reads without an executor.
GitOrigin-RevId: c57e38ec7dcc880c0d4c8a2995adfd3e28301079
## Why
`skills.read` paginated resources against a fixed response limit, so a page could exceed a smaller tool-call response budget.
## What changed
- Size each serialized `skills.read` page to the current call's response budget, accounting for JSON escaping and UTF-8 boundaries.
- Cache one bounded executor-resource snapshot per thread so cursor continuations return consistent contents without rereading the file. Replace the snapshot when another resource is read, and reject stale cursors after a reread.
- Tie cached executor snapshots to the selected environment and sandbox context without keeping the environment alive.
## Testing
- Cover budget-constrained reads, escaped and multibyte contents, multi-page reconstruction, changed resources, snapshot eviction, and expired turn-scoped access.
GitOrigin-RevId: e515935b82771b3f29b3fb91ea7a7a72e19a65ee
## What changed
- Emit skill invocation analytics when a resource-backed skill is explicitly selected or its main resource is successfully read from the first page.
- Preserve provider-supplied skill IDs and user scope when available, and derive a stable ID from the main resource otherwise.
- Deduplicate repeated implicit reads of the same main resource within a turn.
## Testing
- Extend the orchestrator skill integration test to cover explicit and implicit invocation events, stable fallback IDs, failed reads, and repeated-read deduplication.
GitOrigin-RevId: 7d8453b31b5a2205847e65cda2c46183d6c97b45
## What changed
- Move `SkillLoadOutcome` and its implicit-path indexing into
`codex-skills-extension`.
- Import shared skill types directly from `codex-skills` and keep the skill
prompt size limit with the extension renderer that uses it.
- Remove `codex-core-skills` from the Cargo workspace and dependent crates.
GitOrigin-RevId: 4e9e84909fa2f692bcc94af990bb4671affd776f
## What changed
- Move config-layer, user, system, plugin, extra, and repository skill-root
resolution from `core-skills` into the host skills extension.
- Keep the core loader focused on loading explicit `SkillRoot` values.
- Relocate and expand tests for root precedence, deduplication, repository
ancestry, plugin metadata, and concurrent probing.
GitOrigin-RevId: 3b95cf28101b8b4d64d54079d202154dad560aab
## What changed
- Move host skill discovery, snapshot caching, and configuration handling from
`codex-core-skills` to `codex-skills-extension`.
- Rename `SkillsService` and `SkillsLoadInput` to `HostSkillsService` and
`HostSkillsLoadInput` to distinguish the host implementation from other skill
providers.
- Keep shared loading primitives and skill outcome modeling in
`codex-core-skills`.
GitOrigin-RevId: d81a21791d2ef8c066e157e7e538b8cb7ee4c24b
## What changed
- Add a loader that discovers skills from canonical host roots and preserves their `SkillScope`.
- Skip hidden directories, follow directory symlinks for user, repo, and admin scopes, and ignore them for system skills.
- Load dependencies and policy from optional `agents/openai.yaml` metadata without rejecting a skill when that metadata is missing or invalid.
- Resolve plugin and symlink namespaces while retaining host paths and reporting per-skill errors outside system scope.
## Testing
Add coverage for frontmatter and metadata loading, invalid optional metadata, hidden directories, plugin namespaces, and scope-specific symlink discovery.
GitOrigin-RevId: ac436d93b5943b7eaae32143e43367921dd1c350
## What changed
- Add extension-owned discovery and namespace resolution for skills loaded directly through an `ExecutorFileSystem`.
- Preserve hidden and symlinked skills, nested plugin namespaces, optional `agents/openai.yaml` metadata, product restrictions, and deterministic ordering.
- Reuse the filesystem walk inventory and bound concurrent skill, metadata, and manifest reads.
- Route direct executor catalog loading through the new extension loader.
## Testing
- Cover namespace lookup, metadata probing, walk reuse, concurrent reads, and parity with the existing environment loader.
GitOrigin-RevId: 4e0b821eb84d03f0dc1c2dee7b2b9a072ee3fd44
## What changed
- Move parsing of pre-discovered executor skill bundles from `core-skills` to
the skills extension.
- Expose shared `SKILL.md` frontmatter parsing from `codex-skills` so direct
and pre-discovered loading use the same validation and repair behavior.
## Testing
- Add parity coverage for direct and pre-discovered executor skill catalogs,
including plugin namespaces, metadata, product policy, and warnings.
- Add a snapshot for the resulting pre-discovered executor catalog.
GitOrigin-RevId: c5f888226fa5600bd8b90f5682400da39a5db5ff
## What changed
- Load and cache the host skill provider catalog during world-state contribution.
- Render host skill listings from that catalog with Core-compatible budgeting, while preserving Core's full prompt injection for selected host skills.
- Reuse the cached host catalog for shadow selection, including when skill listings are disabled.
- Preserve an empty Core-compatible skills fragment when every entry exceeds the metadata budget, and make budget warnings independent of the configured percentage.
## Testing
- Cover provider-backed host listings, selected-skill prompt injection, shadow selection, disabled listings, oversized prompts, empty fragments, metrics, and warnings.
GitOrigin-RevId: b6b2d8ea14386356094cff5783d121d74657e9e3
## What changed
- Add executor authority support to `skills.list` and `skills.read` for skills from selected capability roots.
- Allow `skills.read` to load package-relative resources referenced by an executor skill while keeping reads within the selected package.
- Paginate skill listings and resource contents with bounded response sizes and stale-cursor validation.
- Resolve executor filesystems per sampling step so skill tools use the active selected capability roots.
## Testing
- Extend the app-server executor skill test to list an executor skill, read its main `SKILL.md`, and read a paginated reference resource.
- Cover visibility filtering, metadata bounds, pagination, invalid cursors, package boundaries, and resource size limits.
GitOrigin-RevId: 1b5de95d5d1b9f79c34d2879d6e67f24401e9c29
## Why
A fixed character limit does not account for the different context-window sizes supported by models.
## What changed
- Budget extension-rendered skill metadata at 2% of the resolved model context window, capped at 4,000 tokens.
- Keep the existing 8,000-character fallback when model context metadata is unavailable.
- Include the omission marker in the budget and still emit it when no skill entry fits.
- Apply the same resolved budget to executor and host skill catalogs assembled for a turn.
## Testing
- Cover proportional and capped budgets, multibyte fallback accounting, and omission-marker behavior.
- Verify through the production turn path that larger model context windows include more catalog entries without exceeding the computed budget.
GitOrigin-RevId: 4667293f1594de4dd605b32b9fa4255d26772c0f
## What changed
- Define and export skill metadata, policy, dependency, interface, and configuration rule types from `codex-skills`.
- Update core, plugin, and extension consumers to use the shared models while retaining compatibility re-exports from `codex-core-skills`.
- Share product-restriction handling between host and environment skill metadata.
## Testing
- Add unit coverage for default and explicit implicit-invocation and product policies.
GitOrigin-RevId: 9093469f8fff8e2d902dc9781235f64709827a87
## What changed
- Add a bounded BM25 selector that ranks skills across name, short-description,
and description fields, weighting names most heavily and rare terms more highly.
- Run the selector alongside the weighted lexical method in the shadow-selection
experiment, with deterministic tie-breaking and existing per-method metrics.
- Add unit coverage for field weighting, rare-term ranking, unmatched queries,
and input limits.
- Remove the implicit-invocation integration test and its telemetry development
dependency.
GitOrigin-RevId: e5ac6a1f0d6a1f68b194467ef7da48adf4a88d7c
## What changed
- Add an opt-in `skill_search` feature that ranks prompt-visible skills against each turn's user input with a bounded weighted lexical selector.
- Keep the ranked selection out of model-visible context and record metrics for selection cost, catalog reduction, and whether later implicit or `skills.read` invocations matched the ranked candidates.
- Include host-provided skills in the experiment catalog without changing the rendered skill catalog.
## Testing
- Add selector unit tests covering ranking, limits, truncation, stop words, and deterministic tie-breaking.
- Add extension tests covering turn-local invocation recording and host-skill shadow selection.
GitOrigin-RevId: 4d00a1c805ea8b391d6c6ac6a8450afa88ca3e25
## Why
After #28918, selected skill roots are `PathUri`, but the executor skill
provider still converts them to the app-server host's `AbsolutePathBuf`.
A foreign Windows root therefore cannot be discovered by a Unix host,
and the inverse has the same problem.
This PR keeps executor skill discovery and reads on the filesystem that
owns the selected root while reusing the existing skill rules.
## What changed
- Generalize the existing skill traversal to operate on `PathUri`
through `ExecutorFileSystem`, preserving its depth, directory, symlink,
and sibling-metadata concurrency behavior.
- Add a small environment skill loader that reuses the shared discovery,
frontmatter validation, dependency parsing, product policy, and
prompt-visibility rules.
- Keep the environment id and entrypoint `PathUri` in the skill catalog,
then route `skills.read` back through the same environment filesystem.
- Preserve the executor's path convention when deriving catalog handles,
including literal backslashes in POSIX filenames.
- Resolve plugin namespaces from nearby manifests through URI-native
filesystem reads.
- Cover foreign Windows roots, executor-owned reads, namespaces,
metadata, policy, and path identity.
```text
selected root (PathUri)
|
v
shared discovery over ExecutorFileSystem
|
v
environment-bound catalog entry --skills.read--> same ExecutorFileSystem
```
No second filesystem abstraction or duplicate traversal implementation
is introduced.
## Stack
1. #29614 — add lexical `PathUri` containment.
2. #29620 — share URI-native manifest path resolution.
3. #28918 — keep selected plugin roots and resources URI-native.
4. **This PR** — load executor skills without host path conversion.
5. #29628 — resolve executor MCP working directories without host path
conversion.
## Why
First-party async traits should expose their `Send` contracts explicitly
without requiring `async_trait`. This completes the migration pattern
established in #27303 and #27304.
## What changed
- Replaced the remaining first-party `async_trait` traits with native
return-position `impl Future + Send` where statically dispatched and
explicit boxed `Send` futures where object safety is required.
- Kept implementations behavior-preserving, outlining existing async
bodies into inherent methods where that keeps the diff reviewable.
- Removed all direct first-party `async-trait` dependencies and the
workspace dependency declaration.
- Added a cargo-deny policy that permits `async-trait` only through the
remaining transitive wrapper crates.
- Updated `rand` from 0.8.5 to 0.8.6 to resolve RUSTSEC-2026-0097 and
keep the full cargo-deny check passing.
## Validation
- `just test -p codex-exec-server`: 216 passed, 2 skipped.
- `just test -p codex-model-provider`: 39 passed.
- `just test -p codex-core` and `just test`: changed tests passed;
remaining failures are environment-sensitive suites unrelated to this
migration.
- `cargo deny check`
- `just fix`
- `just fmt`
- `cargo shear`
- `just bazel-lock-check`
## Why
We're moving exec-server to use PathUri for its internal path
representations.
## What
Move `ExecutorFileSystem` APIs to use `PathUri` instead of
`AbsolutePathBuf`. Future changes will convert higher-level parts of
exec-server.
## Why
`ext/skills` currently depends on `codex-core` for two host concerns:
reading the concrete `Config` type and borrowing core-owned
model-context fragment types. That coupling prevents the extension from
being assembled independently above core and leaves context that belongs
to the skills feature owned by core.
This stacked PR introduces the host boundary needed for the broader
extension migration while intentionally preserving existing skills
behavior. It is stacked on #27404.
## What changed
- Adds a small public `SkillsExtensionConfig` view and makes skills
installation generic over the host config type.
- Requires the host to map its config into that view; app-server
supplies the current `Config` values.
- Moves the available-skills and selected-skill context fragment
implementations into `ext/skills`, preserving their roles, markers, and
rendered bytes.
- Removes the direct `codex-core` dependency from
`codex-skills-extension`.
- Keeps local discovery, invocation, side effects, and the
`codex-core-skills` compatibility types unchanged for later staged PRs.
## Behavior
This adds no capability and is intended to have no user-visible or
model-visible behavior change. The install API and ownership boundary
change internally; emitted skills context remains byte-for-byte
compatible.
## Validation
- Updates the skills extension integration coverage to use a host-owned
test config.
- Asserts the complete rendered catalog and selected-skill fragments,
including their roles and markers.
- `just bazel-lock-check`
- Rust tests and Clippy were not run locally per request; CI will run
them.
## Why
PR #27387 makes backend plugin skills discoverable and invocable without
an executor, but resources referenced by those skills still sit behind
the generic MCP resource surface. The model needs a skills-owned API
that preserves the provider authority and package boundary instead of
treating remote resources like local files.
This is stacked on #27387.
## What
- Adds one `skills` namespace with bounded `list` and `read` tools for
remote skill providers.
- Revalidates `authority + package` against the live remote catalog on
every read, then routes the opaque resource ID back through that
provider.
- Allows the backend provider to read canonical child `skill://`
resources while rejecting cross-package, non-canonical, query, fragment,
and traversal-shaped URIs.
- Caps each serialized tool result at 8 KB. Lists are paginated; reads
return an opaque continuation cursor.
- Marks the JSON output as external context so memory generation can
apply its normal suppression policy.
- Deliberately does not add `skills.search`; that waits for a bounded
plugin-service search contract.
## Tool contract
Pseudo-Python matching the wire shape:
```python
from typing import Literal, NotRequired, TypedDict
class RemoteSkillAuthority(TypedDict):
kind: Literal["remote"]
id: str # e.g. "codex_apps"
class RemoteSkill(TypedDict):
authority: RemoteSkillAuthority
package: str # opaque provider-owned package ID
name: str
description: str
main_resource: str # opaque provider-owned SKILL.md ID
class SkillsListParams(TypedDict):
cursor: NotRequired[str]
class SkillsListResult(TypedDict):
skills: list[RemoteSkill]
next_cursor: str | None
warnings: list[str]
truncated: bool
class SkillsReadParams(TypedDict):
authority: RemoteSkillAuthority # copied from skills.list
package: str # copied from skills.list
resource: str # provider-owned child resource ID
cursor: NotRequired[str] # copy next_cursor to continue
class SkillsReadResult(TypedDict):
resource: str
contents: str
next_cursor: str | None
truncated: bool
class Skills:
def list(self, params: SkillsListParams) -> SkillsListResult: ...
def read(self, params: SkillsReadParams) -> SkillsReadResult: ...
```
There is one namespace for all remote skills, not one tool or MCP server
per skill. No resource ID is converted into a filesystem path.
## Backend dependency
`/ps/mcp` must support direct reads of child resources such as
`skill://plugin_demo/deploy/references/deploy.md`. This PR implements
and tests the Codex side of that contract; production child reads remain
dependent on the corresponding plugin-service support. Search remains
out of scope until that service exposes a bounded search/resource API.
## Validation
- Added an app-server integration test covering `skills.list` followed
by `skills.read` with no executor.
- Ran `just fmt`.
- Ran `just bazel-lock-update` and `just bazel-lock-check`.
- Did not run Rust tests or Clippy locally, per request; CI will run
them.
## 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.
## Why
Extension contributors are registered behind `dyn Trait` objects, so
native `async fn`/RPITIT methods would make these traits
non-object-safe. Spell out the boxed, `Send` future contract directly so
`extension-api` no longer needs `async-trait` while retaining the
existing runtime model.
## What changed
- add a shared `ExtensionFuture` alias and use it for asynchronous
contributor methods
- migrate production and test implementations to return `Box::pin(async
move { ... })`
- remove `async-trait` dependencies where they are no longer used,
keeping it dev-only where unrelated test executors still require it
## Behavior
No behavior change is intended. Contributor futures remain boxed,
`Send`, dynamically dispatched, and lazily executed; cancellation and
callback ordering stay unchanged.
## Testing
- `just test -p codex-extension-api` (11 passed)
- affected extension crates (64 passed)
- targeted `codex-core` contributor tests (14 passed)
- `just fmt`
- `just bazel-lock-update`
- `just bazel-lock-check`
A broad local `codex-core` run compiled successfully but encountered
unrelated sandbox and missing test-binary fixture failures; CI will run
the full checks.
## 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.
## Why
The skills extension needs a real turn-time path before host, executor,
or remote skills can be routed through it. The previous code was mostly
a placeholder catalog/provider sketch, so there was no bounded
available-skills fragment, no source-owned `SKILL.md` read, and no place
for warnings or per-turn selection state to live.
This PR makes `ext/skills` the authority-preserving flow for listing
candidate skills and injecting only explicitly selected main prompts,
without adding more of that logic to `codex-core`.
## What changed
- Expands catalog entries with `main_prompt`, display path, short
description, dependency metadata, enabled/prompt visibility flags, and
authority/package-aware read requests.
- Replaces the placeholder `providers/*` modules with
`SkillProviderSource` and `SkillProviders`, routing list/read/search
calls by source kind and surfacing provider failures as warnings.
- Adds bounded available-skills rendering and `SKILL.md` main-prompt
truncation before the fragments enter model context.
- Resolves explicit skill selections from structured `UserInput::Skill`,
skill-file mentions, `skill://...` paths, and plain `$skill` text
mentions, then reads selected prompts through their owning provider.
- Stores mutable per-thread skills config and per-turn
catalog/selection/warning state.
- Adds `install_with_providers` so tests and future host wiring can
supply concrete providers.
## Testing
- Not run locally.
- Added `codex-rs/ext/skills/tests/skills_extension.rs` coverage for
available-catalog injection, selected prompt injection through the
owning provider, and prompt-hidden skills that remain invokable.
## Disclaimer
This is only here for iteration purpose! Do not make any code rely on
this
## Why
Skills still live behind `codex-core` discovery and injection paths, but
the extension system needs an authority-aware home before that logic can
move. This adds that boundary without changing current skills behavior,
and keeps host, executor, and remote skills distinct so future
list/read/search flows do not collapse back to ambient local paths.
## What changed
- Add the `codex-skills-extension` workspace/Bazel crate under
`ext/skills`.
- Define the initial catalog, authority, provider, and turn-state types
for authority-bound skill packages and resources.
- Register placeholder thread/config/prompt/turn lifecycle contributors
plus host, executor, and remote provider aggregation points.
- Capture the remaining extraction work as TODOs, including the missing
extension API hooks needed for per-turn catalog construction and typed
skill injection.
- Keep plugins outside the runtime skills model: plugin-installed skills
are treated as materialized host-owned skill sources once available.
## Verification
- Not run locally.