Make the update_plan tool opt-in (#41744)

## What changed

- Default `tools.update_plan.enabled` to `false`; users can explicitly enable it to expose `update_plan`.
- Remove bundled `update_plan` guidance from model, collaboration-mode, multi-agent, compaction, prewarm, and goal-continuation prompts when the tool is disabled.
- Preserve custom base instructions, model catalog instructions, collaboration policies, and user goal text even when they mention planning or `update_plan`.

## Testing

- Cover default and explicitly enabled tool registration, prompt consistency across request paths, and preservation of custom instructions.

GitOrigin-RevId: a53964e6e72f98e2557dd3090fdb7caccb956527
This commit is contained in:
rka-oai authored and copyberry committed 2026-08-31 00:53:30 +00:00
1 parent b7cd519c76
commit a9519cbcdd
31 files changed
+500 -71

No files matched your search

+20
View File
@@ -1314,6 +1314,26 @@ impl Session {
}
}
/// Render the request copy without changing instructions persisted or inherited by forks.
pub(crate) async fn get_prompt_base_instructions(&self) -> BaseInstructions {
let config = self.get_config().await;
let instructions = self.get_base_instructions().await;
if !config.update_plan_enabled
&& config.model_catalog.is_none()
&& matches!(
instructions.provenance,
Some(BaseInstructionsProvenance::Model { .. })
)
{
BaseInstructions {
text: crate::context::without_update_plan_instructions(&instructions.text),
..instructions
}
} else {
instructions
}
}
// Merges connector IDs into the session-level explicit connector selection.
#[tracing::instrument(
level = "trace",
+11 -1
View File
@@ -78,7 +78,11 @@ pub(super) fn usage_hint_text(
.as_ref()
.and_then(|messages| messages.multi_agent.as_ref())
.and_then(|messages| messages.role.as_ref());
let snapshot = resolve_usage_hints(&turn_context.config.multi_agent_v2, catalog);
let snapshot = resolve_usage_hints(
&turn_context.config.multi_agent_v2,
catalog,
!turn_context.config.update_plan_enabled && turn_context.config.model_catalog.is_none(),
);
match session_source {
SessionSource::SubAgent(SubAgentSource::ThreadSpawn { .. }) => snapshot.subagent,
SessionSource::Cli
@@ -94,6 +98,7 @@ pub(super) fn usage_hint_text(
pub(crate) fn resolve_usage_hints(
config: &MultiAgentV2Config,
catalog: Option<&MultiAgentRoleMessages>,
omit_update_plan_instructions: bool,
) -> ResolvedMultiAgentV2UsageHints {
let resolve_role = |configured: Option<&str>, catalog: Option<&str>, bundled: &str| {
// Configured roles take precedence; empty configured or catalog roles suppress fallback.
@@ -106,6 +111,11 @@ pub(crate) fn resolve_usage_hints(
if base.is_empty() {
return None;
}
let base = if omit_update_plan_instructions {
crate::context::without_update_plan_instructions(base)
} else {
base.to_string()
};
let max_concurrency = config.max_concurrent_threads_per_session;
let wait_agent_guidance = if config.wait_agent_enabled {
+1 -1
View File
@@ -1579,7 +1579,7 @@ impl Session {
)
.await?;
sess.start_mcp_prewarm_worker(mcp_prewarm_rx, mcp_auth_changes);
sess.schedule_startup_prewarm(session_configuration.base_instructions.clone())
sess.schedule_startup_prewarm(sess.get_prompt_base_instructions().await.text)
.await;
let session_start_source = match &initial_history {
InitialHistory::Resumed(_) => codex_hooks::SessionStartSource::Resume,
+1 -1
View File
@@ -1370,7 +1370,7 @@ async fn run_sampling_request(
cancellation_token: CancellationToken,
) -> CodexResult<(SamplingRequestResult, Vec<ResponseItem>)> {
let turn_context = Arc::clone(&step_context.turn);
let base_instructions = sess.get_base_instructions().await;
let base_instructions = sess.get_prompt_base_instructions().await;
let tool_runtime = ToolCallRuntime::new(
Arc::clone(&sess),
+14 -1
View File
@@ -44,9 +44,20 @@ impl Session {
let model_instructions = turn_context
.model_info()
.get_model_instructions(turn_context.personality());
let model_instructions = if !turn_context.config.update_plan_enabled
&& turn_context.config.model_catalog.is_none()
&& (turn_context.config.base_instructions.is_none()
|| matches!(
turn_context.config.base_instructions_provenance,
Some(BaseInstructionsProvenance::Model { .. })
)) {
crate::context::without_update_plan_instructions(&model_instructions)
} else {
model_instructions
};
let base_instructions = self.get_prompt_base_instructions().await.text;
let (previous_model, previous_context, base_instructions) = {
let state = self.state.lock().await;
let base_instructions = state.session_configuration.base_instructions.clone();
(
state
.previous_turn_settings()
@@ -189,6 +200,8 @@ impl Session {
.model_messages
.as_ref()
.and_then(|messages| messages.collaboration_modes.as_ref()),
turn_context.config.update_plan_enabled,
turn_context.config.model_catalog.is_some(),
));
}
if !crate::guardian::is_basic_session_source(&turn_context.session_source) {