mirror of
https://github.com/openai/codex.git
synced 2026-11-13 21:30:47 +00:00
refactor to use generalized subagent config builder
This commit is contained in:
@@ -4,6 +4,7 @@ pub(crate) mod mailbox;
|
||||
mod registry;
|
||||
pub(crate) mod role;
|
||||
pub(crate) mod status;
|
||||
pub(crate) mod subagent_config;
|
||||
|
||||
pub(crate) use codex_protocol::protocol::AgentStatus;
|
||||
pub(crate) use control::AgentControl;
|
||||
@@ -12,3 +13,7 @@ pub(crate) use mailbox::MailboxReceiver;
|
||||
pub(crate) use registry::exceeds_thread_spawn_depth_limit;
|
||||
pub(crate) use registry::next_thread_spawn_depth;
|
||||
pub(crate) use status::agent_status_from_event;
|
||||
pub(crate) use subagent_config::SubAgentConfigBuilder;
|
||||
pub(crate) use subagent_config::SubAgentExtensionInheritance;
|
||||
pub(crate) use subagent_config::SubAgentPromptInheritance;
|
||||
pub(crate) use subagent_config::SubAgentPromptSelection;
|
||||
|
||||
@@ -0,0 +1,320 @@
|
||||
use anyhow::Context;
|
||||
use codex_features::Feature;
|
||||
|
||||
use crate::codex::TurnContext;
|
||||
use crate::config::Config;
|
||||
use crate::config::Constrained;
|
||||
use crate::config::InitialContextInclusions;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) enum SubAgentPromptInheritance {
|
||||
InheritParent,
|
||||
None,
|
||||
Select(SubAgentPromptSelection),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct SubAgentPromptSelection {
|
||||
pub(crate) model_update: bool,
|
||||
pub(crate) base_instructions: bool,
|
||||
pub(crate) user_instructions: bool,
|
||||
pub(crate) project_docs: bool,
|
||||
pub(crate) developer_instructions: bool,
|
||||
pub(crate) separate_developer_instructions: bool,
|
||||
pub(crate) compact_prompt: bool,
|
||||
pub(crate) permissions: bool,
|
||||
pub(crate) memory: bool,
|
||||
pub(crate) collaboration: bool,
|
||||
pub(crate) realtime: bool,
|
||||
pub(crate) personality: bool,
|
||||
pub(crate) apps: bool,
|
||||
pub(crate) skills: bool,
|
||||
pub(crate) plugins: bool,
|
||||
pub(crate) commit: bool,
|
||||
pub(crate) environment_context: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) enum SubAgentExtensionInheritance {
|
||||
InheritParent,
|
||||
None,
|
||||
Select(SubAgentExtensionSelection),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct SubAgentExtensionSelection {
|
||||
pub(crate) mcp_servers: bool,
|
||||
pub(crate) apps: bool,
|
||||
pub(crate) plugins: bool,
|
||||
pub(crate) tool_search: bool,
|
||||
pub(crate) web_search: bool,
|
||||
pub(crate) js_repl: bool,
|
||||
pub(crate) code_mode: bool,
|
||||
pub(crate) memory: bool,
|
||||
pub(crate) apply_patch: bool,
|
||||
pub(crate) subagents: bool,
|
||||
pub(crate) image_generation: bool,
|
||||
pub(crate) request_permissions: bool,
|
||||
pub(crate) skill_dependencies: bool,
|
||||
pub(crate) shell_snapshot: bool,
|
||||
pub(crate) shell_zsh_fork: bool,
|
||||
pub(crate) unified_exec: bool,
|
||||
pub(crate) request_rule: bool,
|
||||
pub(crate) git_commit: bool,
|
||||
}
|
||||
|
||||
pub(crate) struct SubAgentConfigBuilder {
|
||||
config: Config,
|
||||
}
|
||||
|
||||
impl SubAgentConfigBuilder {
|
||||
pub(crate) fn from_parent_config(parent_config: &Config) -> Self {
|
||||
Self {
|
||||
config: parent_config.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_parent_turn(turn: &TurnContext) -> anyhow::Result<Self> {
|
||||
let base_config = turn.config.clone();
|
||||
let mut builder = Self::from_parent_config(base_config.as_ref());
|
||||
builder.config.model = Some(turn.model_info.slug.clone());
|
||||
builder.config.model_provider = turn.provider.clone();
|
||||
builder.config.model_reasoning_effort = turn.reasoning_effort;
|
||||
builder.config.model_reasoning_summary = Some(turn.reasoning_summary);
|
||||
builder.config.developer_instructions = turn.developer_instructions.clone();
|
||||
builder.config.compact_prompt = turn.compact_prompt.clone();
|
||||
Self::apply_turn_runtime_to_config(&mut builder.config, turn)?;
|
||||
Ok(builder)
|
||||
}
|
||||
|
||||
pub(crate) fn apply_turn_runtime_to_config(
|
||||
config: &mut Config,
|
||||
turn: &TurnContext,
|
||||
) -> anyhow::Result<()> {
|
||||
config
|
||||
.permissions
|
||||
.approval_policy
|
||||
.set(turn.approval_policy.value())
|
||||
.context("approval_policy is invalid")?;
|
||||
config.permissions.shell_environment_policy = turn.shell_environment_policy.clone();
|
||||
config.codex_linux_sandbox_exe = turn.codex_linux_sandbox_exe.clone();
|
||||
config.cwd = turn.cwd.clone();
|
||||
config
|
||||
.permissions
|
||||
.sandbox_policy
|
||||
.set(turn.sandbox_policy.get().clone())
|
||||
.context("sandbox_policy is invalid")?;
|
||||
config.permissions.file_system_sandbox_policy = turn.file_system_sandbox_policy.clone();
|
||||
config.permissions.network_sandbox_policy = turn.network_sandbox_policy;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn prompt_inheritance(mut self, inheritance: SubAgentPromptInheritance) -> Self {
|
||||
match inheritance {
|
||||
SubAgentPromptInheritance::InheritParent => {}
|
||||
SubAgentPromptInheritance::None => {
|
||||
self.config.base_instructions = None;
|
||||
self.config.user_instructions = None;
|
||||
self.config.developer_instructions = None;
|
||||
self.config.compact_prompt = None;
|
||||
self.config.project_doc_max_bytes = 0;
|
||||
self.config.project_doc_fallback_filenames.clear();
|
||||
self.config.include_permissions_instructions = false;
|
||||
self.config.include_apps_instructions = false;
|
||||
self.config.include_environment_context = false;
|
||||
self.config.initial_context_inclusions = InitialContextInclusions::none();
|
||||
}
|
||||
SubAgentPromptInheritance::Select(selection) => {
|
||||
self.apply_prompt_selection(selection);
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn extension_inheritance(
|
||||
mut self,
|
||||
inheritance: SubAgentExtensionInheritance,
|
||||
) -> anyhow::Result<Self> {
|
||||
match inheritance {
|
||||
SubAgentExtensionInheritance::InheritParent => {}
|
||||
SubAgentExtensionInheritance::None => {
|
||||
self.apply_extension_selection(SubAgentExtensionSelection::none())?;
|
||||
}
|
||||
SubAgentExtensionInheritance::Select(selection) => {
|
||||
self.apply_extension_selection(selection)?;
|
||||
}
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub(crate) fn initial_context_inclusions(
|
||||
mut self,
|
||||
inclusions: InitialContextInclusions,
|
||||
) -> Self {
|
||||
self.config.initial_context_inclusions = inclusions;
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn build(self) -> Config {
|
||||
self.config
|
||||
}
|
||||
|
||||
fn apply_prompt_selection(&mut self, selection: SubAgentPromptSelection) {
|
||||
if !selection.base_instructions {
|
||||
self.config.base_instructions = None;
|
||||
}
|
||||
if !selection.user_instructions {
|
||||
self.config.user_instructions = None;
|
||||
}
|
||||
if !selection.developer_instructions {
|
||||
self.config.developer_instructions = None;
|
||||
}
|
||||
if !selection.compact_prompt {
|
||||
self.config.compact_prompt = None;
|
||||
}
|
||||
if !selection.project_docs {
|
||||
self.config.project_doc_max_bytes = 0;
|
||||
self.config.project_doc_fallback_filenames.clear();
|
||||
}
|
||||
|
||||
self.config.include_permissions_instructions = selection.permissions;
|
||||
self.config.include_apps_instructions = selection.apps;
|
||||
self.config.include_environment_context = selection.environment_context;
|
||||
self.config.initial_context_inclusions = InitialContextInclusions {
|
||||
model_update: selection.model_update,
|
||||
permissions: selection.permissions,
|
||||
developer_instructions: selection.developer_instructions,
|
||||
separate_developer_instructions: selection.separate_developer_instructions,
|
||||
memory: selection.memory,
|
||||
collaboration: selection.collaboration,
|
||||
realtime: selection.realtime,
|
||||
personality: selection.personality,
|
||||
apps: selection.apps,
|
||||
skills: selection.skills,
|
||||
plugins: selection.plugins,
|
||||
commit: selection.commit,
|
||||
user_instructions: selection.user_instructions || selection.project_docs,
|
||||
environment_context: selection.environment_context,
|
||||
};
|
||||
}
|
||||
|
||||
fn apply_extension_selection(
|
||||
&mut self,
|
||||
selection: SubAgentExtensionSelection,
|
||||
) -> anyhow::Result<()> {
|
||||
if !selection.mcp_servers {
|
||||
self.config.mcp_servers = Constrained::allow_only(Default::default());
|
||||
}
|
||||
if !selection.apps {
|
||||
self.disable_feature(Feature::Apps)?;
|
||||
}
|
||||
if !selection.plugins {
|
||||
self.disable_feature(Feature::Plugins)?;
|
||||
}
|
||||
if !selection.tool_search {
|
||||
self.disable_feature(Feature::ToolSearch)?;
|
||||
self.disable_feature(Feature::ToolSuggest)?;
|
||||
}
|
||||
if !selection.web_search {
|
||||
self.disable_feature(Feature::WebSearchRequest)?;
|
||||
self.disable_feature(Feature::WebSearchCached)?;
|
||||
}
|
||||
if !selection.js_repl {
|
||||
self.config.js_repl_node_path = None;
|
||||
self.config.js_repl_node_module_dirs = Vec::new();
|
||||
self.disable_feature(Feature::JsReplToolsOnly)?;
|
||||
self.disable_feature(Feature::JsRepl)?;
|
||||
}
|
||||
if !selection.code_mode {
|
||||
self.disable_feature(Feature::CodeModeOnly)?;
|
||||
self.disable_feature(Feature::CodeMode)?;
|
||||
}
|
||||
if !selection.memory {
|
||||
self.disable_feature(Feature::MemoryTool)?;
|
||||
}
|
||||
if !selection.apply_patch {
|
||||
self.config.include_apply_patch_tool = false;
|
||||
self.disable_feature(Feature::ApplyPatchFreeform)?;
|
||||
}
|
||||
if !selection.subagents {
|
||||
self.disable_feature(Feature::SpawnCsv)?;
|
||||
self.disable_feature(Feature::MultiAgentV2)?;
|
||||
self.disable_feature(Feature::Collab)?;
|
||||
self.disable_feature(Feature::ChildAgentsMd)?;
|
||||
}
|
||||
if !selection.image_generation {
|
||||
self.disable_feature(Feature::ImageGeneration)?;
|
||||
}
|
||||
if !selection.request_permissions {
|
||||
self.disable_feature(Feature::RequestPermissionsTool)?;
|
||||
self.disable_feature(Feature::ExecPermissionApprovals)?;
|
||||
}
|
||||
if !selection.skill_dependencies {
|
||||
self.disable_feature(Feature::SkillMcpDependencyInstall)?;
|
||||
self.disable_feature(Feature::SkillEnvVarDependencyPrompt)?;
|
||||
}
|
||||
if !selection.shell_snapshot {
|
||||
self.disable_feature(Feature::ShellSnapshot)?;
|
||||
}
|
||||
if !selection.shell_zsh_fork {
|
||||
self.config.zsh_path = None;
|
||||
self.disable_feature(Feature::ShellZshFork)?;
|
||||
}
|
||||
if !selection.unified_exec {
|
||||
self.config.use_experimental_unified_exec_tool = false;
|
||||
self.config.main_execve_wrapper_exe = None;
|
||||
self.disable_feature(Feature::UnifiedExec)?;
|
||||
}
|
||||
if !selection.request_rule {
|
||||
self.disable_feature(Feature::RequestRule)?;
|
||||
}
|
||||
if !selection.git_commit {
|
||||
self.disable_feature(Feature::CodexGitCommit)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn disable_feature(&mut self, feature: Feature) -> anyhow::Result<()> {
|
||||
self.config.features.disable(feature).map_err(|err| {
|
||||
anyhow::anyhow!(
|
||||
"subagent config could not disable `features.{}`: {err}",
|
||||
feature.key()
|
||||
)
|
||||
})?;
|
||||
if self.config.features.enabled(feature) {
|
||||
anyhow::bail!(
|
||||
"subagent config requires `features.{}` to be disabled",
|
||||
feature.key()
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAgentExtensionSelection {
|
||||
const fn none() -> Self {
|
||||
Self {
|
||||
mcp_servers: false,
|
||||
apps: false,
|
||||
plugins: false,
|
||||
tool_search: false,
|
||||
web_search: false,
|
||||
js_repl: false,
|
||||
code_mode: false,
|
||||
memory: false,
|
||||
apply_patch: false,
|
||||
subagents: false,
|
||||
image_generation: false,
|
||||
request_permissions: false,
|
||||
skill_dependencies: false,
|
||||
shell_snapshot: false,
|
||||
shell_zsh_fork: false,
|
||||
unified_exec: false,
|
||||
request_rule: false,
|
||||
git_commit: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
+82
-79
@@ -3680,31 +3680,26 @@ impl Session {
|
||||
let mut developer_sections = Vec::<String>::with_capacity(8);
|
||||
let mut contextual_user_sections = Vec::<String>::with_capacity(2);
|
||||
let shell = self.user_shell();
|
||||
let (
|
||||
reference_context_item,
|
||||
previous_turn_settings,
|
||||
collaboration_mode,
|
||||
base_instructions,
|
||||
session_source,
|
||||
) = {
|
||||
let (reference_context_item, previous_turn_settings, collaboration_mode, base_instructions) = {
|
||||
let state = self.state.lock().await;
|
||||
(
|
||||
state.reference_context_item(),
|
||||
state.previous_turn_settings(),
|
||||
state.session_configuration.collaboration_mode.clone(),
|
||||
state.session_configuration.base_instructions.clone(),
|
||||
state.session_configuration.session_source.clone(),
|
||||
)
|
||||
};
|
||||
if let Some(model_switch_message) =
|
||||
crate::context_manager::updates::build_model_instructions_update_item(
|
||||
previous_turn_settings.as_ref(),
|
||||
turn_context,
|
||||
)
|
||||
let initial_context = turn_context.config.initial_context_inclusions;
|
||||
if initial_context.model_update
|
||||
&& let Some(model_switch_message) =
|
||||
crate::context_manager::updates::build_model_instructions_update_item(
|
||||
previous_turn_settings.as_ref(),
|
||||
turn_context,
|
||||
)
|
||||
{
|
||||
developer_sections.push(model_switch_message.into_text());
|
||||
}
|
||||
if turn_context.config.include_permissions_instructions {
|
||||
if initial_context.permissions && turn_context.config.include_permissions_instructions {
|
||||
developer_sections.push(
|
||||
DeveloperInstructions::from_policy(
|
||||
turn_context.sandbox_policy.get(),
|
||||
@@ -3722,71 +3717,76 @@ impl Session {
|
||||
.into_text(),
|
||||
);
|
||||
}
|
||||
let separate_guardian_developer_message =
|
||||
crate::guardian::is_guardian_reviewer_source(&session_source);
|
||||
// Keep the guardian policy prompt out of the aggregated developer bundle so it
|
||||
// stays isolated as its own top-level developer message for guardian subagents.
|
||||
if !separate_guardian_developer_message
|
||||
// Some internally spawned subagents keep their role policy separate from the
|
||||
// aggregated developer bundle so their top-level instructions are easy to audit.
|
||||
if initial_context.developer_instructions
|
||||
&& !initial_context.separate_developer_instructions
|
||||
&& let Some(developer_instructions) = turn_context.developer_instructions.as_deref()
|
||||
{
|
||||
developer_sections.push(developer_instructions.to_string());
|
||||
}
|
||||
if !separate_guardian_developer_message {
|
||||
// Add developer instructions for memories.
|
||||
if turn_context.features.enabled(Feature::MemoryTool)
|
||||
&& turn_context.config.memories.use_memories
|
||||
&& let Some(memory_prompt) =
|
||||
build_memory_tool_developer_instructions(&turn_context.config.codex_home).await
|
||||
{
|
||||
developer_sections.push(memory_prompt);
|
||||
}
|
||||
// Add developer instructions from collaboration_mode if they exist and are non-empty
|
||||
if let Some(collab_instructions) =
|
||||
// Add developer instructions for memories.
|
||||
if initial_context.memory
|
||||
&& turn_context.features.enabled(Feature::MemoryTool)
|
||||
&& turn_context.config.memories.use_memories
|
||||
&& let Some(memory_prompt) =
|
||||
build_memory_tool_developer_instructions(&turn_context.config.codex_home).await
|
||||
{
|
||||
developer_sections.push(memory_prompt);
|
||||
}
|
||||
// Add developer instructions from collaboration_mode if they exist and are non-empty
|
||||
if initial_context.collaboration
|
||||
&& let Some(collab_instructions) =
|
||||
DeveloperInstructions::from_collaboration_mode(&collaboration_mode)
|
||||
{
|
||||
developer_sections.push(collab_instructions.into_text());
|
||||
}
|
||||
if let Some(realtime_update) =
|
||||
{
|
||||
developer_sections.push(collab_instructions.into_text());
|
||||
}
|
||||
if initial_context.realtime
|
||||
&& let Some(realtime_update) =
|
||||
crate::context_manager::updates::build_initial_realtime_item(
|
||||
reference_context_item.as_ref(),
|
||||
previous_turn_settings.as_ref(),
|
||||
turn_context,
|
||||
)
|
||||
{
|
||||
developer_sections.push(realtime_update.into_text());
|
||||
}
|
||||
if self.features.enabled(Feature::Personality)
|
||||
&& let Some(personality) = turn_context.personality
|
||||
{
|
||||
let model_info = turn_context.model_info.clone();
|
||||
let has_baked_personality = model_info.supports_personality()
|
||||
&& base_instructions == model_info.get_model_instructions(Some(personality));
|
||||
if !has_baked_personality
|
||||
&& let Some(personality_message) =
|
||||
crate::context_manager::updates::personality_message_for(
|
||||
&model_info,
|
||||
personality,
|
||||
)
|
||||
{
|
||||
developer_sections.push(
|
||||
DeveloperInstructions::personality_spec_message(personality_message)
|
||||
.into_text(),
|
||||
);
|
||||
}
|
||||
}
|
||||
if turn_context.config.include_apps_instructions && turn_context.apps_enabled() {
|
||||
let mcp_connection_manager = self.services.mcp_connection_manager.read().await;
|
||||
let accessible_and_enabled_connectors =
|
||||
connectors::list_accessible_and_enabled_connectors_from_manager(
|
||||
&mcp_connection_manager,
|
||||
&turn_context.config,
|
||||
{
|
||||
developer_sections.push(realtime_update.into_text());
|
||||
}
|
||||
if initial_context.personality
|
||||
&& self.features.enabled(Feature::Personality)
|
||||
&& let Some(personality) = turn_context.personality
|
||||
{
|
||||
let model_info = turn_context.model_info.clone();
|
||||
let has_baked_personality = model_info.supports_personality()
|
||||
&& base_instructions == model_info.get_model_instructions(Some(personality));
|
||||
if !has_baked_personality
|
||||
&& let Some(personality_message) =
|
||||
crate::context_manager::updates::personality_message_for(
|
||||
&model_info,
|
||||
personality,
|
||||
)
|
||||
.await;
|
||||
if let Some(apps_section) = render_apps_section(&accessible_and_enabled_connectors)
|
||||
{
|
||||
developer_sections.push(apps_section);
|
||||
}
|
||||
{
|
||||
developer_sections.push(
|
||||
DeveloperInstructions::personality_spec_message(personality_message)
|
||||
.into_text(),
|
||||
);
|
||||
}
|
||||
}
|
||||
if initial_context.apps
|
||||
&& turn_context.config.include_apps_instructions
|
||||
&& turn_context.apps_enabled()
|
||||
{
|
||||
let mcp_connection_manager = self.services.mcp_connection_manager.read().await;
|
||||
let accessible_and_enabled_connectors =
|
||||
connectors::list_accessible_and_enabled_connectors_from_manager(
|
||||
&mcp_connection_manager,
|
||||
&turn_context.config,
|
||||
)
|
||||
.await;
|
||||
if let Some(apps_section) = render_apps_section(&accessible_and_enabled_connectors) {
|
||||
developer_sections.push(apps_section);
|
||||
}
|
||||
}
|
||||
if initial_context.skills {
|
||||
let implicit_skills = turn_context
|
||||
.turn_skills
|
||||
.outcome
|
||||
@@ -3794,6 +3794,8 @@ impl Session {
|
||||
if let Some(skills_section) = render_skills_section(&implicit_skills) {
|
||||
developer_sections.push(skills_section);
|
||||
}
|
||||
}
|
||||
if initial_context.plugins {
|
||||
let loaded_plugins = self
|
||||
.services
|
||||
.plugins_manager
|
||||
@@ -3803,15 +3805,16 @@ impl Session {
|
||||
{
|
||||
developer_sections.push(plugin_section);
|
||||
}
|
||||
if turn_context.features.enabled(Feature::CodexGitCommit)
|
||||
&& let Some(commit_message_instruction) = commit_message_trailer_instruction(
|
||||
turn_context.config.commit_attribution.as_deref(),
|
||||
)
|
||||
{
|
||||
developer_sections.push(commit_message_instruction);
|
||||
}
|
||||
}
|
||||
if !separate_guardian_developer_message
|
||||
if initial_context.commit
|
||||
&& turn_context.features.enabled(Feature::CodexGitCommit)
|
||||
&& let Some(commit_message_instruction) = commit_message_trailer_instruction(
|
||||
turn_context.config.commit_attribution.as_deref(),
|
||||
)
|
||||
{
|
||||
developer_sections.push(commit_message_instruction);
|
||||
}
|
||||
if initial_context.user_instructions
|
||||
&& let Some(user_instructions) = turn_context.user_instructions.as_deref()
|
||||
{
|
||||
contextual_user_sections.push(
|
||||
@@ -3822,7 +3825,7 @@ impl Session {
|
||||
.serialize_to_text(),
|
||||
);
|
||||
}
|
||||
if turn_context.config.include_environment_context {
|
||||
if initial_context.environment_context && turn_context.config.include_environment_context {
|
||||
let subagents = self
|
||||
.services
|
||||
.agent_control
|
||||
@@ -3846,9 +3849,9 @@ impl Session {
|
||||
{
|
||||
items.push(contextual_user_message);
|
||||
}
|
||||
// Emit the guardian policy prompt as a separate developer item so the guardian
|
||||
// subagent sees a distinct, easy-to-audit instruction block.
|
||||
if separate_guardian_developer_message
|
||||
// Emit selected role policy as a separate developer item.
|
||||
if initial_context.developer_instructions
|
||||
&& initial_context.separate_developer_instructions
|
||||
&& let Some(developer_instructions) = turn_context.developer_instructions.as_deref()
|
||||
&& let Some(guardian_developer_message) =
|
||||
crate::context_manager::updates::build_developer_update_item(vec![
|
||||
|
||||
@@ -247,6 +247,13 @@ async fn process_compacted_history_preserves_separate_guardian_developer_message
|
||||
}
|
||||
turn_context.session_source = guardian_source;
|
||||
turn_context.developer_instructions = Some(guardian_policy.clone());
|
||||
let mut config = (*turn_context.config).clone();
|
||||
config.initial_context_inclusions = crate::config::InitialContextInclusions {
|
||||
developer_instructions: true,
|
||||
separate_developer_instructions: true,
|
||||
..crate::config::InitialContextInclusions::none()
|
||||
};
|
||||
turn_context.config = Arc::new(config);
|
||||
|
||||
let refreshed = crate::compact_remote::process_compacted_history(
|
||||
&session,
|
||||
@@ -290,8 +297,7 @@ async fn process_compacted_history_preserves_separate_guardian_developer_message
|
||||
.iter()
|
||||
.any(|message| message.contains("stale developer message"))
|
||||
);
|
||||
assert!(developer_messages.len() >= 2);
|
||||
assert_eq!(developer_messages.last(), Some(&guardian_policy));
|
||||
assert_eq!(developer_messages, vec![guardian_policy]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -4562,6 +4562,7 @@ fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> {
|
||||
include_permissions_instructions: true,
|
||||
include_apps_instructions: true,
|
||||
include_environment_context: true,
|
||||
initial_context_inclusions: InitialContextInclusions::full(),
|
||||
compact_prompt: None,
|
||||
commit_attribution: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
@@ -4709,6 +4710,7 @@ fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> {
|
||||
include_permissions_instructions: true,
|
||||
include_apps_instructions: true,
|
||||
include_environment_context: true,
|
||||
initial_context_inclusions: InitialContextInclusions::full(),
|
||||
compact_prompt: None,
|
||||
commit_attribution: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
@@ -4854,6 +4856,7 @@ fn test_precedence_fixture_with_zdr_profile() -> std::io::Result<()> {
|
||||
include_permissions_instructions: true,
|
||||
include_apps_instructions: true,
|
||||
include_environment_context: true,
|
||||
initial_context_inclusions: InitialContextInclusions::full(),
|
||||
compact_prompt: None,
|
||||
commit_attribution: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
@@ -4985,6 +4988,7 @@ fn test_precedence_fixture_with_gpt5_profile() -> std::io::Result<()> {
|
||||
include_permissions_instructions: true,
|
||||
include_apps_instructions: true,
|
||||
include_environment_context: true,
|
||||
initial_context_inclusions: InitialContextInclusions::full(),
|
||||
compact_prompt: None,
|
||||
commit_attribution: None,
|
||||
forced_chatgpt_workspace_id: None,
|
||||
|
||||
@@ -269,6 +269,12 @@ pub struct Config {
|
||||
/// Whether to inject the `<environment_context>` user block.
|
||||
pub include_environment_context: bool,
|
||||
|
||||
/// In-memory policy for which startup context blocks are model-visible.
|
||||
///
|
||||
/// This is not loaded from config.toml. It is used by internally spawned
|
||||
/// subagents that need a narrower prompt than the parent session.
|
||||
pub(crate) initial_context_inclusions: InitialContextInclusions,
|
||||
|
||||
/// Compact prompt override.
|
||||
pub compact_prompt: Option<String>,
|
||||
|
||||
@@ -576,6 +582,64 @@ pub struct Config {
|
||||
pub otel: codex_config::types::OtelConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) struct InitialContextInclusions {
|
||||
pub(crate) model_update: bool,
|
||||
pub(crate) permissions: bool,
|
||||
pub(crate) developer_instructions: bool,
|
||||
pub(crate) separate_developer_instructions: bool,
|
||||
pub(crate) memory: bool,
|
||||
pub(crate) collaboration: bool,
|
||||
pub(crate) realtime: bool,
|
||||
pub(crate) personality: bool,
|
||||
pub(crate) apps: bool,
|
||||
pub(crate) skills: bool,
|
||||
pub(crate) plugins: bool,
|
||||
pub(crate) commit: bool,
|
||||
pub(crate) user_instructions: bool,
|
||||
pub(crate) environment_context: bool,
|
||||
}
|
||||
|
||||
impl InitialContextInclusions {
|
||||
pub(crate) const fn full() -> Self {
|
||||
Self {
|
||||
model_update: true,
|
||||
permissions: true,
|
||||
developer_instructions: true,
|
||||
separate_developer_instructions: false,
|
||||
memory: true,
|
||||
collaboration: true,
|
||||
realtime: true,
|
||||
personality: true,
|
||||
apps: true,
|
||||
skills: true,
|
||||
plugins: true,
|
||||
commit: true,
|
||||
user_instructions: true,
|
||||
environment_context: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn none() -> Self {
|
||||
Self {
|
||||
model_update: false,
|
||||
permissions: false,
|
||||
developer_instructions: false,
|
||||
separate_developer_instructions: false,
|
||||
memory: false,
|
||||
collaboration: false,
|
||||
realtime: false,
|
||||
personality: false,
|
||||
apps: false,
|
||||
skills: false,
|
||||
plugins: false,
|
||||
commit: false,
|
||||
user_instructions: false,
|
||||
environment_context: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct MultiAgentV2Config {
|
||||
pub usage_hint_enabled: bool,
|
||||
@@ -2018,6 +2082,7 @@ impl Config {
|
||||
include_permissions_instructions,
|
||||
include_apps_instructions,
|
||||
include_environment_context,
|
||||
initial_context_inclusions: InitialContextInclusions::full(),
|
||||
// The config.toml omits "_mode" because it's a config file. However, "_mode"
|
||||
// is important in code to differentiate the mode from the store implementation.
|
||||
cli_auth_credentials_store_mode: cfg.cli_auth_credentials_store.unwrap_or_default(),
|
||||
|
||||
@@ -24,16 +24,20 @@ use tokio::sync::Mutex;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::warn;
|
||||
|
||||
use crate::agent::SubAgentConfigBuilder;
|
||||
use crate::agent::SubAgentExtensionInheritance;
|
||||
use crate::agent::SubAgentPromptInheritance;
|
||||
use crate::agent::SubAgentPromptSelection;
|
||||
use crate::codex::Codex;
|
||||
use crate::codex::Session;
|
||||
use crate::codex::TurnContext;
|
||||
use crate::codex_delegate::run_codex_thread_interactive;
|
||||
use crate::config::Config;
|
||||
use crate::config::Constrained;
|
||||
use crate::config::InitialContextInclusions;
|
||||
use crate::config::NetworkProxySpec;
|
||||
use crate::config::Permissions;
|
||||
use crate::rollout::recorder::RolloutRecorder;
|
||||
use codex_features::Feature;
|
||||
use codex_model_provider_info::ModelProviderInfo;
|
||||
|
||||
use super::GUARDIAN_REVIEW_TIMEOUT;
|
||||
@@ -47,32 +51,6 @@ const GUARDIAN_BASE_INSTRUCTIONS: &str = concat!(
|
||||
"Follow the guardian developer policy. ",
|
||||
"Return only the required final JSON."
|
||||
);
|
||||
const GUARDIAN_DISABLED_FEATURES: &[Feature] = &[
|
||||
Feature::SpawnCsv,
|
||||
Feature::MultiAgentV2,
|
||||
Feature::Collab,
|
||||
Feature::WebSearchRequest,
|
||||
Feature::WebSearchCached,
|
||||
Feature::CodeModeOnly,
|
||||
Feature::CodeMode,
|
||||
Feature::JsReplToolsOnly,
|
||||
Feature::JsRepl,
|
||||
Feature::MemoryTool,
|
||||
Feature::ChildAgentsMd,
|
||||
Feature::Apps,
|
||||
Feature::ToolSearch,
|
||||
Feature::ToolSuggest,
|
||||
Feature::Plugins,
|
||||
Feature::ImageGeneration,
|
||||
Feature::RequestPermissionsTool,
|
||||
Feature::ExecPermissionApprovals,
|
||||
Feature::RequestRule,
|
||||
Feature::ShellSnapshot,
|
||||
Feature::ShellZshFork,
|
||||
Feature::UnifiedExec,
|
||||
Feature::ApplyPatchFreeform,
|
||||
Feature::CodexGitCommit,
|
||||
];
|
||||
const GUARDIAN_FOLLOWUP_REVIEW_REMINDER: &str = concat!(
|
||||
"Use prior reviews as context, not binding precedent. ",
|
||||
"Follow the Workspace Policy. ",
|
||||
@@ -644,7 +622,44 @@ pub(crate) fn build_guardian_review_session_config(
|
||||
active_model: &str,
|
||||
reasoning_effort: Option<codex_protocol::openai_models::ReasoningEffort>,
|
||||
) -> anyhow::Result<Config> {
|
||||
let mut guardian_config = parent_config.clone();
|
||||
let mut guardian_config = SubAgentConfigBuilder::from_parent_config(parent_config)
|
||||
.prompt_inheritance(SubAgentPromptInheritance::Select(SubAgentPromptSelection {
|
||||
model_update: false,
|
||||
base_instructions: false,
|
||||
user_instructions: false,
|
||||
project_docs: true,
|
||||
developer_instructions: true,
|
||||
separate_developer_instructions: true,
|
||||
compact_prompt: false,
|
||||
permissions: false,
|
||||
memory: false,
|
||||
collaboration: false,
|
||||
realtime: false,
|
||||
personality: false,
|
||||
apps: false,
|
||||
skills: false,
|
||||
plugins: false,
|
||||
commit: false,
|
||||
environment_context: true,
|
||||
}))
|
||||
.extension_inheritance(SubAgentExtensionInheritance::None)?
|
||||
.initial_context_inclusions(InitialContextInclusions {
|
||||
model_update: false,
|
||||
permissions: false,
|
||||
developer_instructions: true,
|
||||
separate_developer_instructions: true,
|
||||
memory: false,
|
||||
collaboration: false,
|
||||
realtime: false,
|
||||
personality: false,
|
||||
apps: false,
|
||||
skills: false,
|
||||
plugins: false,
|
||||
commit: false,
|
||||
user_instructions: true,
|
||||
environment_context: true,
|
||||
})
|
||||
.build();
|
||||
guardian_config.model = Some(active_model.to_string());
|
||||
guardian_config.model_reasoning_effort = reasoning_effort;
|
||||
guardian_config.personality = None;
|
||||
@@ -660,13 +675,7 @@ pub(crate) fn build_guardian_review_session_config(
|
||||
guardian_config.compact_prompt = None;
|
||||
guardian_config.include_permissions_instructions = false;
|
||||
guardian_config.include_apps_instructions = false;
|
||||
guardian_config.include_environment_context = false;
|
||||
guardian_config.project_doc_max_bytes = 0;
|
||||
guardian_config.mcp_servers = Constrained::allow_only(Default::default());
|
||||
guardian_config.js_repl_node_path = None;
|
||||
guardian_config.js_repl_node_module_dirs = Vec::new();
|
||||
guardian_config.zsh_path = None;
|
||||
guardian_config.main_execve_wrapper_exe = None;
|
||||
guardian_config.include_environment_context = true;
|
||||
guardian_config.include_apply_patch_tool = false;
|
||||
guardian_config.use_experimental_unified_exec_tool = false;
|
||||
guardian_config.permissions.allow_login_shell = false;
|
||||
@@ -688,28 +697,9 @@ pub(crate) fn build_guardian_review_session_config(
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
)?);
|
||||
}
|
||||
disable_guardian_feature_set(&mut guardian_config)?;
|
||||
Ok(guardian_config)
|
||||
}
|
||||
|
||||
fn disable_guardian_feature_set(guardian_config: &mut Config) -> anyhow::Result<()> {
|
||||
for &feature in GUARDIAN_DISABLED_FEATURES {
|
||||
guardian_config.features.disable(feature).map_err(|err| {
|
||||
anyhow::anyhow!(
|
||||
"guardian review session could not disable `features.{}`: {err}",
|
||||
feature.key()
|
||||
)
|
||||
})?;
|
||||
if guardian_config.features.enabled(feature) {
|
||||
anyhow::bail!(
|
||||
"guardian review session requires `features.{}` to be disabled",
|
||||
feature.key()
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_before_review_deadline<T>(
|
||||
deadline: tokio::time::Instant,
|
||||
external_cancel: Option<&CancellationToken>,
|
||||
|
||||
+10
-7
File diff suppressed because one or more lines are too long
+4
-2
File diff suppressed because one or more lines are too long
@@ -4,6 +4,7 @@ use crate::codex::TurnContext;
|
||||
use crate::config::Config;
|
||||
use crate::config::ConfigOverrides;
|
||||
use crate::config::Constrained;
|
||||
use crate::config::InitialContextInclusions;
|
||||
use crate::config::ManagedFeatures;
|
||||
use crate::config::NetworkProxySpec;
|
||||
use crate::config::test_config;
|
||||
@@ -560,6 +561,10 @@ async fn guardian_review_request_layout_matches_model_visible_request_snapshot()
|
||||
|
||||
let (mut session, mut turn) = crate::codex::make_session_and_context().await;
|
||||
let temp_cwd = TempDir::new()?;
|
||||
std::fs::write(
|
||||
temp_cwd.path().join("AGENTS.md"),
|
||||
"GUARDIAN_REPO_AGENTS_SHOULD_BE_VISIBLE",
|
||||
)?;
|
||||
let mut config = (*turn.config).clone();
|
||||
config.cwd = temp_cwd.abs();
|
||||
config.model_provider.base_url = Some(format!("{}/v1", server.uri()));
|
||||
@@ -624,14 +629,24 @@ async fn guardian_review_request_layout_matches_model_visible_request_snapshot()
|
||||
"PARENT_BASE_INSTRUCTIONS_SHOULD_NOT_BE_VISIBLE",
|
||||
"PARENT_USER_INSTRUCTIONS_SHOULD_NOT_BE_VISIBLE",
|
||||
"PARENT_DEVELOPER_INSTRUCTIONS_SHOULD_NOT_BE_VISIBLE",
|
||||
"<environment_context>",
|
||||
"<permissions instructions>",
|
||||
"<apps_instructions>",
|
||||
"<skills_instructions>",
|
||||
"<plugins_instructions>",
|
||||
] {
|
||||
assert!(
|
||||
!request_body_text.contains(omitted_parent_text),
|
||||
"guardian review request should omit inherited parent text: {omitted_parent_text}"
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
request_body_text.contains("GUARDIAN_REPO_AGENTS_SHOULD_BE_VISIBLE"),
|
||||
"guardian review request should include repo AGENTS.md project docs"
|
||||
);
|
||||
assert!(
|
||||
request_body_text.contains("<environment_context>"),
|
||||
"guardian review request should include existing environment context"
|
||||
);
|
||||
let mut settings = Settings::clone_current();
|
||||
settings.set_snapshot_path("snapshots");
|
||||
settings.set_prepend_module_to_snapshot(false);
|
||||
@@ -1192,8 +1207,27 @@ fn guardian_review_session_config_strips_parent_prompt_and_tooling_surface() {
|
||||
assert_eq!(guardian_config.compact_prompt, None);
|
||||
assert!(!guardian_config.include_permissions_instructions);
|
||||
assert!(!guardian_config.include_apps_instructions);
|
||||
assert!(!guardian_config.include_environment_context);
|
||||
assert_eq!(guardian_config.project_doc_max_bytes, 0);
|
||||
assert!(guardian_config.include_environment_context);
|
||||
assert_eq!(guardian_config.project_doc_max_bytes, 4096);
|
||||
assert_eq!(
|
||||
guardian_config.initial_context_inclusions,
|
||||
InitialContextInclusions {
|
||||
model_update: false,
|
||||
permissions: false,
|
||||
developer_instructions: true,
|
||||
separate_developer_instructions: true,
|
||||
memory: false,
|
||||
collaboration: false,
|
||||
realtime: false,
|
||||
personality: false,
|
||||
apps: false,
|
||||
skills: false,
|
||||
plugins: false,
|
||||
commit: false,
|
||||
user_instructions: true,
|
||||
environment_context: true,
|
||||
}
|
||||
);
|
||||
assert!(guardian_config.mcp_servers.is_empty());
|
||||
assert_eq!(guardian_config.js_repl_node_path, None);
|
||||
assert_eq!(
|
||||
@@ -1211,7 +1245,9 @@ fn guardian_review_session_config_strips_parent_prompt_and_tooling_surface() {
|
||||
Feature::MemoryTool,
|
||||
Feature::Collab,
|
||||
Feature::SpawnCsv,
|
||||
Feature::ChildAgentsMd,
|
||||
Feature::ApplyPatchFreeform,
|
||||
Feature::CodexGitCommit,
|
||||
] {
|
||||
assert!(!guardian_config.features.enabled(disabled_feature));
|
||||
}
|
||||
@@ -1283,9 +1319,10 @@ fn guardian_review_session_config_rejects_pinned_collab_feature() {
|
||||
)
|
||||
.expect_err("guardian config should fail when collab is pinned on");
|
||||
|
||||
let err = err.to_string();
|
||||
assert!(
|
||||
err.to_string()
|
||||
.contains("guardian review session requires `features.multi_agent` to be disabled")
|
||||
err.contains("`features.multi_agent`"),
|
||||
"error should name the pinned feature: {err}"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use crate::agent::AgentStatus;
|
||||
use crate::agent::SubAgentConfigBuilder;
|
||||
use crate::agent::SubAgentExtensionInheritance;
|
||||
use crate::agent::SubAgentPromptInheritance;
|
||||
use crate::codex::Session;
|
||||
use crate::codex::TurnContext;
|
||||
use crate::config::Config;
|
||||
@@ -221,17 +224,16 @@ pub(crate) fn build_agent_resume_config(
|
||||
}
|
||||
|
||||
fn build_agent_shared_config(turn: &TurnContext) -> Result<Config, FunctionCallError> {
|
||||
let base_config = turn.config.clone();
|
||||
let mut config = (*base_config).clone();
|
||||
config.model = Some(turn.model_info.slug.clone());
|
||||
config.model_provider = turn.provider.clone();
|
||||
config.model_reasoning_effort = turn.reasoning_effort;
|
||||
config.model_reasoning_summary = Some(turn.reasoning_summary);
|
||||
config.developer_instructions = turn.developer_instructions.clone();
|
||||
config.compact_prompt = turn.compact_prompt.clone();
|
||||
apply_spawn_agent_runtime_overrides(&mut config, turn)?;
|
||||
|
||||
Ok(config)
|
||||
let builder = SubAgentConfigBuilder::from_parent_turn(turn).map_err(|err| {
|
||||
FunctionCallError::RespondToModel(format!("spawn config is invalid: {err}"))
|
||||
})?;
|
||||
let builder = builder
|
||||
.prompt_inheritance(SubAgentPromptInheritance::InheritParent)
|
||||
.extension_inheritance(SubAgentExtensionInheritance::InheritParent)
|
||||
.map_err(|err| {
|
||||
FunctionCallError::RespondToModel(format!("spawn config is invalid: {err}"))
|
||||
})?;
|
||||
Ok(builder.build())
|
||||
}
|
||||
|
||||
/// Copies runtime-only turn state onto a child config before it is handed to `AgentControl`.
|
||||
@@ -242,26 +244,8 @@ pub(crate) fn apply_spawn_agent_runtime_overrides(
|
||||
config: &mut Config,
|
||||
turn: &TurnContext,
|
||||
) -> Result<(), FunctionCallError> {
|
||||
config
|
||||
.permissions
|
||||
.approval_policy
|
||||
.set(turn.approval_policy.value())
|
||||
.map_err(|err| {
|
||||
FunctionCallError::RespondToModel(format!("approval_policy is invalid: {err}"))
|
||||
})?;
|
||||
config.permissions.shell_environment_policy = turn.shell_environment_policy.clone();
|
||||
config.codex_linux_sandbox_exe = turn.codex_linux_sandbox_exe.clone();
|
||||
config.cwd = turn.cwd.clone();
|
||||
config
|
||||
.permissions
|
||||
.sandbox_policy
|
||||
.set(turn.sandbox_policy.get().clone())
|
||||
.map_err(|err| {
|
||||
FunctionCallError::RespondToModel(format!("sandbox_policy is invalid: {err}"))
|
||||
})?;
|
||||
config.permissions.file_system_sandbox_policy = turn.file_system_sandbox_policy.clone();
|
||||
config.permissions.network_sandbox_policy = turn.network_sandbox_policy;
|
||||
Ok(())
|
||||
SubAgentConfigBuilder::apply_turn_runtime_to_config(config, turn)
|
||||
.map_err(|err| FunctionCallError::RespondToModel(format!("spawn config is invalid: {err}")))
|
||||
}
|
||||
|
||||
pub(crate) fn apply_spawn_agent_overrides(config: &mut Config, child_depth: i32) {
|
||||
|
||||
Reference in New Issue
Block a user