[codex] Start the guardian child session when parent session is started (#27982)

## Why

The first auto-review currently creates its Guardian child session on
demand, adding avoidable latency before the review can begin. Creating
the ordinary Guardian child during parent-session initialization lets
that child use the existing session startup WebSocket prewarm before the
first escalation. This does not introduce a Guardian-specific prewarm
mechanism.

## What changed

- initialize the existing Guardian review-session manager owned by
`Session` when a thread starts with auto-review enabled and an approval
policy that routes to Guardian
- use the standard Guardian child-session construction and the existing
session startup WebSocket prewarm
- preserve the existing reuse-key invalidation and lazy creation
fallback when startup initialization fails or the effective review
configuration changes
- add an integration test that verifies normal root-session startup
emits a Guardian `generate=false` prewarm request

## Benchmark

I compared release builds against main. Each prompt first ran a
non-escalated `sleep 3`, then requested an escalated marker command.

| binary | count | avg Guardian duration | median Guardian duration |
avg Guardian TTFT |
|---|---:|---:|---:|---:|
| origin-main | 10 | 4008.7 ms | 3949.5 ms | 3746.5 ms |
| session-fix | 10 | 2865.0 ms | 2594.0 ms | 2492.7 ms |

Guardian duration fell by 28.5% and Guardian TTFT fell by 33.5%. These
measurements cover Guardian review latency; they do not measure parent
thread-start latency.
This commit is contained in:
jgershen-oai
2026-06-22 11:54:44 -07:00
committed by GitHub
parent 5c0fbf3492
commit 15f448d8b0
5 changed files with 210 additions and 56 deletions
@@ -17,6 +17,7 @@ use core_test_support::responses::ev_response_created;
use core_test_support::responses::mount_sse_sequence;
use core_test_support::responses::sse;
use core_test_support::responses::start_mock_server;
use core_test_support::responses::start_websocket_server;
use core_test_support::skip_if_no_network;
use core_test_support::skip_if_sandbox;
use core_test_support::test_codex::local_selections;
@@ -30,6 +31,85 @@ use std::os::unix::fs::PermissionsExt;
use std::time::Duration;
use tempfile::TempDir;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn guardian_session_prewarms_and_is_reused_for_first_review() -> Result<()> {
skip_if_no_network!(Ok(()));
let tool_args = json!({
"cmd": "true",
"sandbox_permissions": SandboxPermissions::RequireEscalated,
"justification": "Exercise Guardian approval routing.",
})
.to_string();
let server = start_websocket_server(vec![
vec![vec![ev_response_created("warm-1"), ev_completed("warm-1")]],
vec![vec![ev_response_created("warm-2"), ev_completed("warm-2")]],
vec![vec![
ev_response_created("approval-request"),
ev_function_call("approval-call", "exec_command", &tool_args),
ev_completed("approval-request"),
]],
vec![vec![
ev_response_created("guardian-review"),
ev_completed("guardian-review"),
]],
])
.await;
let mut builder = test_codex().with_config(|config| {
config.permissions.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
config.approvals_reviewer = ApprovalsReviewer::AutoReview;
});
let test = builder.build_with_websocket_server(&server).await?;
let (first, second) = tokio::time::timeout(Duration::from_secs(5), async {
tokio::join!(
server.wait_for_request(/*connection_index*/ 0, /*request_index*/ 0),
server.wait_for_request(/*connection_index*/ 1, /*request_index*/ 0)
)
})
.await?;
let prewarm_requests = [first.body_json(), second.body_json()];
let guardian_prewarm = prewarm_requests
.iter()
.find(|request| {
request["client_metadata"]["x-openai-subagent"].as_str() == Some("guardian")
})
.expect("guardian startup prewarm request");
assert_eq!(guardian_prewarm["generate"].as_bool(), Some(false));
let guardian_thread_id = guardian_prewarm["client_metadata"]["thread_id"]
.as_str()
.expect("guardian thread id");
test.codex
.submit(
vec![UserInput::Text {
text: "run a command that requires Guardian review".into(),
text_elements: Vec::new(),
}]
.into(),
)
.await?;
let guardian_review = tokio::time::timeout(
Duration::from_secs(5),
server.wait_for_request(/*connection_index*/ 3, /*request_index*/ 0),
)
.await?
.body_json();
assert_eq!(
guardian_review["client_metadata"]["x-openai-subagent"].as_str(),
Some("guardian")
);
assert_eq!(
guardian_review["client_metadata"]["thread_id"].as_str(),
Some(guardian_thread_id)
);
assert_eq!(guardian_review.get("generate"), None);
test.codex.shutdown_and_wait().await?;
server.shutdown().await;
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn guardian_review_session_does_not_inherit_legacy_notify() -> Result<()> {
skip_if_no_network!(Ok(()));