Support window prefixes for extension world-state sections (#57276)

## What changed

Add `WorldStateSectionContribution::with_window_prefix()` to render extension developer fragments as separate messages before retained history when building a context window, including after compaction. Updates within an existing window still append to history. Ignore fragments with other roles for sections that opt in.

## Testing

Add a unit test for prefix placement and duplicate avoidance, plus local and remote compaction scenarios covering live updates, persisted replacement history, and rejection of non-developer fragments.

GitOrigin-RevId: 8621457c6eec17f1585ca8e6fa154b1bf3257330
This commit is contained in:
Francis Chalissery
2026-10-26 21:46:26 +00:00
committed by copyberry
parent 166aed9854
commit d416d8a0ff
7 changed files with 928 additions and 2 deletions
+49
View File
@@ -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",
("<example>", "</example>"),
codex_protocol::models::input_text("active policy"),
)
});
(Some(json!("active policy")), fragment)
})
.with_window_prefix()
.with_retained_fragment_matcher(|role, text| {
role == "developer" && text == "<example>active policy</example>"
}),
);
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(
"<example>active policy</example>"
)]
);
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![
+11 -2
View File
@@ -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)
+3
View File
@@ -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<ExtensionRegistry<Config>> {
let mut extensions = ExtensionRegistryBuilder::<Config>::new();
install(&mut extensions, |config: &Config| SkillsExtensionConfig {
@@ -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<WorldStateSectionContribution>> {
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!("<scenario_instructions>{body}</scenario_instructions>");
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,
("<scenario_instructions>", "</scenario_instructions>"),
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::<Result<Vec<_>, _>>()?
.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::<Result<Vec<_>, _>>()?;
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("<scenario_instructions>"))
.map(str::to_owned)
.collect::<Vec<_>>()
};
let prefix_a = "<scenario_instructions>scenario_prefix: A</scenario_instructions>".to_string();
let prefix_b = "<scenario_instructions>scenario_prefix: B</scenario_instructions>".to_string();
let prefix_c = "<scenario_instructions>scenario_prefix: C</scenario_instructions>".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(())
}
@@ -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: "<PROMPT_CACHE_KEY 1>"
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]
<OMITTED 155 LINES; ~4935 TOKENS; hash=5573869716192298>
- 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_instructions>scenario_prefix: A</scenario_instructions>
03:message/developer[2]:
[01] <permissions instructions>
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.
</permissions instructions>
[02] <collaboration_mode># 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 `<collaboration_mode>...<...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.
</collaboration_mode>
04:message/developer:
<multi_agent_role>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.
<OMITTED 17 LINES; ~246 TOKENS; hash=780031e2df907944>
- 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"`.</multi_agent_role> [hash=d5928243dc643bcc]
05:message/developer:
<multi_agent_mode>Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work.</multi_agent_mode> [hash=2b6e037067b4390c]
06:message/user:
<environment_context>
<cwd><CWD></cwd>
<shell><HOST_SHELL></shell>
<current_date><CURRENT_DATE></current_date>
<timezone><HOST_TIMEZONE></timezone>
<filesystem><workspace_roots><root><CWD></root></workspace_roots><permission_profile type="disabled"><...e="unrestricted" /></permission_profile></filesystem> [hash=d40b2f45cb028888]
</environment_context>
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_instructions>scenario_prefix: B</scenario_instructions>
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]
<OMITTED 155 LINES; ~4935 TOKENS; hash=5573869716192298>
- 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_instructions>scenario_prefix: C</scenario_instructions>
03:message/user:
Plan the release.
04:message/user:
Check the plan.
05:message/developer[2]:
[01] <permissions instructions>
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.
</permissions instructions>
[02] <collaboration_mode># 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 `<collaboration_mode>...<...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.
</collaboration_mode>
06:message/developer:
<multi_agent_role>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.
<OMITTED 17 LINES; ~246 TOKENS; hash=780031e2df907944>
- 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"`.</multi_agent_role> [hash=d5928243dc643bcc]
07:message/developer:
<multi_agent_mode>Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work.</multi_agent_mode> [hash=2b6e037067b4390c]
08:message/user:
<environment_context>
<cwd><CWD></cwd>
<shell><HOST_SHELL></shell>
<current_date><CURRENT_DATE></current_date>
<timezone><HOST_TIMEZONE></timezone>
<filesystem><workspace_roots><root><CWD></root></workspace_roots><permission_profile type="disabled"><...e="unrestricted" /></permission_profile></filesystem> [hash=d40b2f45cb028888]
</environment_context>
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]
<OMITTED 155 LINES; ~4935 TOKENS; hash=5573869716192298>
- 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_instructions>scenario_prefix: C</scenario_instructions>
03:message/user:
Plan the release.
04:message/user:
Check the plan.
05:message/developer[2]:
[01] <permissions instructions>
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.
</permissions instructions>
[02] <collaboration_mode># 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 `<collaboration_mode>...<...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.
</collaboration_mode>
06:message/developer:
<multi_agent_role>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.
<OMITTED 17 LINES; ~246 TOKENS; hash=780031e2df907944>
- 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"`.</multi_agent_role> [hash=d5928243dc643bcc]
07:message/developer:
<multi_agent_mode>Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work.</multi_agent_mode> [hash=2b6e037067b4390c]
08:message/user:
<environment_context>
<cwd><CWD></cwd>
<shell><HOST_SHELL></shell>
<current_date><CURRENT_DATE></current_date>
<timezone><HOST_TIMEZONE></timezone>
<filesystem><workspace_roots><root><CWD></root></workspace_roots><permission_profile type="disabled"><...e="unrestricted" /></permission_profile></filesystem> [hash=d40b2f45cb028888]
</environment_context>
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.
@@ -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: "<PROMPT_CACHE_KEY 1>"
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]
<OMITTED 155 LINES; ~4935 TOKENS; hash=5573869716192298>
- 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_instructions>scenario_prefix: A</scenario_instructions>
03:message/developer[2]:
[01] <permissions instructions>
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.
</permissions instructions>
[02] <collaboration_mode># 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 `<collaboration_mode>...<...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.
</collaboration_mode>
04:message/developer:
<multi_agent_role>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.
<OMITTED 17 LINES; ~246 TOKENS; hash=780031e2df907944>
- 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"`.</multi_agent_role> [hash=d5928243dc643bcc]
05:message/developer:
<multi_agent_mode>Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work.</multi_agent_mode> [hash=2b6e037067b4390c]
06:message/user:
<environment_context>
<cwd><CWD></cwd>
<shell><HOST_SHELL></shell>
<current_date><CURRENT_DATE></current_date>
<timezone><HOST_TIMEZONE></timezone>
<filesystem><workspace_roots><root><CWD></root></workspace_roots><permission_profile type="disabled"><...e="unrestricted" /></permission_profile></filesystem> [hash=d40b2f45cb028888]
</environment_context>
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_instructions>scenario_prefix: B</scenario_instructions>
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]
<OMITTED 155 LINES; ~4935 TOKENS; hash=5573869716192298>
- 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_instructions>scenario_prefix: C</scenario_instructions>
03:message/user:
Plan the release.
04:message/user:
Check the plan.
05:message/developer[2]:
[01] <permissions instructions>
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.
</permissions instructions>
[02] <collaboration_mode># 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 `<collaboration_mode>...<...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.
</collaboration_mode>
06:message/developer:
<multi_agent_role>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.
<OMITTED 17 LINES; ~246 TOKENS; hash=780031e2df907944>
- 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"`.</multi_agent_role> [hash=d5928243dc643bcc]
07:message/developer:
<multi_agent_mode>Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work.</multi_agent_mode> [hash=2b6e037067b4390c]
08:message/user:
<environment_context>
<cwd><CWD></cwd>
<shell><HOST_SHELL></shell>
<current_date><CURRENT_DATE></current_date>
<timezone><HOST_TIMEZONE></timezone>
<filesystem><workspace_roots><root><CWD></root></workspace_roots><permission_profile type="disabled"><...e="unrestricted" /></permission_profile></filesystem> [hash=d40b2f45cb028888]
</environment_context>
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]
<OMITTED 155 LINES; ~4935 TOKENS; hash=5573869716192298>
- 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_instructions>scenario_prefix: C</scenario_instructions>
03:message/user:
Plan the release.
04:message/user:
Check the plan.
05:message/developer[2]:
[01] <permissions instructions>
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.
</permissions instructions>
[02] <collaboration_mode># 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 `<collaboration_mode>...<...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.
</collaboration_mode>
06:message/developer:
<multi_agent_role>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.
<OMITTED 17 LINES; ~246 TOKENS; hash=780031e2df907944>
- 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"`.</multi_agent_role> [hash=d5928243dc643bcc]
07:message/developer:
<multi_agent_mode>Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work.</multi_agent_mode> [hash=2b6e037067b4390c]
08:message/user:
<environment_context>
<cwd><CWD></cwd>
<shell><HOST_SHELL></shell>
<current_date><CURRENT_DATE></current_date>
<timezone><HOST_TIMEZONE></timezone>
<filesystem><workspace_roots><root><CWD></root></workspace_roots><permission_profile type="disabled"><...e="unrestricted" /></permission_profile></filesystem> [hash=d40b2f45cb028888]
</environment_context>
09:message/user:
Update the plan.
10:compaction:encrypted=true; chars=17; hash=4bf78ed3d93cf9a2
11:message/user:
Continue with the release.
@@ -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<RenderDiff>,
matches_legacy_fragment: Arc<LegacyFragmentMatcher>,
matches_retained_fragment: Option<Arc<LegacyFragmentMatcher>>,
@@ -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,