diff --git a/codex-rs/core/src/context/world_state/agents_md_tests.rs b/codex-rs/core/src/context/world_state/agents_md_tests.rs
index d39fe6c727..d5644ec42c 100644
--- a/codex-rs/core/src/context/world_state/agents_md_tests.rs
+++ b/codex-rs/core/src/context/world_state/agents_md_tests.rs
@@ -1,101 +1,29 @@
+use super::super::PreviousSectionState;
+use super::super::test_support::render_section_cases;
use super::*;
-use crate::context::world_state::WorldState;
-use codex_protocol::models::ContentItem;
-use codex_protocol::models::ResponseItem;
-use pretty_assertions::assert_eq;
-use serde_json::json;
#[test]
-fn renders_full_state_and_omits_unchanged_state() {
- let loaded = LoadedAgentsMd::from_text_for_testing("use the project formatter");
- let mut state = WorldState::default();
- state.add_section(AgentsMdState::new(Some(&loaded)));
+fn snapshots() {
+ use PreviousSectionState::Absent;
+ use PreviousSectionState::Known;
+ use PreviousSectionState::Unknown;
- assert_eq!(
- vec![user_message(
- "# AGENTS.md instructions\n\n\nuse the project formatter\n",
- )],
- render_fragments(state.render_full()),
- );
- assert_eq!(
- Vec::::new(),
- render_fragments(state.render_diff(&state.snapshot()))
- );
- assert_eq!(
- state.snapshot().into_value(),
- json!({"agents_md": {"text": "use the project formatter"}}),
- );
-}
+ let empty = AgentsMdState::default();
+ let project_formatter = LoadedAgentsMd::from_text_for_testing("use the project formatter");
+ let project_formatter = AgentsMdState::new(Some(&project_formatter));
+ let old = LoadedAgentsMd::from_text_for_testing("old instructions");
+ let old = AgentsMdState::new(Some(&old));
+ let new = LoadedAgentsMd::from_text_for_testing("new instructions");
+ let new = AgentsMdState::new(Some(&new));
-#[test]
-fn changed_and_removed_state_supersedes_previous_instructions() {
- let previous_loaded = LoadedAgentsMd::from_text_for_testing("old instructions");
- let mut previous = WorldState::default();
- previous.add_section(AgentsMdState::new(Some(&previous_loaded)));
-
- let current_loaded = LoadedAgentsMd::from_text_for_testing("new instructions");
- let mut current = WorldState::default();
- current.add_section(AgentsMdState::new(Some(¤t_loaded)));
- assert_eq!(
- vec![user_message(
- "# AGENTS.md instructions\n\n\nThese AGENTS.md instructions replace all previously provided AGENTS.md instructions.\n\nnew instructions\n",
- )],
- render_fragments(current.render_diff(&previous.snapshot())),
- );
-
- let mut removed = WorldState::default();
- removed.add_section(AgentsMdState::default());
- assert_eq!(
- vec![user_message(
- "# AGENTS.md instructions\n\n\nThe previously provided AGENTS.md instructions no longer apply.\n",
- )],
- render_fragments(removed.render_diff(¤t.snapshot())),
- );
-}
-
-#[test]
-fn unknown_previous_state_is_explicitly_superseded() {
- let loaded = LoadedAgentsMd::from_text_for_testing("current instructions");
- let current = AgentsMdState::new(Some(&loaded));
- assert_eq!(
- vec![user_message(
- "# AGENTS.md instructions\n\n\nThese AGENTS.md instructions replace all previously provided AGENTS.md instructions.\n\ncurrent instructions\n",
- )],
- render_fragments(vec![
- WorldStateSection::render_diff(¤t, PreviousSectionState::Unknown)
- .expect("unknown state should be replaced"),
- ]),
- );
-
- assert_eq!(
- vec![user_message(
- "# AGENTS.md instructions\n\n\nThe previously provided AGENTS.md instructions no longer apply.\n",
- )],
- render_fragments(vec![
- WorldStateSection::render_diff(
- &AgentsMdState::default(),
- PreviousSectionState::Unknown,
- )
- .expect("unknown state should be removed"),
- ]),
- );
-}
-
-fn render_fragments(fragments: Vec>) -> Vec {
- fragments
- .into_iter()
- .map(ContextualUserFragment::into_boxed_response_item)
- .collect()
-}
-
-fn user_message(text: &str) -> ResponseItem {
- ResponseItem::Message {
- id: None,
- role: "user".to_string(),
- content: vec![ContentItem::InputText {
- text: text.to_string(),
- }],
- phase: None,
- internal_chat_message_metadata_passthrough: None,
- }
+ insta::assert_snapshot!(render_section_cases(&[
+ (Absent, Absent),
+ (Absent, Known(&empty)),
+ (Absent, Known(&project_formatter)),
+ (Known(&project_formatter), Known(&project_formatter)),
+ (Known(&old), Known(&new)),
+ (Known(&new), Known(&empty)),
+ (Unknown, Known(&new)),
+ (Unknown, Known(&empty)),
+ ]));
}
diff --git a/codex-rs/core/src/context/world_state/apps_instructions_tests.rs b/codex-rs/core/src/context/world_state/apps_instructions_tests.rs
index 92f6613594..dc67acf048 100644
--- a/codex-rs/core/src/context/world_state/apps_instructions_tests.rs
+++ b/codex-rs/core/src/context/world_state/apps_instructions_tests.rs
@@ -1,40 +1,29 @@
use super::*;
use crate::context::ContextualUserFragment;
+use crate::context::world_state::PreviousSectionState;
+use crate::context::world_state::test_support::render_section_cases;
use codex_protocol::models::ResponseItem;
use pretty_assertions::assert_eq;
-fn render(state: AppsInstructionsState, previous: PreviousSectionState<'_, bool>) -> Vec {
- state
- .render_diff(previous)
- .into_iter()
- .map(|fragment| fragment.render())
- .collect()
-}
-
#[test]
-fn renders_only_when_apps_become_available() {
+fn snapshots() {
+ use PreviousSectionState::Absent;
+ use PreviousSectionState::Known;
+ use PreviousSectionState::Unknown;
+
let unavailable = AppsInstructionsState::new(/*available*/ false);
let available = AppsInstructionsState::new(/*available*/ true);
- let false_snapshot = false;
- let true_snapshot = true;
- assert_eq!(
- render(unavailable, PreviousSectionState::Absent),
- Vec::::new()
- );
- assert_eq!(render(available, PreviousSectionState::Absent).len(), 1);
- assert_eq!(
- render(available, PreviousSectionState::Known(&false_snapshot)).len(),
- 1
- );
- assert_eq!(
- render(available, PreviousSectionState::Known(&true_snapshot)),
- Vec::::new()
- );
- assert_eq!(
- render(unavailable, PreviousSectionState::Known(&true_snapshot)),
- Vec::::new()
- );
+ insta::assert_snapshot!(render_section_cases(&[
+ (Absent, Absent),
+ (Absent, Known(&unavailable)),
+ (Absent, Known(&available)),
+ (Known(&unavailable), Known(&available)),
+ (Known(&available), Known(&available)),
+ (Known(&available), Known(&unavailable)),
+ (Unknown, Known(&unavailable)),
+ (Unknown, Known(&available)),
+ ]));
}
#[test]
diff --git a/codex-rs/core/src/context/world_state/environment_tests.rs b/codex-rs/core/src/context/world_state/environment_tests.rs
index 90af017bda..3aa43abfc2 100644
--- a/codex-rs/core/src/context/world_state/environment_tests.rs
+++ b/codex-rs/core/src/context/world_state/environment_tests.rs
@@ -1,18 +1,18 @@
+use super::super::PreviousSectionState;
+use super::super::test_support::render_section_cases;
use super::*;
-use crate::context::ContextualUserFragment;
-use crate::context::world_state::WorldState;
use anyhow::Result;
use codex_exec_server::LOCAL_ENVIRONMENT_ID;
-use codex_protocol::models::ContentItem;
use codex_protocol::models::PermissionProfile;
-use codex_protocol::models::ResponseItem;
use codex_protocol::permissions::NetworkSandboxPolicy;
-use pretty_assertions::assert_eq;
-use serde_json::json;
#[test]
-fn renders_full_environment_state() -> Result<()> {
- let context = EnvironmentsState {
+fn snapshots() -> Result<()> {
+ use PreviousSectionState::Absent;
+ use PreviousSectionState::Known;
+ use PreviousSectionState::Unknown;
+
+ let full = EnvironmentsState {
environments: [
("laptop".to_string(), available("file:///repo", "zsh")?),
(
@@ -24,34 +24,7 @@ fn renders_full_environment_state() -> Result<()> {
.collect(),
..Default::default()
};
-
- let mut world_state = WorldState::default();
- world_state.add_section(context);
-
- assert_eq!(
- vec![user_message(
- r#"
-
-
- /workspace
- bash
-
-
- /repo
- zsh
-
-
-"#,
- )],
- render_fragments(world_state.render_full()),
- );
- Ok(())
-}
-
-#[test]
-fn renders_only_changed_environments() -> Result<()> {
- let mut previous = WorldState::default();
- previous.add_section(EnvironmentsState {
+ let before_environment_changes = EnvironmentsState {
environments: [
("laptop".to_string(), available("file:///repo", "bash")?),
("devbox".to_string(), starting("file:///workspace")?),
@@ -60,9 +33,8 @@ fn renders_only_changed_environments() -> Result<()> {
.into_iter()
.collect(),
..Default::default()
- });
- let mut current = WorldState::default();
- current.add_section(EnvironmentsState {
+ };
+ let after_environment_changes = EnvironmentsState {
environments: [
("laptop".to_string(), available("file:///repo", "zsh")?),
(
@@ -74,35 +46,7 @@ fn renders_only_changed_environments() -> Result<()> {
.into_iter()
.collect(),
..Default::default()
- });
-
- assert_eq!(
- vec![user_message(
- r#"
-
-
- /workspace
- powershell
-
-
- /repo
- zsh
-
-
-
- /remote
- starting
-
-
-"#,
- )],
- render_fragments(current.render_diff(&previous.snapshot())),
- );
- Ok(())
-}
-
-#[test]
-fn persisted_turn_context_values_render_a_diff() -> Result<()> {
+ };
let environments = EnvironmentsState {
environments: [(
LOCAL_ENVIRONMENT_ID.to_string(),
@@ -112,8 +56,7 @@ fn persisted_turn_context_values_render_a_diff() -> Result<()> {
.collect(),
..Default::default()
};
- let mut previous = WorldState::default();
- previous.add_section(EnvironmentsState {
+ let before_turn_context_changes = EnvironmentsState {
current_date: Some("2026-06-19".to_string()),
timezone: Some("UTC".to_string()),
network: Some(NetworkContext::new(
@@ -126,9 +69,8 @@ fn persisted_turn_context_values_render_a_diff() -> Result<()> {
&[],
)),
..environments.clone()
- });
- let mut current = WorldState::default();
- current.add_section(EnvironmentsState {
+ };
+ let after_turn_context_changes = EnvironmentsState {
current_date: Some("2026-06-20".to_string()),
timezone: Some("America/Los_Angeles".to_string()),
network: Some(NetworkContext::new(
@@ -143,26 +85,8 @@ fn persisted_turn_context_values_render_a_diff() -> Result<()> {
&[],
)),
..environments
- });
-
- assert_eq!(
- vec![user_message(
- r#"
- 2026-06-20
- America/Los_Angeles
- new.example.comblocked.example.com
-
-"#,
- )],
- render_fragments(current.render_diff(&previous.snapshot())),
- );
- Ok(())
-}
-
-#[test]
-fn persisted_snapshot_uses_model_visible_path_and_context_values() -> Result<()> {
- let mut world_state = WorldState::default();
- world_state.add_section(EnvironmentsState {
+ };
+ let foreign_windows = EnvironmentsState {
environments: [(
"remote".to_string(),
available("file:///C:/windows", "powershell")?,
@@ -174,29 +98,8 @@ fn persisted_snapshot_uses_model_visible_path_and_context_values() -> Result<()>
&[],
)),
..Default::default()
- });
-
- assert_eq!(
- serde_json::to_value(world_state.snapshot())?,
- json!({
- "environments": {
- "environments": {
- "remote": {
- "cwd": "C:\\windows",
- "status": "available",
- "shell": "powershell"
- }
- },
- "filesystem": ""
- }
- }),
- );
- Ok(())
-}
-
-#[test]
-fn single_environment_diff_ignores_unknown_shell() -> Result<()> {
- let previous = EnvironmentsState {
+ };
+ let unknown_shell = EnvironmentsState {
environments: [(
LOCAL_ENVIRONMENT_ID.to_string(),
EnvironmentState {
@@ -209,7 +112,7 @@ fn single_environment_diff_ignores_unknown_shell() -> Result<()> {
.collect(),
..Default::default()
};
- let current = EnvironmentsState {
+ let known_shell = EnvironmentsState {
environments: [(
LOCAL_ENVIRONMENT_ID.to_string(),
available("file:///repo", "zsh")?,
@@ -218,21 +121,7 @@ fn single_environment_diff_ignores_unknown_shell() -> Result<()> {
.collect(),
..Default::default()
};
- let previous = WorldStateSection::snapshot(&previous);
-
- assert_eq!(
- None,
- render_fragment(WorldStateSection::render_diff(
- ¤t,
- PreviousSectionState::Known(&previous),
- ))
- );
- Ok(())
-}
-
-#[test]
-fn removed_legacy_environment_renders_unavailable() -> Result<()> {
- let previous = EnvironmentsState {
+ let legacy_environment = EnvironmentsState {
environments: [(
LOCAL_ENVIRONMENT_ID.to_string(),
available("file:///repo", "bash")?,
@@ -241,21 +130,24 @@ fn removed_legacy_environment_renders_unavailable() -> Result<()> {
.collect(),
..Default::default()
};
- let previous = WorldStateSection::snapshot(&previous);
+ let empty = EnvironmentsState::default();
- assert_eq!(
- Some(user_message(
- r#"
-
-
-
-"#,
- )),
- render_fragment(WorldStateSection::render_diff(
- &EnvironmentsState::default(),
- PreviousSectionState::Known(&previous),
- )),
- );
+ insta::assert_snapshot!(render_section_cases(&[
+ (Absent, Absent),
+ (Absent, Known(&full)),
+ (Unknown, Known(&full)),
+ (
+ Known(&before_environment_changes),
+ Known(&after_environment_changes),
+ ),
+ (
+ Known(&before_turn_context_changes),
+ Known(&after_turn_context_changes),
+ ),
+ (Absent, Known(&foreign_windows)),
+ (Known(&unknown_shell), Known(&known_shell)),
+ (Known(&legacy_environment), Known(&empty)),
+ ]));
Ok(())
}
@@ -274,26 +166,3 @@ fn starting(cwd: &str) -> Result {
shell: None,
})
}
-
-fn render_fragments(fragments: Vec>) -> Vec {
- fragments
- .into_iter()
- .map(ContextualUserFragment::into_boxed_response_item)
- .collect()
-}
-
-fn render_fragment(fragment: Option>) -> Option {
- fragment.map(ContextualUserFragment::into_boxed_response_item)
-}
-
-fn user_message(text: &str) -> ResponseItem {
- ResponseItem::Message {
- id: None,
- role: "user".to_string(),
- content: vec![ContentItem::InputText {
- text: text.to_string(),
- }],
- phase: None,
- internal_chat_message_metadata_passthrough: None,
- }
-}
diff --git a/codex-rs/core/src/context/world_state/mod.rs b/codex-rs/core/src/context/world_state/mod.rs
index ccdc976c4c..bb77c6f3da 100644
--- a/codex-rs/core/src/context/world_state/mod.rs
+++ b/codex-rs/core/src/context/world_state/mod.rs
@@ -2,6 +2,8 @@ mod agents_md;
mod apps_instructions;
mod environment;
mod plugins_instructions;
+#[cfg(test)]
+mod test_support;
use crate::context::ContextualUserFragment;
use codex_extension_api::PreviousWorldStateSection;
diff --git a/codex-rs/core/src/context/world_state/plugins_instructions_tests.rs b/codex-rs/core/src/context/world_state/plugins_instructions_tests.rs
index a688ff93ca..c9a5e1d251 100644
--- a/codex-rs/core/src/context/world_state/plugins_instructions_tests.rs
+++ b/codex-rs/core/src/context/world_state/plugins_instructions_tests.rs
@@ -1,43 +1,29 @@
use super::*;
use crate::context::ContextualUserFragment;
+use crate::context::world_state::PreviousSectionState;
+use crate::context::world_state::test_support::render_section_cases;
use codex_protocol::models::ResponseItem;
use pretty_assertions::assert_eq;
-fn render(
- state: PluginsInstructionsState,
- previous: PreviousSectionState<'_, bool>,
-) -> Vec {
- state
- .render_diff(previous)
- .into_iter()
- .map(|fragment| fragment.render())
- .collect()
-}
-
#[test]
-fn renders_only_when_plugins_become_available() {
+fn snapshots() {
+ use PreviousSectionState::Absent;
+ use PreviousSectionState::Known;
+ use PreviousSectionState::Unknown;
+
let unavailable = PluginsInstructionsState::new(/*available*/ false);
let available = PluginsInstructionsState::new(/*available*/ true);
- let false_snapshot = false;
- let true_snapshot = true;
- assert_eq!(
- render(unavailable, PreviousSectionState::Absent),
- Vec::::new()
- );
- assert_eq!(render(available, PreviousSectionState::Absent).len(), 1);
- assert_eq!(
- render(available, PreviousSectionState::Known(&false_snapshot)).len(),
- 1
- );
- assert_eq!(
- render(available, PreviousSectionState::Known(&true_snapshot)),
- Vec::::new()
- );
- assert_eq!(
- render(unavailable, PreviousSectionState::Known(&true_snapshot)),
- Vec::::new()
- );
+ insta::assert_snapshot!(render_section_cases(&[
+ (Absent, Absent),
+ (Absent, Known(&unavailable)),
+ (Absent, Known(&available)),
+ (Known(&unavailable), Known(&available)),
+ (Known(&available), Known(&available)),
+ (Known(&available), Known(&unavailable)),
+ (Unknown, Known(&unavailable)),
+ (Unknown, Known(&available)),
+ ]));
}
#[test]
diff --git a/codex-rs/core/src/context/world_state/snapshots/codex_core__context__world_state__agents_md__tests__snapshots.snap b/codex-rs/core/src/context/world_state/snapshots/codex_core__context__world_state__agents_md__tests__snapshots.snap
new file mode 100644
index 0000000000..8aefb78ed7
--- /dev/null
+++ b/codex-rs/core/src/context/world_state/snapshots/codex_core__context__world_state__agents_md__tests__snapshots.snap
@@ -0,0 +1,51 @@
+---
+source: core/src/context/world_state/agents_md_tests.rs
+expression: "render_section_cases(&[(Absent, Absent), (Absent, Known(&empty)),\n(Absent, Known(&project_formatter)),\n(Known(&project_formatter), Known(&project_formatter)),\n(Known(&old), Known(&new)), (Known(&new), Known(&empty)),\n(Unknown, Known(&new)), (Unknown, Known(&empty)),])"
+---
+Absent -> Absent
+None
+
+Absent -> {}
+None
+
+Absent -> {"text":"use the project formatter"} (role - user)
+# AGENTS.md instructions
+
+
+use the project formatter
+
+
+{"text":"use the project formatter"} -> {"text":"use the project formatter"}
+None
+
+{"text":"old instructions"} -> {"text":"new instructions"} (role - user)
+# AGENTS.md instructions
+
+
+These AGENTS.md instructions replace all previously provided AGENTS.md instructions.
+
+new instructions
+
+
+{"text":"new instructions"} -> {} (role - user)
+# AGENTS.md instructions
+
+
+The previously provided AGENTS.md instructions no longer apply.
+
+
+Unknown -> {"text":"new instructions"} (role - user)
+# AGENTS.md instructions
+
+
+These AGENTS.md instructions replace all previously provided AGENTS.md instructions.
+
+new instructions
+
+
+Unknown -> {} (role - user)
+# AGENTS.md instructions
+
+
+The previously provided AGENTS.md instructions no longer apply.
+
diff --git a/codex-rs/core/src/context/world_state/snapshots/codex_core__context__world_state__apps_instructions__tests__snapshots.snap b/codex-rs/core/src/context/world_state/snapshots/codex_core__context__world_state__apps_instructions__tests__snapshots.snap
new file mode 100644
index 0000000000..05e3a1c2cb
--- /dev/null
+++ b/codex-rs/core/src/context/world_state/snapshots/codex_core__context__world_state__apps_instructions__tests__snapshots.snap
@@ -0,0 +1,39 @@
+---
+source: core/src/context/world_state/apps_instructions_tests.rs
+expression: "render_section_cases(&[(Absent, Absent), (Absent, Known(&unavailable)),\n(Absent, Known(&available)), (Known(&unavailable), Known(&available)),\n(Known(&available), Known(&available)),\n(Known(&available), Known(&unavailable)), (Unknown, Known(&unavailable)),\n(Unknown, Known(&available)),])"
+---
+Absent -> Absent
+None
+
+Absent -> false
+None
+
+Absent -> true (role - developer)
+
+## Apps (Connectors)
+Apps (Connectors) can be explicitly triggered in user messages in the format `[$app-name](app://{connector_id})`. Apps can also be implicitly triggered as long as the context suggests usage of available apps.
+An app is equivalent to a set of MCP tools within the `codex_apps` MCP.
+An installed app's MCP tools are either provided to you already, or can be lazy-loaded through the `tool_search` tool. If `tool_search` is available, the apps that are searchable by `tools_search` will be listed by it.
+Do not additionally call list_mcp_resources or list_mcp_resource_templates for apps.
+
+
+false -> true (role - developer)
+
+## Apps (Connectors)
+Apps (Connectors) can be explicitly triggered in user messages in the format `[$app-name](app://{connector_id})`. Apps can also be implicitly triggered as long as the context suggests usage of available apps.
+An app is equivalent to a set of MCP tools within the `codex_apps` MCP.
+An installed app's MCP tools are either provided to you already, or can be lazy-loaded through the `tool_search` tool. If `tool_search` is available, the apps that are searchable by `tools_search` will be listed by it.
+Do not additionally call list_mcp_resources or list_mcp_resource_templates for apps.
+
+
+true -> true
+None
+
+true -> false
+None
+
+Unknown -> false
+None
+
+Unknown -> true
+None
diff --git a/codex-rs/core/src/context/world_state/snapshots/codex_core__context__world_state__environment__tests__snapshots.snap b/codex-rs/core/src/context/world_state/snapshots/codex_core__context__world_state__environment__tests__snapshots.snap
new file mode 100644
index 0000000000..60e5ba315c
--- /dev/null
+++ b/codex-rs/core/src/context/world_state/snapshots/codex_core__context__world_state__environment__tests__snapshots.snap
@@ -0,0 +1,78 @@
+---
+source: core/src/context/world_state/environment_tests.rs
+expression: "render_section_cases(&[(Absent, Absent), (Absent, Known(&full)),\n(Unknown, Known(&full)),\n(Known(&before_environment_changes), Known(&after_environment_changes),),\n(Known(&before_turn_context_changes), Known(&after_turn_context_changes),),\n(Absent, Known(&foreign_windows)),\n(Known(&unknown_shell), Known(&known_shell)),\n(Known(&legacy_environment), Known(&empty)),])"
+---
+Absent -> Absent
+None
+
+Absent -> {"environments":{"devbox":{"cwd":"/workspace","shell":"bash","status":"available"},"laptop":{"cwd":"/repo","shell":"zsh","status":"available"}}} (role - user)
+
+
+
+ /workspace
+ bash
+
+
+ /repo
+ zsh
+
+
+
+
+Unknown -> {"environments":{"devbox":{"cwd":"/workspace","shell":"bash","status":"available"},"laptop":{"cwd":"/repo","shell":"zsh","status":"available"}}} (role - user)
+
+
+
+ /workspace
+ bash
+
+
+ /repo
+ zsh
+
+
+
+
+{"environments":{"devbox":{"cwd":"/workspace","status":"starting"},"laptop":{"cwd":"/repo","shell":"bash","status":"available"},"old":{"cwd":"/old","shell":"sh","status":"available"}}} -> {"environments":{"devbox":{"cwd":"/workspace","shell":"powershell","status":"available"},"laptop":{"cwd":"/repo","shell":"zsh","status":"available"},"remote":{"cwd":"/remote","status":"starting"}}} (role - user)
+
+
+
+ /workspace
+ powershell
+
+
+ /repo
+ zsh
+
+
+
+ /remote
+ starting
+
+
+
+
+{"current_date":"2026-06-19","environments":{"local":{"cwd":"/repo","shell":"zsh","status":"available"}},"filesystem":"","network":"old.example.com","timezone":"UTC"} -> {"current_date":"2026-06-20","environments":{"local":{"cwd":"/repo","shell":"zsh","status":"available"}},"filesystem":"","network":"new.example.comblocked.example.com","timezone":"America/Los_Angeles"} (role - user)
+
+ 2026-06-20
+ America/Los_Angeles
+ new.example.comblocked.example.com
+
+
+
+Absent -> {"environments":{"remote":{"cwd":"C:\\windows","shell":"powershell","status":"available"}},"filesystem":""} (role - user)
+
+ C:\windows
+ powershell
+
+
+
+{"environments":{"local":{"cwd":"/repo","status":"available"}}} -> {"environments":{"local":{"cwd":"/repo","shell":"zsh","status":"available"}}}
+None
+
+{"environments":{"local":{"cwd":"/repo","shell":"bash","status":"available"}}} -> {"environments":{}} (role - user)
+
+
+
+
+
diff --git a/codex-rs/core/src/context/world_state/snapshots/codex_core__context__world_state__plugins_instructions__tests__snapshots.snap b/codex-rs/core/src/context/world_state/snapshots/codex_core__context__world_state__plugins_instructions__tests__snapshots.snap
new file mode 100644
index 0000000000..851917d2bd
--- /dev/null
+++ b/codex-rs/core/src/context/world_state/snapshots/codex_core__context__world_state__plugins_instructions__tests__snapshots.snap
@@ -0,0 +1,47 @@
+---
+source: core/src/context/world_state/plugins_instructions_tests.rs
+expression: "render_section_cases(&[(Absent, Absent), (Absent, Known(&unavailable)),\n(Absent, Known(&available)), (Known(&unavailable), Known(&available)),\n(Known(&available), Known(&available)),\n(Known(&available), Known(&unavailable)), (Unknown, Known(&unavailable)),\n(Unknown, Known(&available)),])"
+---
+Absent -> Absent
+None
+
+Absent -> false
+None
+
+Absent -> true (role - developer)
+
+## Plugins
+A plugin is a local bundle of skills, MCP servers, and apps.
+### How to use plugins
+- 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-associated skills, MCP tools, and apps exposed elsewhere in this turn.
+- Missing/blocked: If the user requests a plugin that does not have relevant callable capabilities for the task, say so briefly and continue with the best fallback.
+
+
+false -> true (role - developer)
+
+## Plugins
+A plugin is a local bundle of skills, MCP servers, and apps.
+### How to use plugins
+- 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-associated skills, MCP tools, and apps exposed elsewhere in this turn.
+- Missing/blocked: If the user requests a plugin that does not have relevant callable capabilities for the task, say so briefly and continue with the best fallback.
+
+
+true -> true
+None
+
+true -> false
+None
+
+Unknown -> false
+None
+
+Unknown -> true
+None
diff --git a/codex-rs/core/src/context/world_state/test_support.rs b/codex-rs/core/src/context/world_state/test_support.rs
new file mode 100644
index 0000000000..62bbae4d5b
--- /dev/null
+++ b/codex-rs/core/src/context/world_state/test_support.rs
@@ -0,0 +1,83 @@
+use super::ErasedWorldStateSection;
+use super::PreviousSectionState;
+use super::WorldStateSection;
+use crate::context::ContextualUserFragment;
+
+pub(super) fn render_section_cases<'a, S: WorldStateSection>(
+ cases: &[(PreviousSectionState<'a, S>, PreviousSectionState<'a, S>)],
+) -> String {
+ cases
+ .iter()
+ .map(|(before, after)| {
+ let rendered = render_diff(before, after);
+ let role = rendered.as_ref().map_or_else(String::new, |fragment| {
+ format!(" (role - {})", fragment.role())
+ });
+ let content = rendered
+ .as_ref()
+ .map_or_else(|| "None".to_string(), |fragment| fragment.render());
+ format!(
+ "{} -> {}{role}\n{content}",
+ render_state(before),
+ render_state(after),
+ )
+ })
+ .collect::>()
+ .join("\n\n")
+}
+
+fn render_state(state: &PreviousSectionState<'_, S>) -> String {
+ match state {
+ PreviousSectionState::Absent => "Absent".to_string(),
+ PreviousSectionState::Unknown => "Unknown".to_string(),
+ PreviousSectionState::Known(section) => render_snapshot(*section),
+ }
+}
+
+fn render_diff(
+ before: &PreviousSectionState<'_, S>,
+ after: &PreviousSectionState<'_, S>,
+) -> Option> {
+ let PreviousSectionState::Known(after) = after else {
+ return None;
+ };
+ let previous_snapshot;
+ let previous = match before {
+ PreviousSectionState::Absent => PreviousSectionState::Absent,
+ PreviousSectionState::Unknown => PreviousSectionState::Unknown,
+ PreviousSectionState::Known(before) => {
+ previous_snapshot = snapshot_value(*before);
+ PreviousSectionState::Known(&previous_snapshot)
+ }
+ };
+ ErasedWorldStateSection::render_diff(*after, previous)
+}
+
+fn render_snapshot(section: &S) -> String {
+ serde_json::to_string(&sort_json(snapshot_value(section)))
+ .expect("world-state section snapshot should serialize")
+}
+
+fn sort_json(value: serde_json::Value) -> serde_json::Value {
+ match value {
+ serde_json::Value::Array(values) => {
+ serde_json::Value::Array(values.into_iter().map(sort_json).collect())
+ }
+ serde_json::Value::Object(values) => {
+ let mut values = values.into_iter().collect::>();
+ values.sort_by(|(left, _), (right, _)| left.cmp(right));
+ serde_json::Value::Object(
+ values
+ .into_iter()
+ .map(|(key, value)| (key, sort_json(value)))
+ .collect(),
+ )
+ }
+ value => value,
+ }
+}
+
+fn snapshot_value(section: &S) -> serde_json::Value {
+ ErasedWorldStateSection::snapshot(section)
+ .expect("world-state section snapshot should serialize to a non-null value")
+}