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:
brianz-openai authored and copyberry committed 2026-08-27 04:58:21 +00:00
1 parent 72c96598c6
commit f1433fc71f
24 files changed
+565 -9

No files matched your search

+22 -2
View File
@@ -1461,6 +1461,8 @@ async fn spawn_agent_numeric_fork_from_compacted_paginated_parent_clamps_to_prov
#[tokio::test]
async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
let managed_fragment = "<managed_developer_instructions>\nParent developer instructions.\n</managed_developer_instructions>";
let persistent_fragment =
"<persistent_mode>\nParent developer instructions.\n</persistent_mode>";
let harness = AgentControlHarness::new().await;
let mut parent_config = harness.config.clone();
let _ = parent_config.features.enable(Feature::MultiAgentV2);
@@ -1558,6 +1560,9 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
ContentItem::InputText {
text: managed_fragment.to_string(),
},
ContentItem::InputText {
text: persistent_fragment.to_string(),
},
],
phase: None,
internal_chat_message_metadata_passthrough: Some(
@@ -1567,6 +1572,7 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
ContentItemKind("multi_agent.mode_instructions".to_string()),
ContentItemKind("generic.developer_policy".to_string()),
ContentItemKind("managed_config.developer_instructions".to_string()),
ContentItemKind("persistent_mode.instructions".to_string()),
]),
..Default::default()
},
@@ -1677,6 +1683,9 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
ContentItem::InputText {
text: managed_fragment.to_string(),
},
ContentItem::InputText {
text: persistent_fragment.to_string(),
},
],
phase: None,
internal_chat_message_metadata_passthrough: Some(
@@ -1685,6 +1694,7 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
ContentItemKind("generic.developer_instructions".to_string()),
ContentItemKind("generic.developer_policy".to_string()),
ContentItemKind("managed_config.developer_instructions".to_string()),
ContentItemKind("persistent_mode.instructions".to_string()),
]),
..Default::default()
},
@@ -1769,8 +1779,9 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
"empty child developer instructions should remove parent developer instructions"
);
assert!(
history_contains_text(no_hint_history.raw_items(), managed_fragment),
"clearing child instructions must preserve an overlapping managed policy"
history_contains_text(no_hint_history.raw_items(), managed_fragment)
&& history_contains_text(no_hint_history.raw_items(), persistent_fragment),
"clearing child instructions must preserve overlapping managed and persistent instructions"
);
assert!(
history_contains_text(
@@ -2660,6 +2671,8 @@ async fn spawn_agent_fork_last_n_turns_drops_parent_startup_prefix_when_under_li
#[tokio::test]
async fn spawn_agent_fork_last_n_turns_strips_parent_usage_hints() {
let persistent_fragment =
"<persistent_mode>\nParent persistent instructions.\n</persistent_mode>";
let harness = AgentControlHarness::new().await;
let mut parent_config = harness.config.clone();
let _ = parent_config.features.enable(Feature::MultiAgentV2);
@@ -2710,6 +2723,9 @@ async fn spawn_agent_fork_last_n_turns_strips_parent_usage_hints() {
ContentItem::InputText {
text: "Preserved bounded developer context.".to_string(),
},
ContentItem::InputText {
text: persistent_fragment.to_string(),
},
],
phase: None,
internal_chat_message_metadata_passthrough: None,
@@ -2772,6 +2788,10 @@ async fn spawn_agent_fork_last_n_turns_strips_parent_usage_hints() {
!history_contains_text(history.raw_items(), "Child developer instructions."),
"bounded fork should not inject child instructions before its canonical context rebuild"
);
assert!(
!history_contains_text(history.raw_items(), persistent_fragment),
"bounded fork should remove persistent instructions before rebuilding context for the child's effort"
);
assert!(
history_contains_text(history.raw_items(), "Preserved bounded developer context."),
"bounded fork should preserve unrelated developer fragments"