From 60821c50431c600074a2af6d943de76164c51638 Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Mon, 24 Aug 2026 15:34:15 +0000 Subject: [PATCH] Represent Guardian policy as a contextual fragment (#40420) ## What changed - Add a `GuardianPolicy` contextual fragment that renders as a standalone developer message with the `guardian.policy` content kind. - Use the fragment when rebuilding Guardian context and verify that its content kind remains attached after history compaction. GitOrigin-RevId: f7028fb29a48ad2453437f3d3a4405fcab7a2fd7 --- codex-rs/core/src/context/guardian_policy.rs | 42 +++++++++++++++++++ codex-rs/core/src/context/mod.rs | 2 + codex-rs/core/src/session/mod.rs | 10 +---- .../core/src/session/tests/guardian_tests.rs | 19 +++++++-- 4 files changed, 62 insertions(+), 11 deletions(-) create mode 100644 codex-rs/core/src/context/guardian_policy.rs diff --git a/codex-rs/core/src/context/guardian_policy.rs b/codex-rs/core/src/context/guardian_policy.rs new file mode 100644 index 0000000000..6476c5a904 --- /dev/null +++ b/codex-rs/core/src/context/guardian_policy.rs @@ -0,0 +1,42 @@ +use super::ContextualUserFragment; +use codex_protocol::models::ContentItemKind; + +/// The isolated developer policy provided to a Guardian reviewer. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct GuardianPolicy { + policy: String, +} + +impl GuardianPolicy { + pub(crate) fn new(policy: impl Into) -> Self { + Self { + policy: policy.into(), + } + } +} + +impl ContextualUserFragment for GuardianPolicy { + fn content_kind(&self) -> ContentItemKind { + ContentItemKind("guardian.policy".to_string()) + } + + fn role(&self) -> &'static str { + "developer" + } + + fn requires_separate_message(&self) -> bool { + true + } + + fn markers(&self) -> (&'static str, &'static str) { + Self::type_markers() + } + + fn type_markers() -> (&'static str, &'static str) { + ("", "") + } + + fn body(&self) -> String { + self.policy.clone() + } +} diff --git a/codex-rs/core/src/context/mod.rs b/codex-rs/core/src/context/mod.rs index 75900e55f4..16f9002ee3 100644 --- a/codex-rs/core/src/context/mod.rs +++ b/codex-rs/core/src/context/mod.rs @@ -13,6 +13,7 @@ mod environments_instructions; mod guardian_approved_action; mod guardian_followup_review_reminder; mod guardian_node_repl_policy; +mod guardian_policy; mod guardian_review_evidence; mod hook_additional_context; mod image_resize_notice; @@ -62,6 +63,7 @@ pub(crate) use environments_instructions::EnvironmentsInstructions; pub(crate) use guardian_approved_action::GuardianApprovedAction; pub(crate) use guardian_followup_review_reminder::GuardianFollowupReviewReminder; pub(crate) use guardian_node_repl_policy::GuardianNodeReplPolicy; +pub(crate) use guardian_policy::GuardianPolicy; pub use guardian_review_evidence::GuardianReviewEvidence; pub use guardian_review_evidence::GuardianReviewEvidenceFragment; pub(crate) use hook_additional_context::HookAdditionalContext; diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs index b64817041c..ba3a2ab738 100644 --- a/codex-rs/core/src/session/mod.rs +++ b/codex-rs/core/src/session/mod.rs @@ -22,6 +22,7 @@ use crate::config::ManagedFeatures; use crate::config::resolve_tool_suggest_config_from_layer_stack; use crate::context::ContextualUserFragment; use crate::context::DeveloperInstructions; +use crate::context::GuardianPolicy; use crate::context::ManagedDeveloperInstructions; use crate::context::ModelSwitchInstructions; use crate::context::MultiAgentRoleInstructions; @@ -57,7 +58,6 @@ use codex_analytics::SubAgentThreadStartedInput; use codex_analytics::TurnCodexErrorFact; use codex_async_utils::OrCancelExt; use codex_connectors::connector_runtime_context_key; -use codex_context_fragments::AnnotatedContent; use codex_context_fragments::RenderedFragment; use codex_exec_server::Environment; use codex_exec_server::EnvironmentManager; @@ -3719,13 +3719,7 @@ impl Session { && !developer_instructions.is_empty() && let Some(guardian_developer_message) = crate::context_manager::updates::build_rendered_message(vec![ - RenderedFragment::new( - "developer", - AnnotatedContent::input_text( - developer_instructions, - ContentItemKind("guardian.policy".to_string()), - ), - ), + GuardianPolicy::new(developer_instructions).render_fragment(), ]) { items.push(guardian_developer_message); diff --git a/codex-rs/core/src/session/tests/guardian_tests.rs b/codex-rs/core/src/session/tests/guardian_tests.rs index 39befb985f..c982a0a128 100644 --- a/codex-rs/core/src/session/tests/guardian_tests.rs +++ b/codex-rs/core/src/session/tests/guardian_tests.rs @@ -23,6 +23,7 @@ use codex_model_provider::create_model_provider; use codex_protocol::config_types::ApprovalsReviewer; use codex_protocol::models::AdditionalPermissionProfile as PermissionProfile; use codex_protocol::models::ContentItem; +use codex_protocol::models::ContentItemKind; use codex_protocol::models::NetworkPermissions; use codex_protocol::models::ResponseInputItem; use codex_protocol::models::ResponseItem; @@ -640,7 +641,13 @@ async fn process_compacted_history_preserves_separate_guardian_developer_message .iter() .filter_map(|item| match item { ResponseItem::Message { role, content, .. } if role == "developer" => { - crate::content_items_to_text(content) + crate::content_items_to_text(content).map(|text| { + ( + text, + item.executed_tool_call_metadata() + .and_then(|metadata| metadata.content_item_kinds.clone()), + ) + }) } _ => None, }) @@ -649,10 +656,16 @@ async fn process_compacted_history_preserves_separate_guardian_developer_message assert!( !developer_messages .iter() - .any(|message| message.contains("stale developer message")) + .any(|(message, _)| message.contains("stale developer message")) ); assert!(developer_messages.len() >= 2); - assert_eq!(developer_messages.last(), Some(&guardian_policy)); + assert_eq!( + developer_messages.last(), + Some(&( + guardian_policy, + Some(vec![ContentItemKind("guardian.policy".to_string())]), + )) + ); } #[tokio::test]