Skip to content
Prev Previous commit
Next Next commit
Use step-ready roots for executor skills
  • Loading branch information
jif-oai committed Jun 25, 2026
commit 3292f712e168b9f65c17aeffd6cdee90cd44c9ca
6 changes: 6 additions & 0 deletions codex-rs/core/src/session/world_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,18 @@ impl Session {
);
}
let environments = step_context.environments.to_selections();
let ready_selected_capability_roots = step_context
.selected_capability_roots
.iter()
.map(|root| root.selected_root().clone())
.collect::<Vec<_>>();
for contributor in self.services.extensions.context_contributors() {
for section in contributor
.contribute_world_state(WorldStateContributionInput {
thread_id: self.thread_id(),
turn_id: turn_context.sub_id.as_str(),
environments: &environments,
ready_selected_capability_roots: &ready_selected_capability_roots,
session_store: &self.services.session_extension_data,
thread_store: &self.services.thread_extension_data,
turn_store: turn_context.extension_data.as_ref(),
Expand Down
3 changes: 3 additions & 0 deletions codex-rs/ext/extension-api/src/contributors/world_state.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::sync::Arc;

use codex_protocol::ThreadId;
use codex_protocol::capabilities::SelectedCapabilityRoot;
use codex_protocol::protocol::TurnEnvironmentSelection;
use serde_json::Value;

Expand All @@ -11,6 +12,8 @@ pub struct WorldStateContributionInput<'a> {
pub thread_id: ThreadId,
pub turn_id: &'a str,
pub environments: &'a [TurnEnvironmentSelection],
/// Selected roots whose stable environments are ready in this sampling step.
pub ready_selected_capability_roots: &'a [SelectedCapabilityRoot],
pub session_store: &'a ExtensionData,
pub thread_store: &'a ExtensionData,
pub turn_store: &'a ExtensionData,
Expand Down
23 changes: 1 addition & 22 deletions codex-rs/ext/skills/src/extension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ use codex_extension_api::TurnInputContributor;
use codex_extension_api::WorldStateContributionInput;
use codex_extension_api::WorldStateSectionContribution;
use codex_mcp::McpResourceClient;
use codex_protocol::capabilities::CapabilityRootLocation;
use codex_protocol::capabilities::SelectedCapabilityRoot;
use codex_protocol::protocol::Event;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::WarningEvent;
Expand Down Expand Up @@ -61,18 +59,12 @@ where
{
fn on_thread_start<'a>(&'a self, input: ThreadStartInput<'a, C>) -> ExtensionFuture<'a, ()> {
Box::pin(async move {
let selected_roots = input
.thread_store
.get::<Vec<SelectedCapabilityRoot>>()
.map(|selected_roots| selected_roots.as_ref().clone())
.unwrap_or_default();
let orchestrator_skills_available = !input
.environments
.iter()
.any(|environment| environment.environment_id == LOCAL_ENVIRONMENT_ID);
input.thread_store.insert(SkillsThreadState::new(
(self.config_from_host)(input.config),
selected_roots,
orchestrator_skills_available,
));
})
Expand All @@ -97,7 +89,6 @@ where
let orchestrator_skills_available = true;
thread_store.insert(SkillsThreadState::new(
next_config,
Vec::new(),
orchestrator_skills_available,
));
}
Expand Down Expand Up @@ -154,24 +145,12 @@ where
return Vec::new();
};
let config = thread_state.config();
let ready_roots = thread_state
.selected_roots()
.iter()
.filter(|root| {
let CapabilityRootLocation::Environment { environment_id, .. } = &root.location;
input
.environments
.iter()
.any(|environment| environment.environment_id == *environment_id)
})
.cloned()
.collect();
let catalog = thread_state
.executor_catalog_snapshot(
&self.providers,
SkillListQuery {
turn_id: input.turn_id.to_string(),
executor_roots: ready_roots,
executor_roots: input.ready_selected_capability_roots.to_vec(),
host_snapshot: None,
include_host_skills: false,
include_bundled_skills: config.bundled_skills_enabled,
Expand Down
12 changes: 1 addition & 11 deletions codex-rs/ext/skills/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,15 @@ const MAX_CACHED_ORCHESTRATOR_CONTENT_BYTES: usize = 8 * 1024 * 1024;

pub(crate) struct SkillsThreadState {
config: Mutex<SkillsExtensionConfig>,
selected_roots: Vec<SelectedCapabilityRoot>,
orchestrator_skills_available: bool,
executor_cache: Mutex<Vec<CachedExecutorCatalog>>,
orchestrator_cache: Mutex<Option<Arc<OrchestratorGenerationCache>>>,
}

impl SkillsThreadState {
pub(crate) fn new(
config: SkillsExtensionConfig,
selected_roots: Vec<SelectedCapabilityRoot>,
orchestrator_skills_available: bool,
) -> Self {
pub(crate) fn new(config: SkillsExtensionConfig, orchestrator_skills_available: bool) -> Self {
Self {
config: Mutex::new(config),
selected_roots,
orchestrator_skills_available,
executor_cache: Mutex::new(Vec::new()),
orchestrator_cache: Mutex::new(None),
Expand All @@ -62,10 +56,6 @@ impl SkillsThreadState {
.unwrap_or_else(std::sync::PoisonError::into_inner) = config;
}

pub(crate) fn selected_roots(&self) -> &[SelectedCapabilityRoot] {
&self.selected_roots
}

pub(crate) fn orchestrator_skills_enabled(&self) -> bool {
self.orchestrator_skills_available && self.config().orchestrator_skills_enabled
}
Expand Down
38 changes: 20 additions & 18 deletions codex-rs/ext/skills/tests/skills_extension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,13 +171,13 @@ async fn selected_executor_catalog_follows_step_availability_and_reuses_its_cach

let session_store = ExtensionData::new("session");
let thread_store = ExtensionData::new("thread");
thread_store.insert(vec![SelectedCapabilityRoot {
let selected_roots = vec![SelectedCapabilityRoot {
id: "lint-fix".to_string(),
location: CapabilityRootLocation::Environment {
environment_id: "env-1".to_string(),
path: PathUri::parse("file:///skills/lint-fix").expect("skill root URI"),
},
}]);
}];
let session_source = SessionSource::Cli;
let config = default_config();
registry.thread_lifecycle_contributors()[0]
Expand All @@ -197,15 +197,16 @@ async fn selected_executor_catalog_follows_step_availability_and_reuses_its_cach
assert!(prompt_fragments.is_empty());

let turn_store = ExtensionData::new("turn-1");
let ready_environment = TurnEnvironmentSelection {
environment_id: "env-1".to_string(),
let turn_environment = TurnEnvironmentSelection {
environment_id: "turn-env".to_string(),
cwd: PathUri::parse("file:///workspace").expect("cwd URI"),
};
let available_sections = registry.context_contributors()[0]
.contribute_world_state(WorldStateContributionInput {
thread_id: codex_protocol::ThreadId::new(),
turn_id: "turn-1",
environments: std::slice::from_ref(&ready_environment),
environments: std::slice::from_ref(&turn_environment),
ready_selected_capability_roots: &selected_roots,
session_store: &session_store,
thread_store: &thread_store,
turn_store: &turn_store,
Expand Down Expand Up @@ -257,6 +258,7 @@ async fn selected_executor_catalog_follows_step_availability_and_reuses_its_cach
thread_id: codex_protocol::ThreadId::new(),
turn_id: "turn-2",
environments: &[],
ready_selected_capability_roots: &[],
session_store: &session_store,
thread_store: &thread_store,
turn_store: &unavailable_turn_store,
Expand All @@ -277,7 +279,8 @@ async fn selected_executor_catalog_follows_step_availability_and_reuses_its_cach
.contribute_world_state(WorldStateContributionInput {
thread_id: codex_protocol::ThreadId::new(),
turn_id: "turn-3",
environments: &[ready_environment],
environments: &[turn_environment],
ready_selected_capability_roots: &selected_roots,
session_store: &session_store,
thread_store: &thread_store,
turn_store: &restored_turn_store,
Expand Down Expand Up @@ -519,18 +522,16 @@ async fn root_qualified_locator_selects_only_the_matching_executor_skill() -> Te
let registry = builder.build();
let session_store = ExtensionData::new("session");
let thread_store = ExtensionData::new("thread");
thread_store.insert(
[("root-a", "/skills/root-a"), ("root-b", "/skills/root-b")]
.into_iter()
.map(|(id, path)| SelectedCapabilityRoot {
id: id.to_string(),
location: CapabilityRootLocation::Environment {
environment_id: "env-1".to_string(),
path: PathUri::parse(&format!("file://{path}")).expect("skill root URI"),
},
})
.collect::<Vec<_>>(),
);
let selected_roots = [("root-a", "/skills/root-a"), ("root-b", "/skills/root-b")]
.into_iter()
.map(|(id, path)| SelectedCapabilityRoot {
id: id.to_string(),
location: CapabilityRootLocation::Environment {
environment_id: "env-1".to_string(),
path: PathUri::parse(&format!("file://{path}")).expect("skill root URI"),
},
})
.collect::<Vec<_>>();
let session_source = SessionSource::Cli;
let config = default_config();
registry.thread_lifecycle_contributors()[0]
Expand All @@ -553,6 +554,7 @@ async fn root_qualified_locator_selects_only_the_matching_executor_skill() -> Te
environment_id: "env-1".to_string(),
cwd: PathUri::parse("file:///workspace").expect("cwd URI"),
}],
ready_selected_capability_roots: &selected_roots,
session_store: &session_store,
thread_store: &thread_store,
turn_store: &turn_store,
Expand Down
Loading