mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
Use the host skill provider catalog in world state (#36309)
## What changed - Load and cache the host skill provider catalog during world-state contribution. - Render host skill listings from that catalog with Core-compatible budgeting, while preserving Core's full prompt injection for selected host skills. - Reuse the cached host catalog for shadow selection, including when skill listings are disabled. - Preserve an empty Core-compatible skills fragment when every entry exceeds the metadata budget, and make budget warnings independent of the configured percentage. ## Testing - Cover provider-backed host listings, selected-skill prompt injection, shadow selection, disabled listings, oversized prompts, empty fragments, metrics, and warnings. GitOrigin-RevId: b6b2d8ea14386356094cff5783d121d74657e9e3
This commit is contained in:
1 parent
bf4d3f51ea
commit
5e8b22488f
11 files changed
+545
-166
No files matched your search
@@ -64,6 +64,7 @@ use crate::shadow_selection_experiment::ShadowSelectionExperiment;
|
||||
use crate::sources::SkillProviders;
|
||||
use crate::state::EmittedCatalogBudgetWarnings;
|
||||
use crate::state::ExecutorSkillsStepState;
|
||||
use crate::state::HostSkillsStepState;
|
||||
use crate::state::SkillsSessionState;
|
||||
use crate::state::SkillsThreadState;
|
||||
use crate::state::SkillsTurnState;
|
||||
@@ -72,7 +73,6 @@ use crate::warnings::bounded_warnings;
|
||||
use crate::world_state::HostSkillsWarningEmitter;
|
||||
use crate::world_state::executor_skills_world_state_section;
|
||||
use crate::world_state::host_skills_world_state_section;
|
||||
use crate::world_state::rendered_host_skills_world_state_section;
|
||||
|
||||
struct SkillsExtension<C> {
|
||||
providers: SkillProviders,
|
||||
@@ -283,42 +283,50 @@ where
|
||||
let metadata_budget = skill_metadata_budget(context_window);
|
||||
let extension_metrics = input.extension_metrics.clone();
|
||||
let host_snapshot = input.turn_store.get::<HostSkillsSnapshot>();
|
||||
let shared_rendered = if config.include_instructions
|
||||
let needs_host_catalog = config.include_instructions || config.shadow_selection_enabled;
|
||||
let host_catalog = if needs_host_catalog
|
||||
&& let Some(host_snapshot) = host_snapshot.as_ref()
|
||||
&& self.providers.has_host_provider()
|
||||
{
|
||||
let host_catalog = self
|
||||
.providers
|
||||
.list_host_for_turn(SkillListQuery {
|
||||
turn_id: input.turn_id.to_string(),
|
||||
executor_roots: Vec::new(),
|
||||
resolved_executor_roots: Vec::new(),
|
||||
host_snapshot: Some(Arc::clone(host_snapshot)),
|
||||
include_host_skills: true,
|
||||
include_bundled_skills: false,
|
||||
include_orchestrator_skills: false,
|
||||
mcp_resources: None,
|
||||
executor_capability_discovery: None,
|
||||
})
|
||||
.await;
|
||||
let (host_rendered, executor_rendered) =
|
||||
render_combined_available_skills(&host_catalog, &catalog, metadata_budget);
|
||||
if host_catalog
|
||||
Some(
|
||||
self.providers
|
||||
.list_host_for_turn(SkillListQuery {
|
||||
turn_id: input.turn_id.to_string(),
|
||||
executor_roots: Vec::new(),
|
||||
resolved_executor_roots: Vec::new(),
|
||||
host_snapshot: Some(Arc::clone(host_snapshot)),
|
||||
include_host_skills: true,
|
||||
include_bundled_skills: false,
|
||||
include_orchestrator_skills: false,
|
||||
mcp_resources: None,
|
||||
executor_capability_discovery: None,
|
||||
})
|
||||
.await,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(host_catalog) = host_catalog.as_ref() {
|
||||
input
|
||||
.turn_store
|
||||
.insert(HostSkillsStepState(host_catalog.clone()));
|
||||
}
|
||||
let host_catalog_for_rendering = if config.include_instructions {
|
||||
host_catalog.as_ref()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let shared_rendered = host_catalog_for_rendering.and_then(|host_catalog| {
|
||||
(host_catalog
|
||||
.entries
|
||||
.iter()
|
||||
.any(SkillCatalogEntry::is_model_visible)
|
||||
&& catalog
|
||||
.entries
|
||||
.iter()
|
||||
.any(SkillCatalogEntry::is_model_visible)
|
||||
{
|
||||
Some((host_rendered, executor_rendered))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
.any(SkillCatalogEntry::is_model_visible))
|
||||
.then(|| render_combined_available_skills(host_catalog, &catalog, metadata_budget))
|
||||
});
|
||||
let (rendered, rendered_host) =
|
||||
if let Some((host_rendered, executor_rendered)) = shared_rendered {
|
||||
if let Some(host_rendered) = host_rendered.as_ref() {
|
||||
@@ -335,6 +343,16 @@ where
|
||||
host_rendered,
|
||||
)
|
||||
} else {
|
||||
let rendered_host = host_catalog_for_rendering.and_then(|host_catalog| {
|
||||
render_available_skills(
|
||||
host_catalog,
|
||||
SkillCatalogRenderPolicy::CoreCompatible,
|
||||
metadata_budget,
|
||||
)
|
||||
});
|
||||
if let Some(rendered_host) = rendered_host.as_ref() {
|
||||
trace_catalog_budget_pressure(metadata_budget, &rendered_host.report);
|
||||
}
|
||||
(
|
||||
if config.include_instructions {
|
||||
render_catalog(
|
||||
@@ -348,12 +366,29 @@ where
|
||||
} else {
|
||||
RenderedCatalog::default()
|
||||
},
|
||||
None,
|
||||
rendered_host,
|
||||
)
|
||||
};
|
||||
let host_warning_message = rendered_host
|
||||
.as_ref()
|
||||
.and_then(|rendered_host| rendered_host.report.warning_message());
|
||||
let host_render_metrics = config.include_instructions.then(|| {
|
||||
rendered_host
|
||||
.as_ref()
|
||||
.map(|rendered_host| {
|
||||
let report = &rendered_host.report;
|
||||
(
|
||||
report.total_count,
|
||||
report.included_count,
|
||||
report.omitted_count,
|
||||
report.truncated_description_chars,
|
||||
)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
});
|
||||
let host_body = rendered_host
|
||||
.and_then(|rendered_host| rendered_host.into_fragment(include_usage))
|
||||
.map(|fragment| fragment.body());
|
||||
let executor_body = rendered.fragment.map(|fragment| fragment.body());
|
||||
let mut sections = vec![executor_skills_world_state_section(
|
||||
executor_body,
|
||||
@@ -362,9 +397,7 @@ where
|
||||
let emitted_warnings = input
|
||||
.turn_store
|
||||
.get_or_init(EmittedCatalogBudgetWarnings::default);
|
||||
if let Some(host_snapshot) = host_snapshot
|
||||
&& self.providers.has_host_provider()
|
||||
{
|
||||
if host_snapshot.is_some() && self.providers.has_host_provider() {
|
||||
input.turn_store.insert(HostSkillsCatalogInWorldState);
|
||||
let event_sink = Arc::clone(&self.event_sink);
|
||||
let emitted_warnings = Arc::clone(&emitted_warnings);
|
||||
@@ -379,35 +412,14 @@ where
|
||||
});
|
||||
}
|
||||
});
|
||||
if let Some(rendered_host) = rendered_host {
|
||||
let report = &rendered_host.report;
|
||||
let render_metrics = Some((
|
||||
report.total_count,
|
||||
report.included_count,
|
||||
report.omitted_count,
|
||||
report.truncated_description_chars,
|
||||
));
|
||||
let body = rendered_host
|
||||
.into_fragment(include_usage)
|
||||
.map(|fragment| fragment.body());
|
||||
sections.push(rendered_host_skills_world_state_section(
|
||||
body,
|
||||
config.include_instructions,
|
||||
render_metrics,
|
||||
extension_metrics,
|
||||
host_warning_message,
|
||||
warning_emitter,
|
||||
));
|
||||
} else {
|
||||
sections.push(host_skills_world_state_section(
|
||||
&host_snapshot,
|
||||
config.include_instructions,
|
||||
include_usage,
|
||||
metadata_budget,
|
||||
extension_metrics,
|
||||
warning_emitter,
|
||||
));
|
||||
}
|
||||
sections.push(host_skills_world_state_section(
|
||||
host_body,
|
||||
config.include_instructions,
|
||||
host_render_metrics,
|
||||
extension_metrics,
|
||||
host_warning_message,
|
||||
warning_emitter,
|
||||
));
|
||||
}
|
||||
if let Some(message) = rendered.warning_message
|
||||
&& emitted_warnings.insert(&message)
|
||||
@@ -521,18 +533,18 @@ where
|
||||
let host_snapshot = turn_store.get::<HostSkillsSnapshot>();
|
||||
let host_catalog_in_world_state =
|
||||
turn_store.get::<HostSkillsCatalogInWorldState>().is_some();
|
||||
let host_skills = turn_store.get::<HostSkillsStepState>();
|
||||
let query = SkillListQuery {
|
||||
turn_id: input.turn_id.clone(),
|
||||
executor_roots: Vec::new(),
|
||||
resolved_executor_roots: Vec::new(),
|
||||
host_snapshot: host_snapshot.clone(),
|
||||
include_host_skills: !host_catalog_in_world_state,
|
||||
include_host_skills: host_skills.is_none() && !host_catalog_in_world_state,
|
||||
include_bundled_skills: config.bundled_skills_enabled,
|
||||
include_orchestrator_skills: thread_state.orchestrator_skills_enabled(),
|
||||
mcp_resources: mcp_resources.clone(),
|
||||
executor_capability_discovery: None,
|
||||
};
|
||||
let host_query = query.clone();
|
||||
let mut catalog = turn_store
|
||||
.get::<ExecutorSkillsStepState>()
|
||||
.map(|executor_skills| executor_skills.0.clone())
|
||||
@@ -545,8 +557,8 @@ where
|
||||
let selected_entries = collect_explicit_skill_mentions(&input.user_input, &catalog);
|
||||
let shadow_selection_turn = if config.shadow_selection_enabled {
|
||||
let mut shadow_catalog = catalog.clone();
|
||||
if host_catalog_in_world_state && host_snapshot.is_some() {
|
||||
shadow_catalog.extend(self.providers.list_host_for_turn(host_query).await);
|
||||
if let Some(host_skills) = host_skills {
|
||||
shadow_catalog.extend(host_skills.0.clone());
|
||||
}
|
||||
let shadow_selected_entries =
|
||||
collect_explicit_skill_mentions(&input.user_input, &shadow_catalog);
|
||||
@@ -562,9 +574,7 @@ where
|
||||
thread_state
|
||||
.replace_shadow_selection_turn(input.turn_id.clone(), shadow_selection_turn);
|
||||
let mut fragments: Vec<Box<dyn ContextualUserFragment + Send>> = Vec::new();
|
||||
if config.include_instructions
|
||||
&& turn_store.get::<HostSkillsCatalogInWorldState>().is_none()
|
||||
{
|
||||
if config.include_instructions && !host_catalog_in_world_state {
|
||||
let mut turn_catalog = catalog.clone();
|
||||
turn_catalog.entries.retain(|entry| {
|
||||
entry.authority.kind != SkillSourceKind::Executor
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use codex_core_skills::AvailableSkills;
|
||||
use codex_core_skills::SKILLS_HOW_TO_USE_WITH_ABSOLUTE_PATHS;
|
||||
use codex_core_skills::SKILLS_HOW_TO_USE_WITH_ALIASES;
|
||||
use codex_core_skills::render_available_skills_body;
|
||||
@@ -32,26 +31,6 @@ impl AvailableSkillsInstructions {
|
||||
skill_lines,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_available_skills(
|
||||
available: AvailableSkills,
|
||||
include_skills_usage_instructions: bool,
|
||||
) -> Self {
|
||||
let mut skill_lines = available.skill_lines;
|
||||
if include_skills_usage_instructions {
|
||||
skill_lines.push("### How to use skills".to_string());
|
||||
let instructions = if available.skill_root_lines.is_empty() {
|
||||
SKILLS_HOW_TO_USE_WITH_ABSOLUTE_PATHS
|
||||
} else {
|
||||
SKILLS_HOW_TO_USE_WITH_ALIASES
|
||||
};
|
||||
skill_lines.push(instructions.to_string());
|
||||
}
|
||||
Self {
|
||||
skill_root_lines: available.skill_root_lines,
|
||||
skill_lines,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ContextualUserFragment for AvailableSkillsInstructions {
|
||||
|
||||
@@ -440,6 +440,7 @@ struct RenderedCatalog {
|
||||
pub(crate) struct AvailableSkillsRender {
|
||||
skill_root_lines: Vec<String>,
|
||||
skill_lines: Vec<String>,
|
||||
preserve_empty_fragment: bool,
|
||||
pub(crate) report: SkillRenderReport,
|
||||
}
|
||||
|
||||
@@ -448,7 +449,7 @@ impl AvailableSkillsRender {
|
||||
self,
|
||||
include_skills_usage_instructions: bool,
|
||||
) -> Option<AvailableSkillsInstructions> {
|
||||
(!self.skill_lines.is_empty()).then(|| {
|
||||
(self.preserve_empty_fragment || !self.skill_lines.is_empty()).then(|| {
|
||||
AvailableSkillsInstructions::from_skill_lines(
|
||||
self.skill_root_lines,
|
||||
self.skill_lines,
|
||||
@@ -501,6 +502,7 @@ pub(crate) fn render_available_skills(
|
||||
Some(AvailableSkillsRender {
|
||||
skill_root_lines: selected.skill_root_lines,
|
||||
skill_lines: selected.skill_lines,
|
||||
preserve_empty_fragment: policy == SkillCatalogRenderPolicy::CoreCompatible,
|
||||
report: selected.report,
|
||||
})
|
||||
}
|
||||
@@ -615,6 +617,7 @@ fn render_combined_group(
|
||||
AvailableSkillsRender {
|
||||
skill_root_lines,
|
||||
skill_lines: lines.into_iter().map(|rendered| rendered.line).collect(),
|
||||
preserve_empty_fragment: false,
|
||||
report: SkillRenderReport {
|
||||
total_count: skill_lines.len(),
|
||||
included_count: skill_lines.len().saturating_sub(omitted_count),
|
||||
|
||||
@@ -387,6 +387,10 @@ fn mixed_catalog_reserves_executor_omission_marker_by_omitting_host_first() {
|
||||
"- 1 additional skill omitted from this bounded skills list.".to_string(),
|
||||
]
|
||||
);
|
||||
assert!(
|
||||
host.into_fragment(/*include_skills_usage_instructions*/ false)
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -630,10 +634,11 @@ fn catalog_emits_omission_marker_when_every_minimum_skill_line_exceeds_budget()
|
||||
)
|
||||
.expect("core-compatible report should render");
|
||||
assert_eq!(core_render.report, expected_report);
|
||||
assert_eq!(
|
||||
core_render.into_fragment(/*include_skills_usage_instructions*/ false),
|
||||
None
|
||||
);
|
||||
let core_fragment = core_render
|
||||
.into_fragment(/*include_skills_usage_instructions*/ false)
|
||||
.expect("core-compatible rendering should preserve an empty skills fragment");
|
||||
assert!(core_fragment.body().contains("## Skills"));
|
||||
assert!(!core_fragment.body().contains("- oversized:"));
|
||||
let render = render_available_skills(
|
||||
&catalog,
|
||||
SkillCatalogRenderPolicy::ExtensionCompatible,
|
||||
|
||||
@@ -375,3 +375,6 @@ pub(crate) struct SkillsTurnState {
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub(crate) struct ExecutorSkillsStepState(pub(crate) SkillCatalog);
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub(crate) struct HostSkillsStepState(pub(crate) SkillCatalog);
|
||||
@@ -1,9 +1,5 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_core_skills::HostSkillsSnapshot;
|
||||
use codex_core_skills::build_available_skills;
|
||||
use codex_core_skills::render::SkillRenderSideEffects;
|
||||
use codex_extension_api::ContextualUserFragment;
|
||||
use codex_extension_api::ExtensionMetrics;
|
||||
use codex_extension_api::PreviousWorldStateSection;
|
||||
use codex_extension_api::RenderedWorldStateFragment;
|
||||
@@ -12,8 +8,6 @@ use codex_protocol::protocol::SKILLS_INSTRUCTIONS_CLOSE_TAG;
|
||||
use codex_protocol::protocol::SKILLS_INSTRUCTIONS_OPEN_TAG;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::fragments::AvailableSkillsInstructions;
|
||||
use crate::render::SkillMetadataBudget;
|
||||
use crate::render_observability::CatalogSurface;
|
||||
use crate::render_observability::record_catalog_metrics;
|
||||
|
||||
@@ -82,60 +76,6 @@ pub(crate) fn executor_skills_world_state_section(
|
||||
}
|
||||
|
||||
pub(crate) fn host_skills_world_state_section(
|
||||
host_snapshot: &HostSkillsSnapshot,
|
||||
include_instructions: bool,
|
||||
include_skills_usage_instructions: bool,
|
||||
metadata_budget: SkillMetadataBudget,
|
||||
extension_metrics: Option<Arc<dyn ExtensionMetrics>>,
|
||||
warning_emitter: HostSkillsWarningEmitter,
|
||||
) -> WorldStateSectionContribution {
|
||||
let outcome = host_snapshot.outcome();
|
||||
let metadata_budget = match metadata_budget {
|
||||
SkillMetadataBudget::Tokens(limit) => codex_core_skills::SkillMetadataBudget::Tokens(limit),
|
||||
SkillMetadataBudget::Characters(limit) => {
|
||||
codex_core_skills::SkillMetadataBudget::Characters(limit)
|
||||
}
|
||||
};
|
||||
let available = if include_instructions {
|
||||
build_available_skills(outcome, metadata_budget, SkillRenderSideEffects::None)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let warning_message = available
|
||||
.as_ref()
|
||||
.and_then(|available| available.warning_message.clone());
|
||||
let render_metrics = include_instructions.then(|| {
|
||||
available
|
||||
.as_ref()
|
||||
.map(|available| {
|
||||
let report = &available.report;
|
||||
(
|
||||
report.total_count,
|
||||
report.included_count,
|
||||
report.omitted_count,
|
||||
report.truncated_description_chars,
|
||||
)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
});
|
||||
let body = available.map(|available| {
|
||||
AvailableSkillsInstructions::from_available_skills(
|
||||
available,
|
||||
include_skills_usage_instructions,
|
||||
)
|
||||
.body()
|
||||
});
|
||||
rendered_host_skills_world_state_section(
|
||||
body,
|
||||
include_instructions,
|
||||
render_metrics,
|
||||
extension_metrics,
|
||||
warning_message,
|
||||
warning_emitter,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn rendered_host_skills_world_state_section(
|
||||
body: Option<String>,
|
||||
include_instructions: bool,
|
||||
render_metrics: Option<(usize, usize, usize, usize)>,
|
||||
|
||||
Reference in new issue
Block a user