Acquire and cache opt-in executor capability inventories via V2 (#53736)

## What changed

- Add the disabled-by-default `executor_capability_discovery_v2` feature to acquire `capabilities/discoverV2` inventories from supporting remote executors using each selected environment's working directory and sandbox.
- Share snapshots with MCP and world-state contributors and skill providers without activating discovered plugins or publishing discovered skills to the model.
- Cache inventories by environment instance, executor session, working directory, and sandbox. Coalesce concurrent requests, refresh the cache at turn start, retry transient failures, and refresh MCP projections after recovery.
- Resolve the system skills root in the executor and pass it explicitly to capability location discovery.

## Testing

Add coverage for feature gating, unsupported executors, inert inventories, cache reuse and eviction, turn refreshes, failure recovery, executor replacement, and system skill locations.

GitOrigin-RevId: 1bdca28fa7f17622b27e2713ab3dcc0c13426889
This commit is contained in:
Anthony Tafoya
2026-10-13 18:15:50 +00:00
committed by copyberry
parent 4d82d36ccf
commit cf0ed4c600
39 changed files with 1027 additions and 12 deletions
@@ -8,7 +8,6 @@ use std::io;
use crate::LocalFileSystem;
use crate::protocol::FsOpenMode;
use codex_config::loader::system_config_toml_file;
use codex_file_system::ExecutorFileSystem;
use codex_file_system::WalkEntryKind;
use codex_file_system::WalkOptions;
@@ -24,11 +23,12 @@ use codex_core_plugin_common::installed::parse_remote_plugin_id;
use codex_core_plugin_common::plugin_id::PluginId;
use tokio::io::AsyncReadExt;
/// Home paths are supplied by the executor, not read from the sandbox helper's sanitized env.
/// Fixed paths are supplied by the executor, not read from the sandbox helper's sanitized env.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub(crate) struct CapabilityLocationRequest {
pub codex_home: PathUri,
pub user_home: Option<PathUri>,
pub system_skills_root: Option<PathUri>,
}
const MAX_LOCATIONS: usize = 512;
@@ -73,8 +73,8 @@ pub(crate) async fn prewarm_locations(
if let Some(home) = user_home {
skill_roots.push(home.join(AGENTS_SKILLS_SUFFIX));
}
if let Some(system_root) = system_config_toml_file()?.parent() {
skill_roots.push(system_root.join("skills"));
if let Some(system_root) = &request.system_skills_root {
skill_roots.push(system_root.to_abs_path()?);
}
CandidateCapabilityLocations::resolve(
fs,
@@ -211,4 +211,4 @@ async fn read_remote_plugin_id(path: &std::path::Path) -> io::Result<Option<Stri
#[cfg(test)]
#[path = "tests/capability_locations_tests.rs"]
pub(super) mod tests;
pub(crate) mod tests;
@@ -9,6 +9,7 @@ use crate::discover_v2::capability_discoveries::load_capability_discoveries;
use crate::discover_v2::capability_locations::CapabilityLocationRequest;
use crate::discover_v2::capability_locations::CapabilityLocations;
use crate::discover_v2::capability_locations::prewarm_locations;
use codex_config::loader::system_config_toml_file;
use codex_file_watcher::WatchRegistration;
use codex_utils_path_uri::PathUri;
use std::io;
@@ -55,7 +56,7 @@ impl CapabilityManager {
})
}
/// Resolves home paths on first initialization and reuses the prewarmed locations.
/// Resolves fixed paths on first initialization and reuses the prewarmed locations.
pub(crate) async fn prewarm_locations(
self: &Arc<Self>,
) -> io::Result<&(CapabilityLocationRequest, CapabilityLocations)> {
@@ -66,6 +67,9 @@ impl CapabilityManager {
codex_home: PathUri::from_abs_path(&codex_utils_home_dir::find_codex_home()?),
user_home: dirs::home_dir()
.and_then(|home| PathUri::from_host_native_path(home).ok()),
system_skills_root: system_config_toml_file()?
.parent()
.map(|root| PathUri::from_abs_path(&root.join("skills"))),
};
let locations = prewarm_locations(&self.file_system, &request).await?;
Ok::<_, io::Error>((request, locations))
@@ -14,12 +14,15 @@ use crate::LocalFileSystem;
pub(crate) const MANIFEST: &str = r#"{"name":"demo","version":"1.10.0"}"#;
pub(crate) fn request(root: &Path) -> anyhow::Result<CapabilityLocationRequest> {
for path in ["codex", "home", "project"] {
for path in ["codex", "home", "project", "etc/codex"] {
std::fs::create_dir_all(root.join(path))?;
}
Ok(CapabilityLocationRequest {
codex_home: PathUri::from_host_native_path(root.join("codex"))?,
user_home: Some(PathUri::from_host_native_path(root.join("home"))?),
system_skills_root: Some(PathUri::from_host_native_path(
root.join("etc/codex/skills"),
)?),
})
}
@@ -42,6 +45,7 @@ async fn prewarming_resolves_global_locations_without_cwd_or_config() -> anyhow:
for root in [
"codex/skills",
"home/.agents/skills",
"etc/codex/skills",
"project/.agents/skills",
] {
write(temp.path(), &format!("{root}/demo/SKILL.md"), "unparsed")?;
@@ -69,7 +73,7 @@ async fn prewarming_resolves_global_locations_without_cwd_or_config() -> anyhow:
remote_plugin_id: Some("plugins~demo".to_string()),
}),
}];
for root in ["codex/skills", "home/.agents/skills"] {
for root in ["codex/skills", "home/.agents/skills", "etc/codex/skills"] {
expected.push(CapabilityLocation {
root: PathUri::from_host_native_path(temp.path().join(root))?,
plugin: None,