[skills] cache executor catalog per thread

This commit is contained in:
bryanashley
2026-06-23 21:39:47 -07:00
parent 1d65ccabd5
commit bbbad9979b
4 changed files with 54 additions and 5 deletions

View File

@@ -293,12 +293,16 @@ impl<C> SkillsExtension<C> {
mut query: SkillListQuery,
thread_state: &SkillsThreadState,
) -> SkillCatalog {
let executor_query = query.clone();
let include_orchestrator_skills = query.include_orchestrator_skills;
let orchestrator_query = query.clone();
let mcp_resources = orchestrator_query.mcp_resources.clone();
query.include_orchestrator_skills = false;
let mut catalog = self.providers.list_for_turn(query).await;
let executor_catalog = thread_state
.executor_catalog_snapshot(self.providers.list_executor_for_turn(executor_query))
.await;
let mut catalog = self.providers.list_for_turn(query, executor_catalog).await;
if include_orchestrator_skills {
let orchestrator_catalog = thread_state
.orchestrator_catalog_snapshot(

View File

@@ -107,9 +107,40 @@ impl SkillProviders {
.any(|source| source.kind == SkillSourceKind::Orchestrator)
}
pub(crate) async fn list_for_turn(&self, query: SkillListQuery) -> SkillCatalog {
self.list_matching(&query, |source| source.should_list(&query))
.await
pub(crate) async fn list_for_turn(
&self,
query: SkillListQuery,
executor_catalog: SkillCatalog,
) -> SkillCatalog {
let mut catalog = SkillCatalog::default();
let mut executor_catalog = Some(executor_catalog);
for source in self
.sources
.iter()
.filter(|source| source.should_list(&query))
{
if source.kind == SkillSourceKind::Executor {
if let Some(executor_catalog) = executor_catalog.take() {
catalog.extend(executor_catalog);
}
continue;
}
extend_catalog(
&mut catalog,
source.provider.list(query.clone()).await,
source.label.as_str(),
);
}
catalog
}
pub(crate) async fn list_executor_for_turn(&self, query: SkillListQuery) -> SkillCatalog {
self.list_matching(&query, |source| {
source.kind == SkillSourceKind::Executor && source.should_list(&query)
})
.await
}
pub(crate) async fn list_orchestrator_for_turn(

View File

@@ -27,6 +27,7 @@ const MAX_CACHED_ORCHESTRATOR_CONTENT_BYTES: usize = 8 * 1024 * 1024;
pub(crate) struct SkillsThreadState {
config: Mutex<SkillsExtensionConfig>,
selected_roots: Vec<SelectedCapabilityRoot>,
executor_catalog: OnceCell<SkillCatalog>,
orchestrator_skills_available: bool,
orchestrator_cache: Mutex<Option<Arc<OrchestratorGenerationCache>>>,
}
@@ -40,6 +41,7 @@ impl SkillsThreadState {
Self {
config: Mutex::new(config),
selected_roots,
executor_catalog: OnceCell::new(),
orchestrator_skills_available,
orchestrator_cache: Mutex::new(None),
}
@@ -67,6 +69,16 @@ impl SkillsThreadState {
self.orchestrator_skills_available && self.config().orchestrator_skills_enabled
}
pub(crate) async fn executor_catalog_snapshot(
&self,
initialize: impl Future<Output = SkillCatalog> + Send,
) -> SkillCatalog {
self.executor_catalog
.get_or_init(|| initialize)
.await
.clone()
}
pub(crate) async fn orchestrator_catalog_snapshot(
&self,
mcp_resources: Option<&McpResourceClient>,

View File

@@ -147,6 +147,7 @@ async fn installed_extension_uses_host_service_snapshot() -> TestResult {
async fn selected_executor_catalog_is_context_and_selected_entrypoint_is_turn_input() -> TestResult
{
let read_requests = Arc::new(Mutex::new(Vec::new()));
let list_calls = Arc::new(AtomicUsize::new(0));
let executor_provider = Arc::new(StaticSkillProvider {
catalog: SkillCatalog {
entries: vec![test_entry(
@@ -158,7 +159,7 @@ async fn selected_executor_catalog_is_context_and_selected_entrypoint_is_turn_in
warnings: Vec::new(),
},
read_requests: Arc::clone(&read_requests),
list_calls: None,
list_calls: Some(Arc::clone(&list_calls)),
fail_first_list: false,
});
let providers = SkillProviders::new().with_executor_provider(executor_provider);
@@ -257,6 +258,7 @@ async fn selected_executor_catalog_is_context_and_selected_entrypoint_is_turn_in
.await;
assert!(next_fragments.is_empty());
assert_eq!(1, list_calls.load(Ordering::Relaxed));
Ok(())
}