Parallelize environment skill loading (#29990)

## Why

Avoid a request waterfall for loading lots of skills at once by hiding
latency in concurrent tasks.

## What changed

Poll the per-skill parse futures concurrently with an order-preserving
stream capped at 64 in-flight loads. Results retain discovery order, and
the existing filtering, warnings, and final catalog sorting are
unchanged.
This commit is contained in:
Adam Perry @ OpenAI authored and GitHub committed 2026-06-25 10:02:07 +01:00
1 parent ab80d4d484
commit 5579792b3b
1 file changed
+26 -16
+26 -16
View File
@@ -10,6 +10,7 @@ use codex_utils_path_uri::PathUri;
use codex_utils_plugins::DISCOVERABLE_PLUGIN_MANIFEST_PATHS; use codex_utils_plugins::DISCOVERABLE_PLUGIN_MANIFEST_PATHS;
use codex_utils_plugins::plugin_namespace_for_root_uri; use codex_utils_plugins::plugin_namespace_for_root_uri;
use codex_utils_plugins::plugin_namespace_for_skill_uri; use codex_utils_plugins::plugin_namespace_for_skill_uri;
use futures::StreamExt;
use futures::future::join_all; use futures::future::join_all;
use crate::model::SkillDependencies; use crate::model::SkillDependencies;
@@ -31,6 +32,7 @@ use super::sanitize_single_line;
use super::validate_len; use super::validate_len;
const MAX_SKILLS_ENTRIES_PER_ROOT: usize = 20_000; const MAX_SKILLS_ENTRIES_PER_ROOT: usize = 20_000;
const MAX_CONCURRENT_SKILL_LOADS: usize = 64;
/// URI-native metadata for one skill owned by an execution environment. /// URI-native metadata for one skill owned by an execution environment.
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
@@ -220,24 +222,32 @@ pub async fn load_environment_skills_from_root(
.filter_map(|(plugin_root, namespace)| namespace.map(|namespace| (plugin_root, namespace))) .filter_map(|(plugin_root, namespace)| namespace.map(|namespace| (plugin_root, namespace)))
.collect::<HashMap<_, _>>(); .collect::<HashMap<_, _>>();
for path in discovery.skill_files { // Remote executors can multiplex these independent per-skill reads, so polling a bounded
let mut ancestor = path.parent(); // number together allows the I/O for each skill and its metadata to happen concurrently.
let plugin_namespace = loop { let skill_results = futures::stream::iter(discovery.skill_files)
let Some(current) = ancestor else { .map(|path| {
break None; let mut ancestor = path.parent();
let plugin_namespace = loop {
let Some(current) = ancestor else {
break None;
};
if let Some(namespace) = plugin_namespaces.get(&current) {
break Some(namespace.as_str());
}
ancestor = current.parent();
}; };
if let Some(namespace) = plugin_namespaces.get(&current) { async move {
break Some(namespace.as_str()); let result =
EnvironmentSkillMetadata::parse(file_system, &path, plugin_namespace).await;
(path, result)
} }
ancestor = current.parent(); })
}; .buffered(MAX_CONCURRENT_SKILL_LOADS)
match EnvironmentSkillMetadata::parse( .collect::<Vec<_>>()
file_system, .await;
&path,
/*plugin_namespace*/ plugin_namespace, for (path, result) in skill_results {
) match result {
.await
{
Ok(skill) if skill.matches_product_restriction(restriction_product) => { Ok(skill) if skill.matches_product_restriction(restriction_product) => {
outcome.skills.push(skill); outcome.skills.push(skill);
} }