guardian initial feedback / tweaks (#13897)

## Summary
- remove the remaining model-visible guardian-specific `on-request`
prompt additions so enabling the feature does not change the main
approval-policy instructions
- neutralize user-facing guardian wording to talk about automatic
approval review / approval requests rather than a second reviewer or
only sandbox escalations
- tighten guardian retry-context handling so agent-authored
`justification` stays in the structured action JSON and is not also
injected as raw retry context
- simplify guardian review plumbing in core by deleting dead
prompt-append paths and trimming some request/transcript setup code

## Notable Changes
- delete the dead `permissions/approval_policy/guardian.md` append path
and stop threading `guardian_approval_enabled` through model-facing
developer-instruction builders
- rename the experimental feature copy to `Automatic approval review`
and update the `/experimental` snapshot text accordingly
- make approval-review status strings generic across shell, patch,
network, and MCP review types
- forward real sandbox/network retry reasons for shell and unified-exec
guardian review, but do not pass agent-authored justification as raw
retry context
- simplify `guardian.rs` by removing the one-field request wrapper,
deduping reasoning-effort selection, and cleaning up transcript entry
collection

## Testing
- `just fmt`
- full validation left to CI

---------

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Charley Cunningham
2026-03-09 09:25:24 -07:00
committed by GitHub
parent 2bc3e52a91
commit f23fcd6ced
16 changed files with 421 additions and 352 deletions

View File

@@ -1,6 +1,6 @@
use crate::codex::Session;
use crate::guardian::GUARDIAN_REJECTION_MESSAGE;
use crate::guardian::GuardianReviewRequest;
use crate::guardian::GuardianApprovalRequest;
use crate::guardian::review_approval_request;
use crate::guardian::routes_approval_to_guardian;
use crate::network_policy_decision::denied_network_policy_message;
@@ -21,7 +21,6 @@ use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::ReviewDecision;
use codex_protocol::protocol::WarningEvent;
use indexmap::IndexMap;
use serde_json::json;
use std::collections::HashMap;
use std::collections::HashSet;
use std::sync::Arc;
@@ -344,14 +343,11 @@ impl NetworkApprovalService {
review_approval_request(
&session,
&turn_context,
GuardianReviewRequest {
action: json!({
"tool": "network_access",
"target": target,
"host": request.host,
"protocol": key.protocol,
"port": key.port,
}),
GuardianApprovalRequest::NetworkAccess {
target,
host: request.host,
protocol,
port: key.port,
},
Some(policy_denial_message.clone()),
)

View File

@@ -5,7 +5,7 @@
//! `codex --codex-run-as-apply-patch`, and runs under the current
//! `SandboxAttempt` with a minimal environment.
use crate::exec::ExecToolCallOutput;
use crate::guardian::GuardianReviewRequest;
use crate::guardian::GuardianApprovalRequest;
use crate::guardian::review_approval_request;
use crate::guardian::routes_approval_to_guardian;
use crate::sandboxing::CommandSpec;
@@ -28,7 +28,6 @@ use codex_protocol::protocol::FileChange;
use codex_protocol::protocol::ReviewDecision;
use codex_utils_absolute_path::AbsolutePathBuf;
use futures::future::BoxFuture;
use serde_json::json;
use std::collections::HashMap;
use std::path::PathBuf;
@@ -50,15 +49,12 @@ impl ApplyPatchRuntime {
Self
}
fn build_guardian_review_request(req: &ApplyPatchRequest) -> GuardianReviewRequest {
GuardianReviewRequest {
action: json!({
"tool": "apply_patch",
"cwd": req.action.cwd,
"files": req.file_paths,
"change_count": req.changes.len(),
"patch": req.action.patch,
}),
fn build_guardian_review_request(req: &ApplyPatchRequest) -> GuardianApprovalRequest {
GuardianApprovalRequest::ApplyPatch {
cwd: req.action.cwd.clone(),
files: req.file_paths.clone(),
change_count: req.changes.len(),
patch: req.action.patch.clone(),
}
}
@@ -135,8 +131,8 @@ impl Approvable<ApplyPatchRequest> for ApplyPatchRuntime {
let changes = req.changes.clone();
Box::pin(async move {
if routes_approval_to_guardian(turn) {
let request = ApplyPatchRuntime::build_guardian_review_request(req);
return review_approval_request(session, turn, request, retry_reason).await;
let action = ApplyPatchRuntime::build_guardian_review_request(req);
return review_approval_request(session, turn, action, retry_reason).await;
}
if let Some(reason) = retry_reason {
let rx_approve = session
@@ -256,14 +252,11 @@ mod tests {
assert_eq!(
guardian_request,
GuardianReviewRequest {
action: json!({
"tool": "apply_patch",
"cwd": expected_cwd,
"files": request.file_paths,
"change_count": 1usize,
"patch": expected_patch,
}),
GuardianApprovalRequest::ApplyPatch {
cwd: expected_cwd,
files: request.file_paths,
change_count: 1usize,
patch: expected_patch,
}
);
}

View File

@@ -11,7 +11,7 @@ pub(crate) mod zsh_fork_backend;
use crate::command_canonicalization::canonicalize_command_for_approval;
use crate::exec::ExecToolCallOutput;
use crate::features::Feature;
use crate::guardian::GuardianReviewRequest;
use crate::guardian::GuardianApprovalRequest;
use crate::guardian::review_approval_request;
use crate::guardian::routes_approval_to_guardian;
use crate::powershell::prefix_powershell_script_with_utf8;
@@ -38,7 +38,6 @@ use codex_network_proxy::NetworkProxy;
use codex_protocol::models::PermissionProfile;
use codex_protocol::protocol::ReviewDecision;
use futures::future::BoxFuture;
use serde_json::json;
use std::collections::HashMap;
use std::path::PathBuf;
@@ -145,33 +144,26 @@ impl Approvable<ShellRequest> for ShellRuntime {
let keys = self.approval_keys(req);
let command = req.command.clone();
let cwd = req.cwd.clone();
let reason = ctx
.retry_reason
.clone()
.or_else(|| req.justification.clone());
let retry_reason = ctx.retry_reason.clone();
let reason = retry_reason.clone().or_else(|| req.justification.clone());
let session = ctx.session;
let turn = ctx.turn;
let call_id = ctx.call_id.to_string();
Box::pin(async move {
if routes_approval_to_guardian(turn) {
let mut action = json!({
"tool": "shell",
"command": command,
"cwd": cwd,
"sandbox_permissions": req.sandbox_permissions,
"additional_permissions": req.additional_permissions,
"justification": reason,
});
if let Some(action) = action.as_object_mut() {
if req.additional_permissions.is_none() {
action.remove("additional_permissions");
}
if reason.is_none() {
action.remove("justification");
}
}
let request = GuardianReviewRequest { action };
return review_approval_request(session, turn, request, None).await;
return review_approval_request(
session,
turn,
GuardianApprovalRequest::Shell {
command,
cwd,
sandbox_permissions: req.sandbox_permissions,
additional_permissions: req.additional_permissions.clone(),
justification: req.justification.clone(),
},
retry_reason,
)
.await;
}
with_cached_approval(&session.services, "shell", keys, move || async move {
let available_decisions = None;

View File

@@ -7,7 +7,7 @@ use crate::exec::SandboxType;
use crate::exec::is_likely_sandbox_denied;
use crate::exec_policy::prompt_is_rejected_by_policy;
use crate::features::Feature;
use crate::guardian::GuardianReviewRequest;
use crate::guardian::GuardianApprovalRequest;
use crate::guardian::review_approval_request;
use crate::guardian::routes_approval_to_guardian;
use crate::sandboxing::ExecRequest;
@@ -50,7 +50,6 @@ use codex_shell_escalation::PreparedExec;
use codex_shell_escalation::ShellCommandExecutor;
use codex_shell_escalation::Stopwatch;
use codex_utils_absolute_path::AbsolutePathBuf;
use serde_json::json;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
@@ -386,16 +385,19 @@ impl CoreShellActionProvider {
Ok(stopwatch
.pause_for(async move {
if routes_approval_to_guardian(&turn) {
let request = GuardianReviewRequest {
action: json!({
"tool": tool_name,
"program": program,
"argv": argv,
"cwd": workdir,
"additional_permissions": additional_permissions,
}),
};
return review_approval_request(&session, &turn, request, None).await;
return review_approval_request(
&session,
&turn,
GuardianApprovalRequest::Execve {
tool_name: tool_name.to_string(),
program: program.to_string_lossy().into_owned(),
argv: argv.to_vec(),
cwd: workdir,
additional_permissions,
},
None,
)
.await;
}
let available_decisions = vec![
Some(ReviewDecision::Approved),

View File

@@ -9,7 +9,7 @@ use crate::error::CodexErr;
use crate::error::SandboxErr;
use crate::exec::ExecExpiration;
use crate::features::Feature;
use crate::guardian::GuardianReviewRequest;
use crate::guardian::GuardianApprovalRequest;
use crate::guardian::review_approval_request;
use crate::guardian::routes_approval_to_guardian;
use crate::powershell::prefix_powershell_script_with_utf8;
@@ -41,7 +41,6 @@ use codex_network_proxy::NetworkProxy;
use codex_protocol::models::PermissionProfile;
use codex_protocol::protocol::ReviewDecision;
use futures::future::BoxFuture;
use serde_json::json;
use std::collections::HashMap;
use std::path::PathBuf;
@@ -113,31 +112,24 @@ impl Approvable<UnifiedExecRequest> for UnifiedExecRuntime<'_> {
let call_id = ctx.call_id.to_string();
let command = req.command.clone();
let cwd = req.cwd.clone();
let reason = ctx
.retry_reason
.clone()
.or_else(|| req.justification.clone());
let retry_reason = ctx.retry_reason.clone();
let reason = retry_reason.clone().or_else(|| req.justification.clone());
Box::pin(async move {
if routes_approval_to_guardian(turn) {
let mut action = json!({
"tool": "exec_command",
"command": command,
"cwd": cwd,
"sandbox_permissions": req.sandbox_permissions,
"additional_permissions": req.additional_permissions,
"justification": reason,
"tty": req.tty,
});
if let Some(action) = action.as_object_mut() {
if req.additional_permissions.is_none() {
action.remove("additional_permissions");
}
if reason.is_none() {
action.remove("justification");
}
}
let request = GuardianReviewRequest { action };
return review_approval_request(session, turn, request, None).await;
return review_approval_request(
session,
turn,
GuardianApprovalRequest::ExecCommand {
command,
cwd,
sandbox_permissions: req.sandbox_permissions,
additional_permissions: req.additional_permissions.clone(),
justification: req.justification.clone(),
tty: req.tty,
},
retry_reason,
)
.await;
}
with_cached_approval(&session.services, "unified_exec", keys, || async move {
let available_decisions = None;