mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
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.
This commit is contained in:
@@ -180,6 +180,36 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CapabilityRootLocation": {
|
||||
"description": "Location used to resolve a selected capability root.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "A path owned by an execution environment.",
|
||||
"properties": {
|
||||
"environmentId": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"environment"
|
||||
],
|
||||
"title": "EnvironmentCapabilityRootLocationType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"environmentId",
|
||||
"path",
|
||||
"type"
|
||||
],
|
||||
"title": "EnvironmentCapabilityRootLocation",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ClientInfo": {
|
||||
"properties": {
|
||||
"name": {
|
||||
@@ -2947,6 +2977,28 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"SelectedCapabilityRoot": {
|
||||
"description": "A user-selected root that can expose one or more runtime capabilities.",
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "Stable identifier supplied by the capability selection platform.",
|
||||
"type": "string"
|
||||
},
|
||||
"location": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/CapabilityRootLocation"
|
||||
}
|
||||
],
|
||||
"description": "Where the selected root can be resolved."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"location"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SendAddCreditsNudgeEmailParams": {
|
||||
"properties": {
|
||||
"creditType": {
|
||||
|
||||
+52
@@ -6765,6 +6765,36 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"CapabilityRootLocation": {
|
||||
"description": "Location used to resolve a selected capability root.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "A path owned by an execution environment.",
|
||||
"properties": {
|
||||
"environmentId": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"environment"
|
||||
],
|
||||
"title": "EnvironmentCapabilityRootLocationType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"environmentId",
|
||||
"path",
|
||||
"type"
|
||||
],
|
||||
"title": "EnvironmentCapabilityRootLocation",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"CodexErrorInfo": {
|
||||
"description": "This translation layer make sure that we expose codex error code in camel case.\n\nWhen an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.",
|
||||
"oneOf": [
|
||||
@@ -15061,6 +15091,28 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"SelectedCapabilityRoot": {
|
||||
"description": "A user-selected root that can expose one or more runtime capabilities.",
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "Stable identifier supplied by the capability selection platform.",
|
||||
"type": "string"
|
||||
},
|
||||
"location": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/CapabilityRootLocation"
|
||||
}
|
||||
],
|
||||
"description": "Where the selected root can be resolved."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"location"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SendAddCreditsNudgeEmailParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
|
||||
+52
@@ -1087,6 +1087,36 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"CapabilityRootLocation": {
|
||||
"description": "Location used to resolve a selected capability root.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "A path owned by an execution environment.",
|
||||
"properties": {
|
||||
"environmentId": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"environment"
|
||||
],
|
||||
"title": "EnvironmentCapabilityRootLocationType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"environmentId",
|
||||
"path",
|
||||
"type"
|
||||
],
|
||||
"title": "EnvironmentCapabilityRootLocation",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ClientInfo": {
|
||||
"properties": {
|
||||
"name": {
|
||||
@@ -11563,6 +11593,28 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"SelectedCapabilityRoot": {
|
||||
"description": "A user-selected root that can expose one or more runtime capabilities.",
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "Stable identifier supplied by the capability selection platform.",
|
||||
"type": "string"
|
||||
},
|
||||
"location": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/CapabilityRootLocation"
|
||||
}
|
||||
],
|
||||
"description": "Where the selected root can be resolved."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"location"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SendAddCreditsNudgeEmailParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
|
||||
@@ -64,6 +64,36 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"CapabilityRootLocation": {
|
||||
"description": "Location used to resolve a selected capability root.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "A path owned by an execution environment.",
|
||||
"properties": {
|
||||
"environmentId": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"environment"
|
||||
],
|
||||
"title": "EnvironmentCapabilityRootLocationType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"environmentId",
|
||||
"path",
|
||||
"type"
|
||||
],
|
||||
"title": "EnvironmentCapabilityRootLocation",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"DynamicToolSpec": {
|
||||
"properties": {
|
||||
"deferLoading": {
|
||||
@@ -106,6 +136,28 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"SelectedCapabilityRoot": {
|
||||
"description": "A user-selected root that can expose one or more runtime capabilities.",
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "Stable identifier supplied by the capability selection platform.",
|
||||
"type": "string"
|
||||
},
|
||||
"location": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/CapabilityRootLocation"
|
||||
}
|
||||
],
|
||||
"description": "Where the selected root can be resolved."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"location"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ThreadSource": {
|
||||
"enum": [
|
||||
"user",
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* Location used to resolve a selected capability root.
|
||||
*/
|
||||
export type CapabilityRootLocation = { "type": "environment", environmentId: string, path: string, };
|
||||
@@ -0,0 +1,17 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { CapabilityRootLocation } from "./CapabilityRootLocation";
|
||||
|
||||
/**
|
||||
* A user-selected root that can expose one or more runtime capabilities.
|
||||
*/
|
||||
export type SelectedCapabilityRoot = {
|
||||
/**
|
||||
* Stable identifier supplied by the capability selection platform.
|
||||
*/
|
||||
id: string,
|
||||
/**
|
||||
* Where the selected root can be resolved.
|
||||
*/
|
||||
location: CapabilityRootLocation, };
|
||||
@@ -40,6 +40,7 @@ export type { ByteRange } from "./ByteRange";
|
||||
export type { CancelLoginAccountParams } from "./CancelLoginAccountParams";
|
||||
export type { CancelLoginAccountResponse } from "./CancelLoginAccountResponse";
|
||||
export type { CancelLoginAccountStatus } from "./CancelLoginAccountStatus";
|
||||
export type { CapabilityRootLocation } from "./CapabilityRootLocation";
|
||||
export type { ChatgptAuthTokensRefreshParams } from "./ChatgptAuthTokensRefreshParams";
|
||||
export type { ChatgptAuthTokensRefreshReason } from "./ChatgptAuthTokensRefreshReason";
|
||||
export type { ChatgptAuthTokensRefreshResponse } from "./ChatgptAuthTokensRefreshResponse";
|
||||
@@ -334,6 +335,7 @@ export type { ReviewTarget } from "./ReviewTarget";
|
||||
export type { SandboxMode } from "./SandboxMode";
|
||||
export type { SandboxPolicy } from "./SandboxPolicy";
|
||||
export type { SandboxWorkspaceWrite } from "./SandboxWorkspaceWrite";
|
||||
export type { SelectedCapabilityRoot } from "./SelectedCapabilityRoot";
|
||||
export type { SendAddCreditsNudgeEmailParams } from "./SendAddCreditsNudgeEmailParams";
|
||||
export type { SendAddCreditsNudgeEmailResponse } from "./SendAddCreditsNudgeEmailResponse";
|
||||
export type { ServerRequestResolvedNotification } from "./ServerRequestResolvedNotification";
|
||||
|
||||
@@ -11,6 +11,8 @@ use super::TurnEnvironmentParams;
|
||||
use super::TurnItemsView;
|
||||
use super::shared::v2_enum_from_core;
|
||||
use codex_experimental_api_macros::ExperimentalApi;
|
||||
pub use codex_protocol::capabilities::CapabilityRootLocation;
|
||||
pub use codex_protocol::capabilities::SelectedCapabilityRoot;
|
||||
use codex_protocol::config_types::CollaborationMode;
|
||||
use codex_protocol::config_types::Personality;
|
||||
use codex_protocol::config_types::ReasoningSummary;
|
||||
@@ -153,6 +155,10 @@ pub struct ThreadStartParams {
|
||||
#[experimental("thread/start.dynamicTools")]
|
||||
#[ts(optional = nullable)]
|
||||
pub dynamic_tools: Option<Vec<DynamicToolSpec>>,
|
||||
/// Capability roots selected for this thread by the hosting platform.
|
||||
#[experimental("thread/start.selectedCapabilityRoots")]
|
||||
#[ts(optional = nullable)]
|
||||
pub selected_capability_roots: Option<Vec<SelectedCapabilityRoot>>,
|
||||
/// Test-only experimental field used to validate experimental gating and
|
||||
/// schema filtering behavior in a stable way.
|
||||
#[experimental("thread/start.mockExperimentalField")]
|
||||
|
||||
Reference in New Issue
Block a user