Codex Handbook
core/src/guardian/mod.rs 176 lines
//! Guardian review decides whether an `on-request` approval should be granted//! automatically instead of shown to the user.//!//! High-level approach://! 1. Reconstruct a compact transcript that preserves user intent plus the most//!    relevant recent assistant and tool context.//! 2. Ask a dedicated guardian review session to assess the exact planned//!    action and return strict JSON.//!    The guardian clones the parent config, so it inherits any managed//!    network proxy / allowlist that the parent turn already had.//! 3. Fail closed on timeout, execution failure, or malformed output.//! 4. Apply the guardian's explicit allow/deny outcome.mod approval_request;mod metrics;mod prompt;mod review;mod review_session;use std::time::Duration;use codex_protocol::protocol::GuardianAssessmentDecisionSource;use codex_protocol::protocol::GuardianAssessmentOutcome;use serde::Deserialize;use serde::Serialize;pub(crate) use approval_request::GuardianApprovalRequest;pub(crate) use approval_request::GuardianMcpAnnotations;pub(crate) use approval_request::GuardianNetworkAccessTrigger;#[cfg(test)]pub(crate) use approval_request::guardian_approval_request_to_json;pub(crate) use review::guardian_rejection_message;pub(crate) use review::guardian_timeout_message;pub(crate) use review::is_guardian_reviewer_source;pub(crate) use review::new_guardian_review_id;#[cfg(test)]pub(crate) use review::record_guardian_denial_for_test;pub(crate) use review::review_approval_request;#[cfg(test)]pub(crate) use review::review_approval_request_with_cancel;pub(crate) use review::routes_approval_to_guardian;pub(crate) use review::routes_approval_to_guardian_with_reviewer;pub(crate) use review::spawn_approval_request_review;pub(crate) use review_session::GuardianReviewSessionManager;pub(crate) use review_session::prompt_cache_key_override_for_review_session;pub(crate) const GUARDIAN_REVIEW_TIMEOUT: Duration = Duration::from_secs(90);pub(crate) const GUARDIAN_REVIEWER_NAME: &str = "guardian";pub(crate) const MAX_CONSECUTIVE_GUARDIAN_DENIALS_PER_TURN: u32 = 3;pub(crate) const MAX_RECENT_AUTO_REVIEW_DENIALS_PER_TURN: u32 = 10;pub(crate) const AUTO_REVIEW_DENIAL_WINDOW_SIZE: usize = 50;pub(crate) const AUTO_REVIEW_DENIED_ACTION_APPROVAL_DEVELOPER_PREFIX: &str =    "The user has manually approved a specific action that was previously `Rejected`.";const GUARDIAN_MAX_MESSAGE_TRANSCRIPT_TOKENS: usize = 10_000;const GUARDIAN_MAX_TOOL_TRANSCRIPT_TOKENS: usize = 10_000;const GUARDIAN_MAX_MESSAGE_ENTRY_TOKENS: usize = 2_000;const GUARDIAN_MAX_TOOL_ENTRY_TOKENS: usize = 1_000;const GUARDIAN_MAX_ACTION_STRING_TOKENS: usize = 16_000;const GUARDIAN_RECENT_ENTRY_LIMIT: usize = 40;const TRUNCATION_TAG: &str = "truncated";/// Structured output contract that the guardian reviewer must satisfy.#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]pub(crate) struct GuardianAssessment {    pub(crate) risk_level: codex_protocol::protocol::GuardianRiskLevel,    pub(crate) user_authorization: codex_protocol::protocol::GuardianUserAuthorization,    pub(crate) outcome: GuardianAssessmentOutcome,    pub(crate) rationale: String,}#[derive(Debug, Clone, PartialEq, Eq)]pub(crate) struct GuardianRejection {    pub(crate) rationale: String,    pub(crate) source: GuardianAssessmentDecisionSource,}#[derive(Debug, Default)]pub(crate) struct GuardianRejectionCircuitBreaker {    turns: std::collections::HashMap<String, GuardianRejectionCircuitBreakerTurn>,}#[derive(Debug, Default)]struct GuardianRejectionCircuitBreakerTurn {    consecutive_denials: u32,    recent_denials: std::collections::VecDeque<bool>,    interrupt_triggered: bool,}#[derive(Clone, Copy, Debug, PartialEq, Eq)]pub(crate) enum GuardianRejectionCircuitBreakerAction {    Continue,    InterruptTurn {        consecutive_denials: u32,        recent_denials: u32,    },}impl GuardianRejectionCircuitBreaker {    pub(crate) fn clear_turn(&mut self, turn_id: &str) {        self.turns.remove(turn_id);    }    pub(crate) fn record_denial(&mut self, turn_id: &str) -> GuardianRejectionCircuitBreakerAction {        let turn = self.turns.entry(turn_id.to_string()).or_default();        turn.consecutive_denials = turn.consecutive_denials.saturating_add(1);        Self::record_recent_review(turn, /*denied*/ true);        let recent_denials = turn.recent_denials.iter().filter(|denied| **denied).count() as u32;        if !turn.interrupt_triggered            && (turn.consecutive_denials >= MAX_CONSECUTIVE_GUARDIAN_DENIALS_PER_TURN                || recent_denials >= MAX_RECENT_AUTO_REVIEW_DENIALS_PER_TURN)        {            turn.interrupt_triggered = true;            GuardianRejectionCircuitBreakerAction::InterruptTurn {                consecutive_denials: turn.consecutive_denials,                recent_denials,            }        } else {            GuardianRejectionCircuitBreakerAction::Continue        }    }    pub(crate) fn record_non_denial(&mut self, turn_id: &str) {        let turn = self.turns.entry(turn_id.to_string()).or_default();        turn.consecutive_denials = 0;        Self::record_recent_review(turn, /*denied*/ false);    }    fn record_recent_review(turn: &mut GuardianRejectionCircuitBreakerTurn, denied: bool) {        turn.recent_denials.push_back(denied);        if turn.recent_denials.len() > AUTO_REVIEW_DENIAL_WINDOW_SIZE {            turn.recent_denials.pop_front();        }    }}#[cfg(test)]use approval_request::format_guardian_action_pretty;#[cfg(test)]use approval_request::guardian_assessment_action;#[cfg(test)]use approval_request::guardian_request_turn_id;#[cfg(test)]use prompt::GuardianPromptMode;#[cfg(test)]use prompt::GuardianTranscriptCursor;#[cfg(test)]use prompt::GuardianTranscriptEntry;#[cfg(test)]use prompt::GuardianTranscriptEntryKind;#[cfg(test)]use prompt::build_guardian_prompt_items;#[cfg(test)]use prompt::build_guardian_prompt_items_with_parent_turn;#[cfg(test)]use prompt::collect_guardian_transcript_entries;#[cfg(test)]use prompt::guardian_output_schema;#[cfg(test)]pub(crate) use prompt::guardian_policy_prompt;#[cfg(test)]pub(crate) use prompt::guardian_policy_prompt_with_config;#[cfg(test)]use prompt::guardian_truncate_text;#[cfg(test)]use prompt::parse_guardian_assessment;#[cfg(test)]use prompt::render_guardian_transcript_entries;#[cfg(test)]use review::GuardianReviewOutcome;#[cfg(test)]use review::run_guardian_review_session_with_retry as run_guardian_review_session_for_test;#[cfg(test)]use review_session::build_guardian_review_session_config as build_guardian_review_session_config_for_test;#[cfg(test)]mod tests;