Replace Guardian tickets with parent response IDs (#43002)

## What changed

Capture the ID from `response.created` and pass it as `parent_response_id` in Guardian review and classifier request metadata. Clear the current response ID before sampling retries so tool reviews cannot inherit a failed response's ID.

Request Guardian credits with `guardian_credits_requested` on eligible parent requests. Remove the ticket protocol, transport attachment, and ticket-specific body redaction. Keep ordinary app-server client `parent_response_id` values inside `x-codex-turn-metadata` rather than promoting them to Guardian request metadata.

## Testing

Update coverage for HTTP fallback, WebSocket review reuse, missing response IDs, classifier transport retries, and code-mode yield/resume attribution. Extend app-server tests to verify client metadata placement over HTTP and WebSocket.

GitOrigin-RevId: c1665e56382ecec8d260fc2ca0ecd5e20b0b2391
This commit is contained in:
jif
2026-09-05 13:02:49 +00:00
committed by copyberry
parent a7a4321593
commit dee21ec1bc
32 changed files with 178 additions and 362 deletions
-1
View File
@@ -137,7 +137,6 @@ pub(super) async fn spawn_review_thread(
extension_data.insert(parent_turn_context.skills_snapshot().as_ref().clone());
let review_turn_context = TurnContext {
guardian_ticket: None,
sub_id: review_turn_id.clone(),
trace_id: current_span_trace_id(),
realtime_active: parent_turn_context.realtime_active,
-1
View File
@@ -619,7 +619,6 @@ impl Session {
request_kind,
);
CodexResponsesMetadata {
guardian_ticket: turn_context.guardian_ticket.clone(),
window_number: Some(window_number),
context_window_id: Some(context_window_id),
history_ingest_requested: turn_context
+7 -5
View File
@@ -1437,10 +1437,10 @@ async fn run_sampling_request(
let mut original_input = None;
let mut executed_tool_calls_by_output = HashMap::new();
loop {
// A retry must not lend a previous response's ticket to the next tool call.
// A retry must not attribute the next tool call to the previous response.
turn_context
.extension_data
.remove::<codex_protocol::guardian_ticket::GuardianTicket>();
.remove::<codex_api::ResponseId>();
let prompt_input = if let Some(input) = initial_input.take() {
input
} else {
@@ -2376,9 +2376,11 @@ async fn try_run_sampling_request(
record_turn_ttft_metric(&turn_context, &event).await;
match event {
ResponseEvent::Created { guardian_ticket } => {
if let Some(ticket) = guardian_ticket {
turn_context.extension_data.insert(ticket);
ResponseEvent::Created { response_id } => {
if let Some(response_id) = response_id {
turn_context
.extension_data
.insert(codex_api::ResponseId(response_id));
}
}
ResponseEvent::OutputItemDone(mut item) => {
@@ -185,7 +185,6 @@ impl std::fmt::Debug for TurnEnvironment {
/// Request options for one turn, separate from persistent thread settings.
#[derive(Default)]
pub(crate) struct NewTurnContextOptions {
pub(crate) guardian_ticket: Option<codex_protocol::guardian_ticket::GuardianTicket>,
pub(crate) final_output_json_schema: Option<Value>,
pub(crate) cyber_access_program: Option<CyberAccessProgram>,
}
@@ -193,8 +192,6 @@ pub(crate) struct NewTurnContextOptions {
/// The context needed for a single turn of the thread.
#[derive(Debug)]
pub struct TurnContext {
/// Receipt selected for this Guardian turn, separate from its own responses.
pub(crate) guardian_ticket: Option<codex_protocol::guardian_ticket::GuardianTicket>,
pub(crate) sub_id: String,
pub(crate) trace_id: Option<String>,
pub(crate) realtime_active: bool,
@@ -540,7 +537,6 @@ impl TurnContext {
windows_sandbox_level: self.windows_sandbox_level,
available_models,
unified_exec_shell_mode: self.unified_exec_shell_mode.clone(),
guardian_ticket: self.guardian_ticket.clone(),
final_output_json_schema: self.final_output_json_schema.clone(),
dynamic_tools: self.dynamic_tools.clone(),
turn_metadata_state: self.turn_metadata_state.clone(),
@@ -785,7 +781,6 @@ impl Session {
let extension_data = Arc::new(codex_extension_api::ExtensionData::new(sub_id.clone()));
extension_data.insert(skills_snapshot);
TurnContext {
guardian_ticket: None,
sub_id,
trace_id: current_span_trace_id(),
realtime_active: false,
@@ -1040,7 +1035,6 @@ impl Session {
turn_context.extension_data.insert(trusted_plugin_roots);
turn_context.realtime_active = self.conversation.running_state().await.is_some();
turn_context.guardian_ticket = options.guardian_ticket;
turn_context.final_output_json_schema = options.final_output_json_schema;
if turn_context.config.model_provider_id == codex_model_provider_info::OPENAI_PROVIDER_ID {
turn_context.cyber_access_program = options.cyber_access_program;
-2
View File
@@ -121,7 +121,6 @@ impl PreparedTurnInputSettings {
kind: TurnStartKind,
) -> CodexResult<Option<Arc<TurnContext>>> {
let TurnStartOptions {
guardian_ticket,
turn_trigger,
final_output_json_schema,
service_tier,
@@ -139,7 +138,6 @@ impl PreparedTurnInputSettings {
updates.service_tier_for_turn = service_tier;
let options = NewTurnContextOptions {
guardian_ticket,
final_output_json_schema,
cyber_access_program,
};