feat(config) Plan mode instructions

This commit is contained in:
Dylan Hurd
2026-03-06 23:27:07 -08:00
parent 4b4f61d379
commit 0f47454974
14 changed files with 60 additions and 6 deletions
@@ -193,6 +193,7 @@ impl MessageProcessor {
.features
.enabled(codex_core::features::Feature::DefaultModeRequestUserInput),
},
config.plan_mode_developer_instructions.clone(),
));
thread_manager
.plugins_manager()
+6
View File
@@ -521,6 +521,9 @@
"personality": {
"$ref": "#/definitions/Personality"
},
"plan_mode_developer_instructions": {
"type": "string"
},
"plan_mode_reasoning_effort": {
"$ref": "#/definitions/ReasoningEffort"
},
@@ -2158,6 +2161,9 @@
],
"description": "Optionally specify a personality for the model"
},
"plan_mode_developer_instructions": {
"type": "string"
},
"plan_mode_reasoning_effort": {
"$ref": "#/definitions/ReasoningEffort"
},
+8
View File
@@ -433,6 +433,10 @@ pub struct Config {
/// Plan preset. The `none` value means "no reasoning" (not "inherit the
/// global default").
pub plan_mode_reasoning_effort: Option<ReasoningEffort>,
/// Optional Plan-mode-specific developer instructions override.
///
/// When unset, Plan mode uses the built-in Plan preset instructions.
pub plan_mode_developer_instructions: Option<String>,
/// Optional value to use for `reasoning.summary` when making a request
/// using the Responses API. When unset, the model catalog default is used.
@@ -1201,6 +1205,7 @@ pub struct ConfigToml {
pub model_reasoning_effort: Option<ReasoningEffort>,
pub plan_mode_reasoning_effort: Option<ReasoningEffort>,
pub plan_mode_developer_instructions: Option<String>,
pub model_reasoning_summary: Option<ReasoningSummary>,
/// Optional verbosity control for GPT-5 models (Responses API `text.verbosity`).
pub model_verbosity: Option<Verbosity>,
@@ -2422,6 +2427,9 @@ impl Config {
plan_mode_reasoning_effort: config_profile
.plan_mode_reasoning_effort
.or(cfg.plan_mode_reasoning_effort),
plan_mode_developer_instructions: config_profile
.plan_mode_developer_instructions
.or(cfg.plan_mode_developer_instructions),
model_reasoning_summary: config_profile
.model_reasoning_summary
.or(cfg.model_reasoning_summary),
+1
View File
@@ -28,6 +28,7 @@ pub struct ConfigProfile {
pub sandbox_mode: Option<SandboxMode>,
pub model_reasoning_effort: Option<ReasoningEffort>,
pub plan_mode_reasoning_effort: Option<ReasoningEffort>,
pub plan_mode_developer_instructions: Option<String>,
pub model_reasoning_summary: Option<ReasoningSummary>,
pub model_verbosity: Option<Verbosity>,
/// Optional path to a JSON model catalog (applied on startup only).
@@ -23,17 +23,25 @@ pub struct CollaborationModesConfig {
pub(crate) fn builtin_collaboration_mode_presets(
collaboration_modes_config: CollaborationModesConfig,
plan_mode_developer_instructions: Option<&str>,
) -> Vec<CollaborationModeMask> {
vec![plan_preset(), default_preset(collaboration_modes_config)]
vec![
plan_preset(plan_mode_developer_instructions),
default_preset(collaboration_modes_config),
]
}
fn plan_preset() -> CollaborationModeMask {
fn plan_preset(plan_mode_developer_instructions: Option<&str>) -> CollaborationModeMask {
CollaborationModeMask {
name: ModeKind::Plan.display_name().to_string(),
mode: Some(ModeKind::Plan),
model: None,
reasoning_effort: Some(Some(ReasoningEffort::Medium)),
developer_instructions: Some(Some(COLLABORATION_MODE_PLAN.to_string())),
developer_instructions: Some(Some(
plan_mode_developer_instructions
.unwrap_or(COLLABORATION_MODE_PLAN)
.to_string(),
)),
}
}
@@ -109,17 +117,27 @@ mod tests {
#[test]
fn preset_names_use_mode_display_names() {
assert_eq!(plan_preset().name, ModeKind::Plan.display_name());
assert_eq!(plan_preset(None).name, ModeKind::Plan.display_name());
assert_eq!(
default_preset(CollaborationModesConfig::default()).name,
ModeKind::Default.display_name()
);
assert_eq!(
plan_preset().reasoning_effort,
plan_preset(None).reasoning_effort,
Some(Some(ReasoningEffort::Medium))
);
}
#[test]
fn plan_preset_uses_configured_developer_instructions_override() {
let override_instructions = "Use plan override.";
let plan = plan_preset(Some(override_instructions));
assert_eq!(
plan.developer_instructions,
Some(Some(override_instructions.to_string()))
);
}
#[test]
fn default_mode_instructions_replace_mode_names_placeholder() {
let default_instructions = default_preset(CollaborationModesConfig {
+10 -1
View File
@@ -61,6 +61,7 @@ pub struct ModelsManager {
etag: RwLock<Option<String>>,
cache_manager: ModelsCacheManager,
provider: ModelProviderInfo,
plan_mode_developer_instructions: Option<String>,
}
impl ModelsManager {
@@ -74,6 +75,7 @@ impl ModelsManager {
auth_manager: Arc<AuthManager>,
model_catalog: Option<ModelsResponse>,
collaboration_modes_config: CollaborationModesConfig,
plan_mode_developer_instructions: Option<String>,
) -> Self {
let cache_path = codex_home.join(MODEL_CACHE_FILE);
let cache_manager = ModelsCacheManager::new(cache_path, DEFAULT_MODEL_CACHE_TTL);
@@ -96,6 +98,7 @@ impl ModelsManager {
etag: RwLock::new(None),
cache_manager,
provider: ModelProviderInfo::create_openai_provider(),
plan_mode_developer_instructions,
}
}
@@ -121,7 +124,10 @@ impl ModelsManager {
&self,
collaboration_modes_config: CollaborationModesConfig,
) -> Vec<CollaborationModeMask> {
builtin_collaboration_mode_presets(collaboration_modes_config)
builtin_collaboration_mode_presets(
collaboration_modes_config,
self.plan_mode_developer_instructions.as_deref(),
)
}
/// Attempt to list models without blocking, using the current cached state.
@@ -394,6 +400,7 @@ impl ModelsManager {
etag: RwLock::new(None),
cache_manager,
provider,
plan_mode_developer_instructions: None,
}
}
@@ -522,6 +529,7 @@ mod tests {
auth_manager,
None,
CollaborationModesConfig::default(),
None,
);
let known_slug = manager
.get_remote_models()
@@ -562,6 +570,7 @@ mod tests {
models: vec![overlay],
}),
CollaborationModesConfig::default(),
None,
);
let model_info = manager
+1
View File
@@ -87,5 +87,6 @@ pub fn all_model_presets() -> &'static Vec<ModelPreset> {
pub fn builtin_collaboration_mode_presets() -> Vec<CollaborationModeMask> {
collaboration_mode_presets::builtin_collaboration_mode_presets(
collaboration_mode_presets::CollaborationModesConfig::default(),
None,
)
}
+2
View File
@@ -150,6 +150,7 @@ impl ThreadManager {
session_source: SessionSource,
model_catalog: Option<ModelsResponse>,
collaboration_modes_config: CollaborationModesConfig,
plan_mode_developer_instructions: Option<String>,
) -> Self {
let (thread_created_tx, _) = broadcast::channel(THREAD_CREATED_CHANNEL_CAPACITY);
let plugins_manager = Arc::new(PluginsManager::new(codex_home.clone()));
@@ -168,6 +169,7 @@ impl ThreadManager {
auth_manager.clone(),
model_catalog,
collaboration_modes_config,
plan_mode_developer_instructions,
)),
skills_manager,
plugins_manager,
+1
View File
@@ -824,6 +824,7 @@ async fn prefers_apikey_when_config_prefers_apikey_even_with_chatgpt_tokens() {
.features
.enabled(Feature::DefaultModeRequestUserInput),
},
config.plan_mode_developer_instructions.clone(),
);
let NewThread { thread: codex, .. } = thread_manager
.start_thread(config)
@@ -18,6 +18,7 @@ async fn offline_model_info_without_tool_output_override() {
auth_manager,
None,
CollaborationModesConfig::default(),
None,
);
let model_info = manager.get_model_info("gpt-5.1", &config).await;
@@ -41,6 +42,7 @@ async fn offline_model_info_with_tool_output_override() {
auth_manager,
None,
CollaborationModesConfig::default(),
None,
);
let model_info = manager.get_model_info("gpt-5.1-codex", &config).await;
+1
View File
@@ -484,6 +484,7 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> {
.features
.enabled(codex_core::features::Feature::DefaultModeRequestUserInput),
},
config.plan_mode_developer_instructions.clone(),
));
let default_model = thread_manager
.get_models_manager()
@@ -68,6 +68,7 @@ impl MessageProcessor {
.features
.enabled(codex_core::features::Feature::DefaultModeRequestUserInput),
},
config.plan_mode_developer_instructions.clone(),
));
Self {
outgoing,
+1
View File
@@ -1585,6 +1585,7 @@ impl App {
.features
.enabled(codex_core::features::Feature::DefaultModeRequestUserInput),
},
config.plan_mode_developer_instructions.clone(),
));
let mut model = thread_manager
.get_models_manager()
+2
View File
@@ -1754,6 +1754,7 @@ async fn make_chatwidget_manual(
auth_manager.clone(),
None,
CollaborationModesConfig::default(),
None,
));
let reasoning_effort = None;
let base_mode = CollaborationMode {
@@ -1884,6 +1885,7 @@ fn set_chatgpt_auth(chat: &mut ChatWidget) {
chat.auth_manager.clone(),
None,
CollaborationModesConfig::default(),
None,
));
}