mirror of
https://github.com/openai/codex.git
synced 2026-11-13 06:45:07 +00:00
Add developer instructions for persistent mode (#41050)
## What changed - Add bundled proactivity and follow-up guidance when `ReasoningEffort::Persistent` is selected. - Allow model metadata to override or disable the guidance with `persistent_instructions`. - Track the instructions as world state so effort and model changes replace or retire stale guidance without duplicating unchanged context. - Tailor approval guidance to `send_user_message_async` availability, rebuild it for forked agents and rolled-back history, and exclude Guardian sessions. ## Testing - Cover default, overridden, disabled, replaced, and retired instructions. - Cover model changes, history transitions, agent forks, Guardian sessions, and asynchronous-message tool availability. GitOrigin-RevId: 78e05d0335a70b385bd069b45edcd4ba570cef91
This commit is contained in:
1 parent
72c96598c6
commit
f1433fc71f
24 files changed
+565
-9
No files matched your search
@@ -532,6 +532,10 @@ impl ModelInfo {
|
||||
/// When variables are present but incomplete, missing values render as empty strings.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, TS, JsonSchema)]
|
||||
pub struct ModelMessages {
|
||||
/// Additional developer instructions for persistent mode. Missing or null uses the built-in
|
||||
/// instructions; an empty string disables them.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub persistent_instructions: Option<String>,
|
||||
pub instructions_template: Option<String>,
|
||||
pub instructions_variables: Option<ModelInstructionsVariables>,
|
||||
pub approvals: Option<ApprovalMessages>,
|
||||
@@ -761,6 +765,7 @@ where
|
||||
.is_none()
|
||||
{
|
||||
let messages = model.model_messages.get_or_insert(ModelMessages {
|
||||
persistent_instructions: None,
|
||||
instructions_template: None,
|
||||
instructions_variables: None,
|
||||
approvals: None,
|
||||
@@ -959,13 +964,15 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn model_messages_deserialize_without_optional_sections() {
|
||||
let messages: ModelMessages =
|
||||
from_str(r#"{"instructions_template":null,"instructions_variables":null}"#)
|
||||
.expect("model messages should deserialize");
|
||||
let messages: ModelMessages = from_str(
|
||||
r#"{"instructions_template":null,"instructions_variables":null,"persistent_instructions":null}"#,
|
||||
)
|
||||
.expect("model messages should deserialize");
|
||||
|
||||
assert_eq!(
|
||||
messages,
|
||||
ModelMessages {
|
||||
persistent_instructions: None,
|
||||
instructions_template: None,
|
||||
instructions_variables: None,
|
||||
approvals: None,
|
||||
@@ -1111,6 +1118,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
messages,
|
||||
ModelMessages {
|
||||
persistent_instructions: None,
|
||||
instructions_template: None,
|
||||
instructions_variables: None,
|
||||
approvals: None,
|
||||
@@ -1202,6 +1210,7 @@ mod tests {
|
||||
#[test]
|
||||
fn get_model_instructions_uses_template_when_placeholder_present() {
|
||||
let model = test_model(Some(ModelMessages {
|
||||
persistent_instructions: None,
|
||||
instructions_template: Some("Hello {{ personality }}".to_string()),
|
||||
instructions_variables: Some(personality_variables()),
|
||||
approvals: None,
|
||||
@@ -1221,6 +1230,7 @@ mod tests {
|
||||
#[test]
|
||||
fn get_model_instructions_strips_placeholder_with_incomplete_variables() {
|
||||
let model = test_model(Some(ModelMessages {
|
||||
persistent_instructions: None,
|
||||
instructions_template: Some("Hello\n{{ personality }}".to_string()),
|
||||
instructions_variables: Some(ModelInstructionsVariables {
|
||||
personality_default: None,
|
||||
@@ -1245,6 +1255,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let model_no_personality = test_model(Some(ModelMessages {
|
||||
persistent_instructions: None,
|
||||
instructions_template: Some("Hello\n{{ personality }}".to_string()),
|
||||
instructions_variables: Some(ModelInstructionsVariables {
|
||||
personality_default: None,
|
||||
@@ -1280,6 +1291,7 @@ mod tests {
|
||||
#[test]
|
||||
fn get_model_instructions_is_empty_when_template_is_missing() {
|
||||
let model = test_model(Some(ModelMessages {
|
||||
persistent_instructions: None,
|
||||
instructions_template: None,
|
||||
instructions_variables: Some(ModelInstructionsVariables {
|
||||
personality_default: None,
|
||||
@@ -1325,6 +1337,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
model.model_messages,
|
||||
Some(ModelMessages {
|
||||
persistent_instructions: None,
|
||||
instructions_template: Some("legacy instructions".to_string()),
|
||||
instructions_variables: None,
|
||||
approvals: None,
|
||||
@@ -1373,6 +1386,7 @@ mod tests {
|
||||
fn models_response_serializes_rendered_legacy_base_instructions() {
|
||||
let response = ModelsResponse {
|
||||
models: vec![test_model(Some(ModelMessages {
|
||||
persistent_instructions: None,
|
||||
instructions_template: Some("before {{ personality }} after".to_string()),
|
||||
instructions_variables: Some(ModelInstructionsVariables {
|
||||
personality_default: Some("default".to_string()),
|
||||
@@ -1400,6 +1414,7 @@ mod tests {
|
||||
#[test]
|
||||
fn models_response_prefers_template_and_preserves_message_siblings() {
|
||||
let messages = ModelMessages {
|
||||
persistent_instructions: Some("Persistent catalog instructions".to_string()),
|
||||
instructions_template: None,
|
||||
instructions_variables: None,
|
||||
approvals: Some(ApprovalMessages {
|
||||
@@ -1450,6 +1465,7 @@ mod tests {
|
||||
assert_eq!(response.models[0].model_messages, Some(expected_messages));
|
||||
|
||||
let canonical_messages = ModelMessages {
|
||||
persistent_instructions: Some(String::new()),
|
||||
instructions_template: Some("canonical instructions".to_string()),
|
||||
instructions_variables: None,
|
||||
approvals: None,
|
||||
|
||||
Reference in new issue
Block a user