Honor personality = "none" in model instructions (#32277)

## Why

Model catalog instructions can include a baked-in `# Personality` section. An
explicit `none` setting should omit that section instead of sending it as part
of the model's base instructions.

## What changed

- Pass the configured personality into the models manager.
- When personality support is enabled and the setting is explicitly `none`,
  remove the `# Personality` section through the next level-one heading from
  catalog base instructions and instruction templates.
- Preserve explicit `base_instructions` overrides and avoid warning when no
  personality was requested.

## Testing

Added unit and integration coverage for section removal, heading boundaries,
CRLF input, preserved configurations, and explicit base instructions.

GitOrigin-RevId: 452c88d3ac6001c2ac7d4fef269cd75dc239fa61
This commit is contained in:
rhan-oai authored and copyberry committed 2026-07-10 21:39:56 +00:00
1 parent e7d0e14172
commit 09ccae2c07
6 files changed
+220 -9

No files matched your search

+53 -2
View File
@@ -1,3 +1,4 @@
use codex_protocol::config_types::Personality;
use codex_protocol::config_types::ReasoningSummary;
use codex_protocol::openai_models::ConfigShellToolType;
use codex_protocol::openai_models::ModelInfo;
@@ -19,6 +20,7 @@ const LOCAL_FRIENDLY_TEMPLATE: &str =
"You optimize for team morale and being a supportive teammate as much as code quality.";
const LOCAL_PRAGMATIC_TEMPLATE: &str = "You are a deeply pragmatic, effective software engineer.";
const PERSONALITY_PLACEHOLDER: &str = "{{ personality }}";
const PERSONALITY_SECTION_HEADER: &str = "# Personality";
pub fn with_config_overrides(mut model: ModelInfo, config: &ModelsManagerConfig) -> ModelInfo {
if let Some(context_window) = config.model_context_window {
@@ -50,13 +52,62 @@ pub fn with_config_overrides(mut model: ModelInfo, config: &ModelsManagerConfig)
if let Some(base_instructions) = &config.base_instructions {
model.base_instructions = base_instructions.clone();
clear_instruction_messages(&mut model);
} else if !config.personality_enabled {
clear_instruction_messages(&mut model);
} else {
if config.personality_enabled && config.personality == Some(Personality::None) {
model.base_instructions = strip_personality_section(model.base_instructions);
if let Some(instructions_template) = model
.model_messages
.as_mut()
.and_then(|messages| messages.instructions_template.as_mut())
{
*instructions_template =
strip_personality_section(std::mem::take(instructions_template));
}
}
if !config.personality_enabled {
clear_instruction_messages(&mut model);
}
}
model
}
fn strip_personality_section(mut instructions: String) -> String {
let mut section_start = None;
let mut section_end = None;
let mut offset = 0;
for line_with_ending in instructions.split_inclusive('\n') {
let line = match line_with_ending.strip_suffix('\n') {
Some(line) => line.strip_suffix('\r').unwrap_or(line),
None => line_with_ending,
};
if section_start.is_some() {
if is_h1_heading(line) {
section_end = Some(offset);
break;
}
} else if line == PERSONALITY_SECTION_HEADER {
section_start = Some(offset);
}
offset += line_with_ending.len();
}
if let Some(section_start) = section_start {
let section_end = section_end.unwrap_or(instructions.len());
instructions.replace_range(section_start..section_end, "");
}
instructions
}
fn is_h1_heading(line: &str) -> bool {
let Some(rest) = line.strip_prefix('#') else {
return false;
};
rest.is_empty() || rest.starts_with(' ') || rest.starts_with('\t')
}
fn clear_instruction_messages(model: &mut ModelInfo) {
if let Some(model_messages) = &mut model.model_messages {
model_messages.instructions_template = None;