diff --git a/codex-rs/core/src/compact_tests.rs b/codex-rs/core/src/compact_tests.rs
index cc80b0479b..790f0d7362 100644
--- a/codex-rs/core/src/compact_tests.rs
+++ b/codex-rs/core/src/compact_tests.rs
@@ -271,6 +271,55 @@ fn compacted_user_message<'a>(text: &str, original: &'a ResponseItem) -> Compact
}
}
+#[tokio::test]
+async fn extension_window_prefix_precedes_retained_turns_and_is_not_repeated() {
+ use codex_extension_api::PreviousWorldStateSection;
+ use codex_extension_api::RenderedWorldStateFragment;
+ use codex_extension_api::WorldStateSectionContribution;
+
+ let (session, turn) = crate::session::tests::make_session_and_context().await;
+ let step = StepContext::for_test(Arc::new(turn));
+ let mut world = WorldState::default();
+ world.add_extension_section(
+ WorldStateSectionContribution::new("example", |previous| {
+ let fragment = (!matches!(previous, PreviousWorldStateSection::Known(_))).then(|| {
+ RenderedWorldStateFragment::new(
+ "developer",
+ ("", ""),
+ codex_protocol::models::input_text("active policy"),
+ )
+ });
+ (Some(json!("active policy")), fragment)
+ })
+ .with_window_prefix()
+ .with_retained_fragment_matcher(|role, text| {
+ role == "developer" && text == "active policy"
+ }),
+ );
+ let retained = vec![user_message("earlier turn"), user_message("latest turn")];
+ let (replacement, snapshot) =
+ build_compaction_replacement_history(&session, &step, &world, annotated(retained.clone()))
+ .await;
+ let items = raw(replacement);
+ let ResponseItem::Message { role, content, .. } = &items[0] else {
+ panic!("expected a separate developer prefix");
+ };
+ assert_eq!(role, "developer");
+ assert_eq!(
+ content,
+ &[codex_protocol::models::input_text(
+ "active policy"
+ )]
+ );
+ assert_eq!(items[1], retained[0]);
+ assert!(
+ world
+ .render_history_diff(Some(&snapshot), &items)
+ .1
+ .is_empty()
+ );
+}
+
#[test]
fn content_items_to_text_joins_non_empty_segments() {
let items = vec![
diff --git a/codex-rs/core/src/context/world_state/mod.rs b/codex-rs/core/src/context/world_state/mod.rs
index 6724051889..49c6e9ed50 100644
--- a/codex-rs/core/src/context/world_state/mod.rs
+++ b/codex-rs/core/src/context/world_state/mod.rs
@@ -247,11 +247,20 @@ impl ErasedWorldStateSection for ExtensionWorldStateSection {
});
let updates = fragment
.into_iter()
+ .filter(|fragment| !self.0.is_window_prefix() || fragment.role() == "developer")
.map(|fragment| {
- WorldStateUpdate::fragment(WorldStateContextFragment {
+ let update = WorldStateUpdate::fragment(WorldStateContextFragment {
fragment,
content_kind: ContentItemKind(format!("{}.instructions", self.0.id())),
- })
+ });
+ if self.0.is_window_prefix() {
+ WorldStateUpdate {
+ placement: Placement::Prefix,
+ ..update
+ }
+ } else {
+ update
+ }
})
.collect();
(snapshot, updates)
diff --git a/codex-rs/core/tests/suite/scenarios.rs b/codex-rs/core/tests/suite/scenarios.rs
index 77e63a308a..deccca2eba 100644
--- a/codex-rs/core/tests/suite/scenarios.rs
+++ b/codex-rs/core/tests/suite/scenarios.rs
@@ -197,6 +197,9 @@ mod frozen_cloud_skills;
#[path = "scenarios_compaction_tests.rs"]
mod compaction;
+#[path = "scenarios_world_state_prefix_tests.rs"]
+mod world_state_prefix;
+
fn skills_extensions() -> Arc> {
let mut extensions = ExtensionRegistryBuilder::::new();
install(&mut extensions, |config: &Config| SkillsExtensionConfig {
diff --git a/codex-rs/core/tests/suite/scenarios_world_state_prefix_tests.rs b/codex-rs/core/tests/suite/scenarios_world_state_prefix_tests.rs
new file mode 100644
index 0000000000..e1b9418167
--- /dev/null
+++ b/codex-rs/core/tests/suite/scenarios_world_state_prefix_tests.rs
@@ -0,0 +1,210 @@
+//! Exercises extension prefixes through live updates, compaction, and persisted history.
+
+use super::*;
+use codex_extension_api::ContextContributor;
+use codex_extension_api::ExtensionFuture;
+use codex_extension_api::PreviousWorldStateSection;
+use codex_extension_api::RenderedWorldStateFragment;
+use codex_extension_api::WorldStateContributionInput;
+use codex_extension_api::WorldStateSectionContribution;
+use codex_protocol::models::input_text;
+use pretty_assertions::assert_eq;
+use std::sync::Mutex;
+
+struct PrefixInstructions {
+ current: Mutex<&'static str>,
+}
+
+impl ContextContributor for PrefixInstructions {
+ fn contribute_world_state<'a>(
+ &'a self,
+ _input: WorldStateContributionInput<'a>,
+ ) -> ExtensionFuture<'a, Vec> {
+ let current = *self.current.lock().expect("current instructions");
+ Box::pin(async move {
+ [
+ ("scenario_prefix", "developer"),
+ ("rejected_user_prefix", "user"),
+ ("rejected_assistant_prefix", "assistant"),
+ ]
+ .into_iter()
+ .map(|(id, role)| {
+ let body = format!("{id}: {current}");
+ let rendered = format!("{body}");
+ WorldStateSectionContribution::new(id, move |previous| {
+ let snapshot = json!(body);
+ let unchanged = matches!(previous, PreviousWorldStateSection::Known(previous) if previous == &snapshot);
+ (
+ Some(snapshot),
+ (!unchanged).then(|| {
+ RenderedWorldStateFragment::new(
+ role,
+ ("", ""),
+ input_text(body.clone()),
+ )
+ }),
+ )
+ })
+ .with_window_prefix()
+ .with_retained_fragment_matcher(move |candidate_role, text| {
+ candidate_role == role && text == rendered
+ })
+ })
+ .collect()
+ })
+ }
+}
+
+#[test_case::test_case("local"; "local")]
+#[test_case::test_case("remote"; "remote")]
+#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
+async fn extension_window_prefix_survives_compaction(compaction: &str) -> Result<()> {
+ skip_if_no_network!(Ok(()));
+
+ let server = start_mock_server().await;
+ let compact_response = match compaction {
+ "local" => ev_assistant_message("summary", "The user is planning a release."),
+ "remote" => json!({
+ "type": "response.output_item.done",
+ "item": {"type": "compaction", "encrypted_content": "PREFIX_CHECKPOINT"},
+ }),
+ _ => unreachable!(),
+ };
+ let mock = mount_sse_sequence(
+ &server,
+ vec![
+ sse(vec![
+ ev_assistant_message("first", "Plan ready."),
+ ev_completed("first-response"),
+ ]),
+ sse(vec![
+ ev_assistant_message("second", "Plan checked."),
+ ev_completed("second-response"),
+ ]),
+ sse(vec![
+ ev_assistant_message("third", "Plan updated."),
+ ev_completed("third-response"),
+ ]),
+ sse(vec![compact_response, ev_completed("compact-response")]),
+ sse(vec![
+ ev_assistant_message("continue", "Continue the release."),
+ ev_completed("continue-response"),
+ ]),
+ ],
+ )
+ .await;
+ let instructions = Arc::new(PrefixInstructions {
+ current: Mutex::new("A"),
+ });
+ let mut extensions = ExtensionRegistryBuilder::new();
+ extensions.prompt_contributor(instructions.clone());
+ let local_compaction = compaction == "local";
+ let test = test_codex()
+ .with_extensions(Arc::new(extensions.build()))
+ .with_model("gpt-6-astra")
+ .with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing())
+ .with_config(move |config| {
+ configure_scenario_catalog(config);
+ config.workspace_roots = vec![config.cwd.clone()];
+ if local_compaction {
+ config.model_provider.name = "Local compaction test".to_string();
+ }
+ })
+ .build_with_auto_env(&server)
+ .await?;
+
+ test.submit_turn("Plan the release.").await?;
+ test.submit_turn("Check the plan.").await?;
+ *instructions.current.lock().expect("current instructions") = "B";
+ test.submit_turn("Update the plan.").await?;
+ // Capture a new policy during compaction, not only one already rendered in history.
+ *instructions.current.lock().expect("current instructions") = "C";
+ test.codex.submit(Op::Compact).await?;
+ wait_for_event(&test.codex, |event| {
+ assert!(!matches!(event, EventMsg::Error(_)), "{event:?}");
+ matches!(event, EventMsg::TurnComplete(_))
+ })
+ .await;
+
+ test.codex.flush_rollout().await?;
+ let rollout = fs::read_to_string(test.codex.rollout_path().expect("rollout path"))?;
+ let replacement = rollout
+ .lines()
+ .map(codex_rollout::parse_rollout_line)
+ .collect::, _>>()?
+ .into_iter()
+ .find_map(|line| match line.item {
+ RolloutItem::Compacted(compacted) => compacted.replacement_history,
+ _ => None,
+ })
+ .expect("persisted compaction replacement")
+ .into_iter()
+ .map(|envelope| serde_json::to_value(envelope.item))
+ .collect::, _>>()?;
+
+ test.submit_turn("Continue with the release.").await?;
+ let requests = mock.requests();
+ assert_eq!(requests.len(), 5);
+ let prefix_texts = |items: &[serde_json::Value]| {
+ items
+ .iter()
+ .flat_map(|item| item["content"].as_array().into_iter().flatten())
+ .filter_map(|content| content["text"].as_str())
+ .filter(|text| text.starts_with(""))
+ .map(str::to_owned)
+ .collect::>()
+ };
+ let prefix_a = "scenario_prefix: A".to_string();
+ let prefix_b = "scenario_prefix: B".to_string();
+ let prefix_c = "scenario_prefix: C".to_string();
+ assert_eq!(prefix_texts(&requests[0].input()), vec![prefix_a.clone()]);
+ assert_eq!(prefix_texts(&requests[1].input()), vec![prefix_a.clone()]);
+ assert_eq!(prefix_texts(&requests[2].input()), vec![prefix_a, prefix_b]);
+ assert_eq!(prefix_texts(&replacement), vec![prefix_c.clone()]);
+ assert_eq!(prefix_texts(&requests[4].input()), vec![prefix_c]);
+ // Updating the policy preserves the entire old request prefix byte-for-byte.
+ let unchanged_input = requests[1].input();
+ assert_eq!(
+ &requests[2].input()[..unchanged_input.len()],
+ unchanged_input
+ );
+ for items in [
+ requests[0].input(),
+ replacement.clone(),
+ requests[4].input(),
+ ] {
+ let prefix_index = items
+ .iter()
+ .position(|item| !prefix_texts(std::slice::from_ref(item)).is_empty())
+ .expect("prefix");
+ let user_index = items
+ .iter()
+ .position(|item| item["role"] == "user")
+ .expect("user context");
+ assert!(prefix_index < user_index);
+ }
+ for request in &requests {
+ let body = request.body_json().to_string();
+ assert!(!body.contains("rejected_user_prefix"));
+ assert!(!body.contains("rejected_assistant_prefix"));
+ }
+
+ insta::assert_snapshot!(
+ format!("extension_window_prefix_{compaction}"),
+ context_snapshot::format_context_snapshot(
+ "An extension supplies A, stays unchanged, appends B, then supplies C during compaction. Only C starts the new window. Non-developer prefix fragments are never rendered.",
+ &[
+ SnapshotEntry::captured(&requests[0]).labeled("Initial policy A"),
+ SnapshotEntry::captured(&requests[1]).labeled("Unchanged policy A"),
+ SnapshotEntry::captured(&requests[2])
+ .labeled("Policy B appends to existing history"),
+ SnapshotEntry::captured(&requests[3]).labeled("Compaction request"),
+ SnapshotEntry::items(&replacement).labeled("Stored replacement before next turn"),
+ SnapshotEntry::captured(&requests[4])
+ .labeled("Unchanged policy C after compaction"),
+ ],
+ &ContextSnapshotOptions::default().include_request_settings(),
+ )
+ );
+ Ok(())
+}
diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__world_state_prefix__extension_window_prefix_local.snap b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__world_state_prefix__extension_window_prefix_local.snap
new file mode 100644
index 0000000000..102fe57c7c
--- /dev/null
+++ b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__world_state_prefix__extension_window_prefix_local.snap
@@ -0,0 +1,326 @@
+---
+source: core/tests/suite/scenarios_world_state_prefix_tests.rs
+expression: "context_snapshot::format_context_snapshot(\"An extension supplies A, stays unchanged, appends B, then supplies C during compaction. Only C starts the new window. Non-developer prefix fragments are never rendered.\",\n&[SnapshotEntry::captured(&requests[0]).labeled(\"Initial policy A\"),\nSnapshotEntry::captured(&requests[1]).labeled(\"Unchanged policy A\"),\nSnapshotEntry::captured(&requests[2]).labeled(\"Policy B appends to existing history\"),\nSnapshotEntry::captured(&requests[3]).labeled(\"Compaction request\"),\nSnapshotEntry::items(&replacement).labeled(\"Stored replacement before next turn\"),\nSnapshotEntry::captured(&requests[4]).labeled(\"Unchanged policy C after compaction\"),],\n&ContextSnapshotOptions::default().include_request_settings(),)"
+---
+Scenario: An extension supplies A, stays unchanged, appends B, then supplies C during compaction. Only C starts the new window. Non-developer prefix fragments are never rendered.
+
+## Window 1
+Settings:
+ include: ["reasoning.encrypted_content"]
+ model: "gpt-6-astra"
+ parallel_tool_calls: false
+ prompt_cache_key: ""
+ reasoning: {"context":"all_turns","effort":"low"}
+ store: false
+ stream: true
+ text: {"verbosity":"low"}
+ tool_choice: "auto"
+-- request 1 (turn; Initial policy A) --
+00:additional_tools/developer (3; hash=bf2e30605ff5dc17):
+ - namespace/functions; hash=fada4a476a3754dd
+ - custom/exec
+ - function/wait
+ - function/request_user_input
+ - function/request_user_input_async
+ - namespace/clock: Tools for reading and waiting on time.; hash=f402e8c5e9b5e317
+ - function/sleep
+ - namespace/collaboration: Tools for spawning and managing sub-agents.; hash=2c8d6295978dbe63
+ - function/followup_task
+ - function/interrupt_agent
+ - function/list_agents
+ - function/send_message
+ - function/spawn_agent
+ - function/wait_agent
+01:message/developer:
+ You are Codex, an agent based on GPT-6. You and the user share one workspace, and your job is to collabo...them until their intended goal is completely handled. [hash=efa50f074efa5803]
+
+ # When to ask the user for permission
+
+ Use your best judgement given task context for when you really need user permission, like a competent co...ork without ending the turn to clarify with the user. [hash=ca7367e658b51388]
+
+ User authorization and preferences persist across turns. Do not request permission again when the user h... any guidelines provided in skills or external files. [hash=911f91c92c72882c]
+
+
+
+ - Skill naming: If a plugin contributes skills, those skill entries are prefixed with plugin_name: in the Skills list.
+ - MCP naming: Plugin-provided MCP tools keep standard MCP identifiers such as mcp__server__tool; use tool provenance to tell which plugin they come from.
+ - Trigger rules: If the user explicitly names a plugin, prefer capabilities associated with that plugin for that turn.
+ - Relationship to capabilities: Plugins are not invoked directly. Use their underlying skills, MCP tools, and app tools to help solve the task.
+ - Relevance: Determine what a plugin can help with from explicit user mention or from the plugin-associa..., MCP tools, and apps exposed elsewhere in this turn. [hash=620459a4e7b590f0]
+ - Missing/blocked: If the user requests a plugin that does not have relevant callable capabilities for t..., say so briefly and continue with the best fallback. [hash=746b0469a8f6cb3e]
+
+02:message/developer:
+ scenario_prefix: A
+03:message/developer[2]:
+ [01]
+ Filesystem sandboxing defines which files can be read or written. `sandbox_mode` is `danger-full-access`...ll commands are permitted. Network access is enabled. [hash=c134061cebb94a1b]
+ Approval policy is currently never. Do not provide the `sandbox_permissions` for any reason, commands will be rejected.
+
+ [02] # Collaboration Mode: Default
+
+ You are now in Default mode. Any previous instructions for other modes (e.g. Plan mode) are no longer active.
+
+ Your active mode changes only when new developer instructions with a different `...<...by themselves. Known mode names are Default and Plan. [hash=e75f6b12fc52d812]
+
+ ## request_user_input availability
+
+ Use the `request_user_input` tool only when it is listed in the available tools for this turn.
+
+ Use the `request_user_input` tool only for optional questions where the answer would materially improve the quality of the work.
+
+ If `request_user_input` returns no answers, continue with best judgment instead of asking again or treating the turn as blocked.
+
+ Never use the `request_user_input` tool for permission requests or permission-related escalations.
+
+04:message/developer:
+ You are `/root`, the primary agent in a team of agents collaborating to fulfill the user's goals.
+
+ At the start of your turn, you are the active agent.
+ You can spawn sub-agents to handle subtasks, and those sub-agents can spawn their own sub-agents.
+ All agents in the team, including the agents that you can assign tasks to, are equally intelligent and capable, and have access to the same set of tools.
+
+ You can use `spawn_agent` to create a new agent, `followup_task` to give an existing agent a new task an...message to a running agent without triggering a turn. [hash=7d40ff8b58977202]
+ `send_message` calls may be read by a human, so ensure they are legible. Always put proper spaces between words and/or numbers.
+
+ - All agents use the same current working directory.
+ - As a result, edits made by one agent are immediately visible to all other agents.
+
+ When calling `wait_agent`, prefer longer waits (minutes) to avoid busy polling.
+
+ There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you.
+
+ Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d... so, set `fork_turns` to `"none"`. [hash=d5928243dc643bcc]
+05:message/developer:
+ Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work. [hash=2b6e037067b4390c]
+06:message/user:
+
+
+
+
+
+ <...e="unrestricted" /> [hash=d40b2f45cb028888]
+
+07:message/user:
+ Plan the release.
+-- request 2 (turn; Unchanged policy A) --
+08:message/assistant:
+ Plan ready.
+09:message/user:
+ Check the plan.
+-- request 3 (turn; Policy B appends to existing history) --
+10:message/assistant:
+ Plan checked.
+11:message/developer:
+ scenario_prefix: B
+12:message/user:
+ Update the plan.
+-- request 4 (compaction; Compaction request) --
+13:message/assistant:
+ Plan updated.
+14:message/user:
+ You are performing a CONTEXT CHECKPOINT COMPACTION. Create a handoff summary for another LLM that will resume the task.
+
+ Include:
+ - Current progress and key decisions made
+ - Important context, constraints, or user preferences
+ - What remains to be done (clear next steps)
+ - Any critical data, examples, or references needed to continue
+
+ Be concise, structured, and focused on helping the next LLM seamlessly continue the work.
+
+
+## Window 2 (after request 4: input diverged at item 02)
+Settings: unavailable (items only)
+-- request 5 (items; Stored replacement before next turn) --
+00:additional_tools/developer (3; hash=bf2e30605ff5dc17):
+ - namespace/functions; hash=fada4a476a3754dd
+ - custom/exec
+ - function/wait
+ - function/request_user_input
+ - function/request_user_input_async
+ - namespace/clock: Tools for reading and waiting on time.; hash=f402e8c5e9b5e317
+ - function/sleep
+ - namespace/collaboration: Tools for spawning and managing sub-agents.; hash=2c8d6295978dbe63
+ - function/followup_task
+ - function/interrupt_agent
+ - function/list_agents
+ - function/send_message
+ - function/spawn_agent
+ - function/wait_agent
+01:message/developer:
+ You are Codex, an agent based on GPT-6. You and the user share one workspace, and your job is to collabo...them until their intended goal is completely handled. [hash=efa50f074efa5803]
+
+ # When to ask the user for permission
+
+ Use your best judgement given task context for when you really need user permission, like a competent co...ork without ending the turn to clarify with the user. [hash=ca7367e658b51388]
+
+ User authorization and preferences persist across turns. Do not request permission again when the user h... any guidelines provided in skills or external files. [hash=911f91c92c72882c]
+
+
+
+ - Skill naming: If a plugin contributes skills, those skill entries are prefixed with plugin_name: in the Skills list.
+ - MCP naming: Plugin-provided MCP tools keep standard MCP identifiers such as mcp__server__tool; use tool provenance to tell which plugin they come from.
+ - Trigger rules: If the user explicitly names a plugin, prefer capabilities associated with that plugin for that turn.
+ - Relationship to capabilities: Plugins are not invoked directly. Use their underlying skills, MCP tools, and app tools to help solve the task.
+ - Relevance: Determine what a plugin can help with from explicit user mention or from the plugin-associa..., MCP tools, and apps exposed elsewhere in this turn. [hash=620459a4e7b590f0]
+ - Missing/blocked: If the user requests a plugin that does not have relevant callable capabilities for t..., say so briefly and continue with the best fallback. [hash=746b0469a8f6cb3e]
+
+02:message/developer:
+ scenario_prefix: C
+03:message/user:
+ Plan the release.
+04:message/user:
+ Check the plan.
+05:message/developer[2]:
+ [01]
+ Filesystem sandboxing defines which files can be read or written. `sandbox_mode` is `danger-full-access`...ll commands are permitted. Network access is enabled. [hash=c134061cebb94a1b]
+ Approval policy is currently never. Do not provide the `sandbox_permissions` for any reason, commands will be rejected.
+
+ [02] # Collaboration Mode: Default
+
+ You are now in Default mode. Any previous instructions for other modes (e.g. Plan mode) are no longer active.
+
+ Your active mode changes only when new developer instructions with a different `...<...by themselves. Known mode names are Default and Plan. [hash=e75f6b12fc52d812]
+
+ ## request_user_input availability
+
+ Use the `request_user_input` tool only when it is listed in the available tools for this turn.
+
+ Use the `request_user_input` tool only for optional questions where the answer would materially improve the quality of the work.
+
+ If `request_user_input` returns no answers, continue with best judgment instead of asking again or treating the turn as blocked.
+
+ Never use the `request_user_input` tool for permission requests or permission-related escalations.
+
+06:message/developer:
+ You are `/root`, the primary agent in a team of agents collaborating to fulfill the user's goals.
+
+ At the start of your turn, you are the active agent.
+ You can spawn sub-agents to handle subtasks, and those sub-agents can spawn their own sub-agents.
+ All agents in the team, including the agents that you can assign tasks to, are equally intelligent and capable, and have access to the same set of tools.
+
+ You can use `spawn_agent` to create a new agent, `followup_task` to give an existing agent a new task an...message to a running agent without triggering a turn. [hash=7d40ff8b58977202]
+ `send_message` calls may be read by a human, so ensure they are legible. Always put proper spaces between words and/or numbers.
+
+ - All agents use the same current working directory.
+ - As a result, edits made by one agent are immediately visible to all other agents.
+
+ When calling `wait_agent`, prefer longer waits (minutes) to avoid busy polling.
+
+ There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you.
+
+ Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d... so, set `fork_turns` to `"none"`. [hash=d5928243dc643bcc]
+07:message/developer:
+ Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work. [hash=2b6e037067b4390c]
+08:message/user:
+
+
+
+
+
+ <...e="unrestricted" /> [hash=d40b2f45cb028888]
+
+09:message/user:
+ Update the plan.
+10:message/user:
+ Another language model started to solve this problem and produced a summary of its thinking process. You...ion in this summary to assist with your own analysis: [hash=b8ce9f267277728e]
+ The user is planning a release.
+
+## Window 3 (after request 5)
+Settings: same as window 1
+-- request 6 (turn; Unchanged policy C after compaction) --
+00:additional_tools/developer (3; hash=bf2e30605ff5dc17):
+ - namespace/functions; hash=fada4a476a3754dd
+ - custom/exec
+ - function/wait
+ - function/request_user_input
+ - function/request_user_input_async
+ - namespace/clock: Tools for reading and waiting on time.; hash=f402e8c5e9b5e317
+ - function/sleep
+ - namespace/collaboration: Tools for spawning and managing sub-agents.; hash=2c8d6295978dbe63
+ - function/followup_task
+ - function/interrupt_agent
+ - function/list_agents
+ - function/send_message
+ - function/spawn_agent
+ - function/wait_agent
+01:message/developer:
+ You are Codex, an agent based on GPT-6. You and the user share one workspace, and your job is to collabo...them until their intended goal is completely handled. [hash=efa50f074efa5803]
+
+ # When to ask the user for permission
+
+ Use your best judgement given task context for when you really need user permission, like a competent co...ork without ending the turn to clarify with the user. [hash=ca7367e658b51388]
+
+ User authorization and preferences persist across turns. Do not request permission again when the user h... any guidelines provided in skills or external files. [hash=911f91c92c72882c]
+
+
+
+ - Skill naming: If a plugin contributes skills, those skill entries are prefixed with plugin_name: in the Skills list.
+ - MCP naming: Plugin-provided MCP tools keep standard MCP identifiers such as mcp__server__tool; use tool provenance to tell which plugin they come from.
+ - Trigger rules: If the user explicitly names a plugin, prefer capabilities associated with that plugin for that turn.
+ - Relationship to capabilities: Plugins are not invoked directly. Use their underlying skills, MCP tools, and app tools to help solve the task.
+ - Relevance: Determine what a plugin can help with from explicit user mention or from the plugin-associa..., MCP tools, and apps exposed elsewhere in this turn. [hash=620459a4e7b590f0]
+ - Missing/blocked: If the user requests a plugin that does not have relevant callable capabilities for t..., say so briefly and continue with the best fallback. [hash=746b0469a8f6cb3e]
+
+02:message/developer:
+ scenario_prefix: C
+03:message/user:
+ Plan the release.
+04:message/user:
+ Check the plan.
+05:message/developer[2]:
+ [01]
+ Filesystem sandboxing defines which files can be read or written. `sandbox_mode` is `danger-full-access`...ll commands are permitted. Network access is enabled. [hash=c134061cebb94a1b]
+ Approval policy is currently never. Do not provide the `sandbox_permissions` for any reason, commands will be rejected.
+
+ [02] # Collaboration Mode: Default
+
+ You are now in Default mode. Any previous instructions for other modes (e.g. Plan mode) are no longer active.
+
+ Your active mode changes only when new developer instructions with a different `...<...by themselves. Known mode names are Default and Plan. [hash=e75f6b12fc52d812]
+
+ ## request_user_input availability
+
+ Use the `request_user_input` tool only when it is listed in the available tools for this turn.
+
+ Use the `request_user_input` tool only for optional questions where the answer would materially improve the quality of the work.
+
+ If `request_user_input` returns no answers, continue with best judgment instead of asking again or treating the turn as blocked.
+
+ Never use the `request_user_input` tool for permission requests or permission-related escalations.
+
+06:message/developer:
+ You are `/root`, the primary agent in a team of agents collaborating to fulfill the user's goals.
+
+ At the start of your turn, you are the active agent.
+ You can spawn sub-agents to handle subtasks, and those sub-agents can spawn their own sub-agents.
+ All agents in the team, including the agents that you can assign tasks to, are equally intelligent and capable, and have access to the same set of tools.
+
+ You can use `spawn_agent` to create a new agent, `followup_task` to give an existing agent a new task an...message to a running agent without triggering a turn. [hash=7d40ff8b58977202]
+ `send_message` calls may be read by a human, so ensure they are legible. Always put proper spaces between words and/or numbers.
+
+ - All agents use the same current working directory.
+ - As a result, edits made by one agent are immediately visible to all other agents.
+
+ When calling `wait_agent`, prefer longer waits (minutes) to avoid busy polling.
+
+ There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you.
+
+ Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d... so, set `fork_turns` to `"none"`. [hash=d5928243dc643bcc]
+07:message/developer:
+ Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work. [hash=2b6e037067b4390c]
+08:message/user:
+
+
+
+
+
+ <...e="unrestricted" /> [hash=d40b2f45cb028888]
+
+09:message/user:
+ Update the plan.
+10:message/user:
+ Another language model started to solve this problem and produced a summary of its thinking process. You...ion in this summary to assist with your own analysis: [hash=b8ce9f267277728e]
+ The user is planning a release.
+11:message/user:
+ Continue with the release.
diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__world_state_prefix__extension_window_prefix_remote.snap b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__world_state_prefix__extension_window_prefix_remote.snap
new file mode 100644
index 0000000000..5c750ea812
--- /dev/null
+++ b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__world_state_prefix__extension_window_prefix_remote.snap
@@ -0,0 +1,313 @@
+---
+source: core/tests/suite/scenarios_world_state_prefix_tests.rs
+expression: "context_snapshot::format_context_snapshot(\"An extension supplies A, stays unchanged, appends B, then supplies C during compaction. Only C starts the new window. Non-developer prefix fragments are never rendered.\",\n&[SnapshotEntry::captured(&requests[0]).labeled(\"Initial policy A\"),\nSnapshotEntry::captured(&requests[1]).labeled(\"Unchanged policy A\"),\nSnapshotEntry::captured(&requests[2]).labeled(\"Policy B appends to existing history\"),\nSnapshotEntry::captured(&requests[3]).labeled(\"Compaction request\"),\nSnapshotEntry::items(&replacement).labeled(\"Stored replacement before next turn\"),\nSnapshotEntry::captured(&requests[4]).labeled(\"Unchanged policy C after compaction\"),],\n&ContextSnapshotOptions::default().include_request_settings(),)"
+---
+Scenario: An extension supplies A, stays unchanged, appends B, then supplies C during compaction. Only C starts the new window. Non-developer prefix fragments are never rendered.
+
+## Window 1
+Settings:
+ include: ["reasoning.encrypted_content"]
+ model: "gpt-6-astra"
+ parallel_tool_calls: false
+ prompt_cache_key: ""
+ reasoning: {"context":"all_turns","effort":"low"}
+ store: false
+ stream: true
+ text: {"verbosity":"low"}
+ tool_choice: "auto"
+-- request 1 (turn; Initial policy A) --
+00:additional_tools/developer (3; hash=bf2e30605ff5dc17):
+ - namespace/functions; hash=fada4a476a3754dd
+ - custom/exec
+ - function/wait
+ - function/request_user_input
+ - function/request_user_input_async
+ - namespace/clock: Tools for reading and waiting on time.; hash=f402e8c5e9b5e317
+ - function/sleep
+ - namespace/collaboration: Tools for spawning and managing sub-agents.; hash=2c8d6295978dbe63
+ - function/followup_task
+ - function/interrupt_agent
+ - function/list_agents
+ - function/send_message
+ - function/spawn_agent
+ - function/wait_agent
+01:message/developer:
+ You are Codex, an agent based on GPT-6. You and the user share one workspace, and your job is to collabo...them until their intended goal is completely handled. [hash=efa50f074efa5803]
+
+ # When to ask the user for permission
+
+ Use your best judgement given task context for when you really need user permission, like a competent co...ork without ending the turn to clarify with the user. [hash=ca7367e658b51388]
+
+ User authorization and preferences persist across turns. Do not request permission again when the user h... any guidelines provided in skills or external files. [hash=911f91c92c72882c]
+
+
+
+ - Skill naming: If a plugin contributes skills, those skill entries are prefixed with plugin_name: in the Skills list.
+ - MCP naming: Plugin-provided MCP tools keep standard MCP identifiers such as mcp__server__tool; use tool provenance to tell which plugin they come from.
+ - Trigger rules: If the user explicitly names a plugin, prefer capabilities associated with that plugin for that turn.
+ - Relationship to capabilities: Plugins are not invoked directly. Use their underlying skills, MCP tools, and app tools to help solve the task.
+ - Relevance: Determine what a plugin can help with from explicit user mention or from the plugin-associa..., MCP tools, and apps exposed elsewhere in this turn. [hash=620459a4e7b590f0]
+ - Missing/blocked: If the user requests a plugin that does not have relevant callable capabilities for t..., say so briefly and continue with the best fallback. [hash=746b0469a8f6cb3e]
+
+02:message/developer:
+ scenario_prefix: A
+03:message/developer[2]:
+ [01]
+ Filesystem sandboxing defines which files can be read or written. `sandbox_mode` is `danger-full-access`...ll commands are permitted. Network access is enabled. [hash=c134061cebb94a1b]
+ Approval policy is currently never. Do not provide the `sandbox_permissions` for any reason, commands will be rejected.
+
+ [02] # Collaboration Mode: Default
+
+ You are now in Default mode. Any previous instructions for other modes (e.g. Plan mode) are no longer active.
+
+ Your active mode changes only when new developer instructions with a different `...<...by themselves. Known mode names are Default and Plan. [hash=e75f6b12fc52d812]
+
+ ## request_user_input availability
+
+ Use the `request_user_input` tool only when it is listed in the available tools for this turn.
+
+ Use the `request_user_input` tool only for optional questions where the answer would materially improve the quality of the work.
+
+ If `request_user_input` returns no answers, continue with best judgment instead of asking again or treating the turn as blocked.
+
+ Never use the `request_user_input` tool for permission requests or permission-related escalations.
+
+04:message/developer:
+ You are `/root`, the primary agent in a team of agents collaborating to fulfill the user's goals.
+
+ At the start of your turn, you are the active agent.
+ You can spawn sub-agents to handle subtasks, and those sub-agents can spawn their own sub-agents.
+ All agents in the team, including the agents that you can assign tasks to, are equally intelligent and capable, and have access to the same set of tools.
+
+ You can use `spawn_agent` to create a new agent, `followup_task` to give an existing agent a new task an...message to a running agent without triggering a turn. [hash=7d40ff8b58977202]
+ `send_message` calls may be read by a human, so ensure they are legible. Always put proper spaces between words and/or numbers.
+
+ - All agents use the same current working directory.
+ - As a result, edits made by one agent are immediately visible to all other agents.
+
+ When calling `wait_agent`, prefer longer waits (minutes) to avoid busy polling.
+
+ There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you.
+
+ Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d... so, set `fork_turns` to `"none"`. [hash=d5928243dc643bcc]
+05:message/developer:
+ Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work. [hash=2b6e037067b4390c]
+06:message/user:
+
+
+
+
+
+ <...e="unrestricted" /> [hash=d40b2f45cb028888]
+
+07:message/user:
+ Plan the release.
+08:configuration_update:reasoning={"effort":"low"}
+-- request 2 (turn; Unchanged policy A) --
+09:message/assistant:
+ Plan ready.
+10:message/user:
+ Check the plan.
+-- request 3 (turn; Policy B appends to existing history) --
+11:message/assistant:
+ Plan checked.
+12:message/developer:
+ scenario_prefix: B
+13:message/user:
+ Update the plan.
+-- request 4 (compaction; Compaction request) --
+14:message/assistant:
+ Plan updated.
+15:compaction_trigger
+
+## Window 2 (after request 4: input diverged at item 02)
+Settings: unavailable (items only)
+-- request 5 (items; Stored replacement before next turn) --
+00:additional_tools/developer (3; hash=bf2e30605ff5dc17):
+ - namespace/functions; hash=fada4a476a3754dd
+ - custom/exec
+ - function/wait
+ - function/request_user_input
+ - function/request_user_input_async
+ - namespace/clock: Tools for reading and waiting on time.; hash=f402e8c5e9b5e317
+ - function/sleep
+ - namespace/collaboration: Tools for spawning and managing sub-agents.; hash=2c8d6295978dbe63
+ - function/followup_task
+ - function/interrupt_agent
+ - function/list_agents
+ - function/send_message
+ - function/spawn_agent
+ - function/wait_agent
+01:message/developer:
+ You are Codex, an agent based on GPT-6. You and the user share one workspace, and your job is to collabo...them until their intended goal is completely handled. [hash=efa50f074efa5803]
+
+ # When to ask the user for permission
+
+ Use your best judgement given task context for when you really need user permission, like a competent co...ork without ending the turn to clarify with the user. [hash=ca7367e658b51388]
+
+ User authorization and preferences persist across turns. Do not request permission again when the user h... any guidelines provided in skills or external files. [hash=911f91c92c72882c]
+
+
+
+ - Skill naming: If a plugin contributes skills, those skill entries are prefixed with plugin_name: in the Skills list.
+ - MCP naming: Plugin-provided MCP tools keep standard MCP identifiers such as mcp__server__tool; use tool provenance to tell which plugin they come from.
+ - Trigger rules: If the user explicitly names a plugin, prefer capabilities associated with that plugin for that turn.
+ - Relationship to capabilities: Plugins are not invoked directly. Use their underlying skills, MCP tools, and app tools to help solve the task.
+ - Relevance: Determine what a plugin can help with from explicit user mention or from the plugin-associa..., MCP tools, and apps exposed elsewhere in this turn. [hash=620459a4e7b590f0]
+ - Missing/blocked: If the user requests a plugin that does not have relevant callable capabilities for t..., say so briefly and continue with the best fallback. [hash=746b0469a8f6cb3e]
+
+02:message/developer:
+ scenario_prefix: C
+03:message/user:
+ Plan the release.
+04:message/user:
+ Check the plan.
+05:message/developer[2]:
+ [01]
+ Filesystem sandboxing defines which files can be read or written. `sandbox_mode` is `danger-full-access`...ll commands are permitted. Network access is enabled. [hash=c134061cebb94a1b]
+ Approval policy is currently never. Do not provide the `sandbox_permissions` for any reason, commands will be rejected.
+
+ [02] # Collaboration Mode: Default
+
+ You are now in Default mode. Any previous instructions for other modes (e.g. Plan mode) are no longer active.
+
+ Your active mode changes only when new developer instructions with a different `...<...by themselves. Known mode names are Default and Plan. [hash=e75f6b12fc52d812]
+
+ ## request_user_input availability
+
+ Use the `request_user_input` tool only when it is listed in the available tools for this turn.
+
+ Use the `request_user_input` tool only for optional questions where the answer would materially improve the quality of the work.
+
+ If `request_user_input` returns no answers, continue with best judgment instead of asking again or treating the turn as blocked.
+
+ Never use the `request_user_input` tool for permission requests or permission-related escalations.
+
+06:message/developer:
+ You are `/root`, the primary agent in a team of agents collaborating to fulfill the user's goals.
+
+ At the start of your turn, you are the active agent.
+ You can spawn sub-agents to handle subtasks, and those sub-agents can spawn their own sub-agents.
+ All agents in the team, including the agents that you can assign tasks to, are equally intelligent and capable, and have access to the same set of tools.
+
+ You can use `spawn_agent` to create a new agent, `followup_task` to give an existing agent a new task an...message to a running agent without triggering a turn. [hash=7d40ff8b58977202]
+ `send_message` calls may be read by a human, so ensure they are legible. Always put proper spaces between words and/or numbers.
+
+ - All agents use the same current working directory.
+ - As a result, edits made by one agent are immediately visible to all other agents.
+
+ When calling `wait_agent`, prefer longer waits (minutes) to avoid busy polling.
+
+ There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you.
+
+ Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d... so, set `fork_turns` to `"none"`. [hash=d5928243dc643bcc]
+07:message/developer:
+ Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work. [hash=2b6e037067b4390c]
+08:message/user:
+
+
+
+
+
+ <...e="unrestricted" /> [hash=d40b2f45cb028888]
+
+09:message/user:
+ Update the plan.
+10:compaction:encrypted=true; chars=17; hash=4bf78ed3d93cf9a2
+
+## Window 3 (after request 5)
+Settings: same as window 1
+-- request 6 (turn; Unchanged policy C after compaction) --
+00:additional_tools/developer (3; hash=bf2e30605ff5dc17):
+ - namespace/functions; hash=fada4a476a3754dd
+ - custom/exec
+ - function/wait
+ - function/request_user_input
+ - function/request_user_input_async
+ - namespace/clock: Tools for reading and waiting on time.; hash=f402e8c5e9b5e317
+ - function/sleep
+ - namespace/collaboration: Tools for spawning and managing sub-agents.; hash=2c8d6295978dbe63
+ - function/followup_task
+ - function/interrupt_agent
+ - function/list_agents
+ - function/send_message
+ - function/spawn_agent
+ - function/wait_agent
+01:message/developer:
+ You are Codex, an agent based on GPT-6. You and the user share one workspace, and your job is to collabo...them until their intended goal is completely handled. [hash=efa50f074efa5803]
+
+ # When to ask the user for permission
+
+ Use your best judgement given task context for when you really need user permission, like a competent co...ork without ending the turn to clarify with the user. [hash=ca7367e658b51388]
+
+ User authorization and preferences persist across turns. Do not request permission again when the user h... any guidelines provided in skills or external files. [hash=911f91c92c72882c]
+
+
+
+ - Skill naming: If a plugin contributes skills, those skill entries are prefixed with plugin_name: in the Skills list.
+ - MCP naming: Plugin-provided MCP tools keep standard MCP identifiers such as mcp__server__tool; use tool provenance to tell which plugin they come from.
+ - Trigger rules: If the user explicitly names a plugin, prefer capabilities associated with that plugin for that turn.
+ - Relationship to capabilities: Plugins are not invoked directly. Use their underlying skills, MCP tools, and app tools to help solve the task.
+ - Relevance: Determine what a plugin can help with from explicit user mention or from the plugin-associa..., MCP tools, and apps exposed elsewhere in this turn. [hash=620459a4e7b590f0]
+ - Missing/blocked: If the user requests a plugin that does not have relevant callable capabilities for t..., say so briefly and continue with the best fallback. [hash=746b0469a8f6cb3e]
+
+02:message/developer:
+ scenario_prefix: C
+03:message/user:
+ Plan the release.
+04:message/user:
+ Check the plan.
+05:message/developer[2]:
+ [01]
+ Filesystem sandboxing defines which files can be read or written. `sandbox_mode` is `danger-full-access`...ll commands are permitted. Network access is enabled. [hash=c134061cebb94a1b]
+ Approval policy is currently never. Do not provide the `sandbox_permissions` for any reason, commands will be rejected.
+
+ [02] # Collaboration Mode: Default
+
+ You are now in Default mode. Any previous instructions for other modes (e.g. Plan mode) are no longer active.
+
+ Your active mode changes only when new developer instructions with a different `...<...by themselves. Known mode names are Default and Plan. [hash=e75f6b12fc52d812]
+
+ ## request_user_input availability
+
+ Use the `request_user_input` tool only when it is listed in the available tools for this turn.
+
+ Use the `request_user_input` tool only for optional questions where the answer would materially improve the quality of the work.
+
+ If `request_user_input` returns no answers, continue with best judgment instead of asking again or treating the turn as blocked.
+
+ Never use the `request_user_input` tool for permission requests or permission-related escalations.
+
+06:message/developer:
+ You are `/root`, the primary agent in a team of agents collaborating to fulfill the user's goals.
+
+ At the start of your turn, you are the active agent.
+ You can spawn sub-agents to handle subtasks, and those sub-agents can spawn their own sub-agents.
+ All agents in the team, including the agents that you can assign tasks to, are equally intelligent and capable, and have access to the same set of tools.
+
+ You can use `spawn_agent` to create a new agent, `followup_task` to give an existing agent a new task an...message to a running agent without triggering a turn. [hash=7d40ff8b58977202]
+ `send_message` calls may be read by a human, so ensure they are legible. Always put proper spaces between words and/or numbers.
+
+ - All agents use the same current working directory.
+ - As a result, edits made by one agent are immediately visible to all other agents.
+
+ When calling `wait_agent`, prefer longer waits (minutes) to avoid busy polling.
+
+ There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you.
+
+ Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d... so, set `fork_turns` to `"none"`. [hash=d5928243dc643bcc]
+07:message/developer:
+ Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work. [hash=2b6e037067b4390c]
+08:message/user:
+
+
+
+
+
+ <...e="unrestricted" /> [hash=d40b2f45cb028888]
+
+09:message/user:
+ Update the plan.
+10:compaction:encrypted=true; chars=17; hash=4bf78ed3d93cf9a2
+11:message/user:
+ Continue with the release.
diff --git a/codex-rs/ext/extension-api/src/contributors/world_state.rs b/codex-rs/ext/extension-api/src/contributors/world_state.rs
index a1e0cd05df..7f30e074ad 100644
--- a/codex-rs/ext/extension-api/src/contributors/world_state.rs
+++ b/codex-rs/ext/extension-api/src/contributors/world_state.rs
@@ -98,6 +98,7 @@ type LegacyFragmentMatcher = dyn Fn(&str, &str) -> bool + Send + Sync;
#[derive(Clone)]
pub struct WorldStateSectionContribution {
id: &'static str,
+ window_prefix: bool,
render_diff: Arc,
matches_legacy_fragment: Arc,
matches_retained_fragment: Option>,
@@ -118,6 +119,7 @@ impl WorldStateSectionContribution {
) -> Self {
Self {
id,
+ window_prefix: false,
render_diff: Arc::new(render_diff),
matches_legacy_fragment: Arc::new(|_, _| false),
matches_retained_fragment: None,
@@ -132,6 +134,20 @@ impl WorldStateSectionContribution {
self
}
+ /// Render this section's developer fragments as separate messages before retained
+ /// history when the harness builds a window (including after compaction). Changes
+ /// within an existing window are still appended as ordinary history updates.
+ /// Fragments with any other role are not rendered, including in history updates.
+ /// The producer still owns content size bounds; prefix placement does not truncate.
+ pub fn with_window_prefix(mut self) -> Self {
+ self.window_prefix = true;
+ self
+ }
+
+ pub fn is_window_prefix(&self) -> bool {
+ self.window_prefix
+ }
+
/// Requires a matching model-visible fragment whenever a persisted snapshot is reused.
pub fn with_retained_fragment_matcher(
mut self,