mirror of
https://github.com/openai/codex.git
synced 2026-11-01 19:33:35 +00:00
guardian: move denial tracking into turn extension state
This commit is contained in:
@@ -7,7 +7,6 @@ version.workspace = true
|
||||
[lib]
|
||||
name = "codex_guardian"
|
||||
path = "src/lib.rs"
|
||||
test = false
|
||||
doctest = false
|
||||
|
||||
[lints]
|
||||
@@ -22,3 +21,6 @@ codex-utils-absolute-path = { workspace = true }
|
||||
codex-utils-output-truncation = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
pub const MAX_CONSECUTIVE_GUARDIAN_DENIALS_PER_TURN: u32 = 3;
|
||||
pub const MAX_RECENT_AUTO_REVIEW_DENIALS_PER_TURN: u32 = 10;
|
||||
pub const AUTO_REVIEW_DENIAL_WINDOW_SIZE: usize = 50;
|
||||
|
||||
/// Per-turn denial history used to stop repeated unsafe approval requests.
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
pub struct GuardianRejectionCircuitBreaker {
|
||||
consecutive_denials: u32,
|
||||
recent_denials: VecDeque<bool>,
|
||||
interrupt_triggered: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum GuardianRejectionCircuitBreakerAction {
|
||||
Continue,
|
||||
InterruptTurn {
|
||||
consecutive_denials: u32,
|
||||
recent_denials: u32,
|
||||
},
|
||||
}
|
||||
|
||||
impl GuardianRejectionCircuitBreaker {
|
||||
pub fn record_denial(&mut self) -> GuardianRejectionCircuitBreakerAction {
|
||||
self.consecutive_denials = self.consecutive_denials.saturating_add(1);
|
||||
self.record_recent_review(/*denied*/ true);
|
||||
let recent_denials = self.recent_denials.iter().filter(|denied| **denied).count() as u32;
|
||||
if !self.interrupt_triggered
|
||||
&& (self.consecutive_denials >= MAX_CONSECUTIVE_GUARDIAN_DENIALS_PER_TURN
|
||||
|| recent_denials >= MAX_RECENT_AUTO_REVIEW_DENIALS_PER_TURN)
|
||||
{
|
||||
self.interrupt_triggered = true;
|
||||
GuardianRejectionCircuitBreakerAction::InterruptTurn {
|
||||
consecutive_denials: self.consecutive_denials,
|
||||
recent_denials,
|
||||
}
|
||||
} else {
|
||||
GuardianRejectionCircuitBreakerAction::Continue
|
||||
}
|
||||
}
|
||||
|
||||
pub fn record_non_denial(&mut self) {
|
||||
self.consecutive_denials = 0;
|
||||
self.record_recent_review(/*denied*/ false);
|
||||
}
|
||||
|
||||
fn record_recent_review(&mut self, denied: bool) {
|
||||
self.recent_denials.push_back(denied);
|
||||
if self.recent_denials.len() > AUTO_REVIEW_DENIAL_WINDOW_SIZE {
|
||||
self.recent_denials.pop_front();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "circuit_breaker_tests.rs"]
|
||||
mod tests;
|
||||
@@ -0,0 +1,100 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn interrupts_once_after_three_consecutive_denials() {
|
||||
let mut circuit_breaker = GuardianRejectionCircuitBreaker::default();
|
||||
assert_eq!(
|
||||
circuit_breaker.record_denial(),
|
||||
GuardianRejectionCircuitBreakerAction::Continue
|
||||
);
|
||||
assert_eq!(
|
||||
circuit_breaker.record_denial(),
|
||||
GuardianRejectionCircuitBreakerAction::Continue
|
||||
);
|
||||
assert_eq!(
|
||||
circuit_breaker.record_denial(),
|
||||
GuardianRejectionCircuitBreakerAction::InterruptTurn {
|
||||
consecutive_denials: 3,
|
||||
recent_denials: 3,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
circuit_breaker.record_denial(),
|
||||
GuardianRejectionCircuitBreakerAction::Continue
|
||||
);
|
||||
assert_eq!(
|
||||
circuit_breaker,
|
||||
GuardianRejectionCircuitBreaker {
|
||||
consecutive_denials: 4,
|
||||
recent_denials: VecDeque::from([true, true, true, true]),
|
||||
interrupt_triggered: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_denial_resets_consecutive_denials() {
|
||||
let mut circuit_breaker = GuardianRejectionCircuitBreaker::default();
|
||||
assert_eq!(
|
||||
circuit_breaker.record_denial(),
|
||||
GuardianRejectionCircuitBreakerAction::Continue
|
||||
);
|
||||
circuit_breaker.record_non_denial();
|
||||
assert_eq!(
|
||||
circuit_breaker.record_denial(),
|
||||
GuardianRejectionCircuitBreakerAction::Continue
|
||||
);
|
||||
assert_eq!(
|
||||
circuit_breaker.record_denial(),
|
||||
GuardianRejectionCircuitBreakerAction::Continue
|
||||
);
|
||||
assert_eq!(
|
||||
circuit_breaker.record_denial(),
|
||||
GuardianRejectionCircuitBreakerAction::InterruptTurn {
|
||||
consecutive_denials: 3,
|
||||
recent_denials: 4,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn interrupts_after_ten_recent_denials() {
|
||||
let mut circuit_breaker = GuardianRejectionCircuitBreaker::default();
|
||||
for _ in 0..9 {
|
||||
assert_eq!(
|
||||
circuit_breaker.record_denial(),
|
||||
GuardianRejectionCircuitBreakerAction::Continue
|
||||
);
|
||||
circuit_breaker.record_non_denial();
|
||||
}
|
||||
assert_eq!(
|
||||
circuit_breaker.record_denial(),
|
||||
GuardianRejectionCircuitBreakerAction::InterruptTurn {
|
||||
consecutive_denials: 1,
|
||||
recent_denials: 10,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn forgets_denials_outside_recent_review_window() {
|
||||
let mut circuit_breaker = GuardianRejectionCircuitBreaker::default();
|
||||
for _ in 0..9 {
|
||||
assert_eq!(
|
||||
circuit_breaker.record_denial(),
|
||||
GuardianRejectionCircuitBreakerAction::Continue
|
||||
);
|
||||
circuit_breaker.record_non_denial();
|
||||
}
|
||||
for _ in 0..(AUTO_REVIEW_DENIAL_WINDOW_SIZE - 18) {
|
||||
circuit_breaker.record_non_denial();
|
||||
}
|
||||
assert_eq!(
|
||||
circuit_breaker.record_denial(),
|
||||
GuardianRejectionCircuitBreakerAction::Continue
|
||||
);
|
||||
}
|
||||
@@ -14,6 +14,7 @@ use codex_protocol::config_types::ApprovalsReviewer;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
|
||||
mod approval_request;
|
||||
mod circuit_breaker;
|
||||
mod truncation;
|
||||
|
||||
pub use approval_request::FormattedGuardianAction;
|
||||
@@ -23,6 +24,11 @@ pub use approval_request::guardian_assessment_action;
|
||||
pub use approval_request::guardian_request_target_item_id;
|
||||
pub use approval_request::guardian_request_turn_id;
|
||||
pub use approval_request::guardian_reviewed_action;
|
||||
pub use circuit_breaker::AUTO_REVIEW_DENIAL_WINDOW_SIZE;
|
||||
pub use circuit_breaker::GuardianRejectionCircuitBreaker;
|
||||
pub use circuit_breaker::GuardianRejectionCircuitBreakerAction;
|
||||
pub use circuit_breaker::MAX_CONSECUTIVE_GUARDIAN_DENIALS_PER_TURN;
|
||||
pub use circuit_breaker::MAX_RECENT_AUTO_REVIEW_DENIALS_PER_TURN;
|
||||
pub use truncation::guardian_truncate_text;
|
||||
|
||||
/// Guardian extension dependencies supplied by the host at construction time.
|
||||
|
||||
Reference in New Issue
Block a user