mirror of
https://github.com/openai/codex.git
synced 2026-10-29 19:08:43 +00:00
## Description
This PR changes guardian transcript compaction so oversized
conversations no longer collapse into a nearly empty placeholder.
Before this change, if the retained user history alone exceeded the
message budget, guardian would replace the entire transcript with
`<transcript omitted to preserve budget for planned action>`!
That meant approvals, especially network approvals, could lose the
recent tool call and tool result that explained what guardian was
actually reviewing. Now we keep a compact but usable transcript instead
of dropping it all.
### Before
```
The following is the Codex agent history whose request action you are assessing...
>>> TRANSCRIPT START
<transcript omitted to preserve budget for planned action>
>>> TRANSCRIPT END
Conversation transcript omitted due to size.
The Codex agent has requested the following action:
>>> APPROVAL REQUEST START
Retry reason:
Sandbox blocked outbound network access.
Assess the exact planned action below. Use read-only tool checks when local state matters.
Planned action JSON:
{
"tool": "network_access",
"target": "https://example.com:443",
"host": "example.com",
"protocol": "https",
"port": 443
}
>>> APPROVAL REQUEST END
```
### After
```
The following is the Codex agent history whose request action you are assessing...
>>> TRANSCRIPT START
[1] user: Please investigate why uploads to example.com are failing and retry if needed.
[8] user: If the request looks correct, go ahead and try again with network access.
[9] tool shell call: {"command":["curl","-X","POST","https://example.com/upload"],"cwd":"/repo"}
[10] tool shell result: sandbox blocked outbound network access
>>> TRANSCRIPT END
Some conversation entries were omitted.
The Codex agent has requested the following action:
>>> APPROVAL REQUEST START
Retry reason:
Sandbox blocked outbound network access.
Assess the exact planned action below. Use read-only tool checks when local state matters.
Planned action JSON:
{
"tool": "network_access",
"target": "https://example.com:443",
"host": "example.com",
"protocol": "https",
"port": 443
}
>>> APPROVAL REQUEST END
```
95 lines
3.4 KiB
Rust
95 lines
3.4 KiB
Rust
//! 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. Approve only low- and medium-risk actions (`risk_score < 80`).
|
|
|
|
mod approval_request;
|
|
mod prompt;
|
|
mod review;
|
|
mod review_session;
|
|
|
|
use std::time::Duration;
|
|
|
|
use serde::Deserialize;
|
|
use serde::Serialize;
|
|
|
|
pub(crate) use approval_request::GuardianApprovalRequest;
|
|
pub(crate) use approval_request::GuardianMcpAnnotations;
|
|
pub(crate) use approval_request::guardian_approval_request_to_json;
|
|
pub(crate) use review::GUARDIAN_REJECTION_MESSAGE;
|
|
pub(crate) use review::is_guardian_reviewer_source;
|
|
pub(crate) use review::review_approval_request;
|
|
pub(crate) use review::review_approval_request_with_cancel;
|
|
pub(crate) use review::routes_approval_to_guardian;
|
|
pub(crate) use review_session::GuardianReviewSessionManager;
|
|
|
|
const GUARDIAN_PREFERRED_MODEL: &str = "gpt-5.4";
|
|
pub(crate) const GUARDIAN_REVIEW_TIMEOUT: Duration = Duration::from_secs(90);
|
|
pub(crate) const GUARDIAN_REVIEWER_NAME: &str = "guardian";
|
|
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_APPROVAL_RISK_THRESHOLD: u8 = 80;
|
|
const GUARDIAN_RECENT_ENTRY_LIMIT: usize = 40;
|
|
const TRUNCATION_TAG: &str = "truncated";
|
|
|
|
/// Evidence item returned by the guardian reviewer.
|
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
pub(crate) struct GuardianEvidence {
|
|
pub(crate) message: String,
|
|
pub(crate) why: String,
|
|
}
|
|
|
|
/// Structured output contract that the guardian reviewer must satisfy.
|
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
pub(crate) struct GuardianAssessment {
|
|
pub(crate) risk_level: codex_protocol::protocol::GuardianRiskLevel,
|
|
pub(crate) risk_score: u8,
|
|
pub(crate) rationale: String,
|
|
pub(crate) evidence: Vec<GuardianEvidence>,
|
|
}
|
|
|
|
#[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::GuardianTranscriptEntry;
|
|
#[cfg(test)]
|
|
use prompt::GuardianTranscriptEntryKind;
|
|
#[cfg(test)]
|
|
use prompt::build_guardian_prompt_items;
|
|
#[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)]
|
|
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 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;
|