mirror of
https://github.com/openai/codex.git
synced 2026-11-13 21:30:47 +00:00
## Why Recording inference starts synchronously serializes prompts and writes trace payloads before model work can proceed. Defer that work so inference dispatch can continue while the trace is recorded. ## What changed - Use one worker per trace bundle to write events in submission order, with up to eight pending jobs. - Share prepared prompts and encoded requests with the worker, and build remote compaction snapshots there. Write payloads before publishing events that reference them. - Add `TraceWriter::flush` and `ThreadTraceContext::flush` for live reads. Drain accepted jobs and seal the bundle on shutdown; suppress the closing marker after a recording failure. ## Testing Add tests for deferred prompt serialization, ordered start and completion events, shutdown draining, and serialization failures that preserve earlier evidence without publishing a closing marker. Verify compaction snapshots are built on the worker and skipped when tracing is disabled. GitOrigin-RevId: 32fd787bb1d281f1814df2f08c73b667628f8add
241 lines
8.6 KiB
Rust
241 lines
8.6 KiB
Rust
//! Hot-path helpers for recording upstream remote compaction attempts.
|
|
//!
|
|
//! Remote compaction is a model-facing request with a different semantic role
|
|
//! from normal sampling. Keeping the no-op capable trace handle in this crate
|
|
//! lets `codex-core` record exact endpoint payloads without owning trace schema
|
|
//! details.
|
|
|
|
use std::fmt::Display;
|
|
use std::sync::Arc;
|
|
use std::sync::atomic::AtomicU64;
|
|
use std::sync::atomic::Ordering;
|
|
|
|
use codex_protocol::models::ResponseItem;
|
|
use serde::Serialize;
|
|
|
|
use crate::inference::serialize_trace_items;
|
|
use crate::model::AgentThreadId;
|
|
use crate::model::CodexTurnId;
|
|
use crate::model::CompactionId;
|
|
use crate::model::CompactionRequestId;
|
|
use crate::payload::RawPayloadKind;
|
|
use crate::raw_event::RawTraceEventContext;
|
|
use crate::raw_event::RawTraceEventPayload;
|
|
use crate::writer::TraceWriter;
|
|
|
|
static NEXT_COMPACTION_REQUEST: AtomicU64 = AtomicU64::new(1);
|
|
|
|
/// Turn-local remote compaction tracing context.
|
|
///
|
|
/// A compaction can retry its upstream request before installing one checkpoint. The context
|
|
/// owns the stable checkpoint ID; each request attempt gets a separate request ID.
|
|
#[derive(Clone, Debug)]
|
|
pub struct CompactionTraceContext {
|
|
state: CompactionTraceContextState,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
enum CompactionTraceContextState {
|
|
Disabled,
|
|
Enabled(EnabledCompactionTraceContext),
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
struct EnabledCompactionTraceContext {
|
|
writer: Arc<TraceWriter>,
|
|
thread_id: AgentThreadId,
|
|
codex_turn_id: CodexTurnId,
|
|
compaction_id: CompactionId,
|
|
model: String,
|
|
provider_name: String,
|
|
}
|
|
|
|
/// One upstream request attempt made while computing a compaction checkpoint.
|
|
#[derive(Clone, Debug)]
|
|
pub struct CompactionTraceAttempt {
|
|
state: CompactionTraceAttemptState,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
enum CompactionTraceAttemptState {
|
|
Disabled,
|
|
Enabled(EnabledCompactionTraceAttempt),
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
struct EnabledCompactionTraceAttempt {
|
|
context: EnabledCompactionTraceContext,
|
|
compaction_request_id: CompactionRequestId,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
struct TracedCompactionCompleted<'a> {
|
|
#[serde(serialize_with = "serialize_trace_items")]
|
|
output_items: &'a [ResponseItem],
|
|
}
|
|
|
|
/// History replacement checkpoint persisted when compaction installs new live history.
|
|
///
|
|
/// The checkpoint keeps compaction separate from ordinary sampling snapshots:
|
|
/// `input_history` is the live thread history selected for compaction, while
|
|
/// `replacement_history` is what future prompts may carry after the checkpoint.
|
|
#[derive(Serialize)]
|
|
pub struct CompactionCheckpointTracePayload<'a> {
|
|
pub input_history: &'a [ResponseItem],
|
|
pub replacement_history: &'a [ResponseItem],
|
|
}
|
|
|
|
impl CompactionTraceContext {
|
|
/// Builds a context that accepts trace calls and records nothing.
|
|
pub fn disabled() -> Self {
|
|
Self {
|
|
state: CompactionTraceContextState::Disabled,
|
|
}
|
|
}
|
|
|
|
/// Builds an enabled context for upstream attempts that compute one checkpoint.
|
|
pub fn enabled(
|
|
writer: Arc<TraceWriter>,
|
|
thread_id: AgentThreadId,
|
|
codex_turn_id: CodexTurnId,
|
|
compaction_id: CompactionId,
|
|
model: String,
|
|
provider_name: String,
|
|
) -> Self {
|
|
Self {
|
|
state: CompactionTraceContextState::Enabled(EnabledCompactionTraceContext {
|
|
writer,
|
|
thread_id,
|
|
codex_turn_id,
|
|
compaction_id,
|
|
model,
|
|
provider_name,
|
|
}),
|
|
}
|
|
}
|
|
|
|
/// Returns whether this context records compaction traces.
|
|
pub fn is_enabled(&self) -> bool {
|
|
matches!(self.state, CompactionTraceContextState::Enabled(_))
|
|
}
|
|
|
|
/// Starts an upstream attempt. The recording worker builds the snapshot if tracing is enabled.
|
|
pub fn start_attempt<T: Serialize>(
|
|
&self,
|
|
request: impl FnOnce() -> T + Send + 'static,
|
|
) -> CompactionTraceAttempt {
|
|
let CompactionTraceContextState::Enabled(context) = &self.state else {
|
|
return CompactionTraceAttempt {
|
|
state: CompactionTraceAttemptState::Disabled,
|
|
};
|
|
};
|
|
|
|
let compaction_request_id = next_compaction_request_id();
|
|
let attempt = CompactionTraceAttempt {
|
|
state: CompactionTraceAttemptState::Enabled(EnabledCompactionTraceAttempt {
|
|
context: context.clone(),
|
|
compaction_request_id: compaction_request_id.clone(),
|
|
}),
|
|
};
|
|
let compaction_id = context.compaction_id.clone();
|
|
let thread_id = context.thread_id.clone();
|
|
let codex_turn_id = context.codex_turn_id.clone();
|
|
let model = context.model.clone();
|
|
let provider_name = context.provider_name.clone();
|
|
let _ = context.writer.defer_event(
|
|
RawTraceEventContext::turn(&thread_id, &codex_turn_id),
|
|
move |bundle| {
|
|
let request_payload = bundle.write_body(
|
|
RawPayloadKind::CompactionRequest,
|
|
&serde_json::to_vec(&request())?,
|
|
)?;
|
|
Ok(RawTraceEventPayload::CompactionRequestStarted {
|
|
compaction_id,
|
|
compaction_request_id,
|
|
thread_id,
|
|
codex_turn_id,
|
|
model,
|
|
provider_name,
|
|
request_payload,
|
|
})
|
|
},
|
|
);
|
|
attempt
|
|
}
|
|
|
|
/// Records the point where compacted history becomes the live thread history.
|
|
///
|
|
/// The checkpoint belongs to the same semantic compaction lifecycle as the
|
|
/// compact endpoint attempts, so the context reuses its stable compaction ID.
|
|
pub fn record_installed(&self, checkpoint: &CompactionCheckpointTracePayload<'_>) {
|
|
let CompactionTraceContextState::Enabled(context) = &self.state else {
|
|
return;
|
|
};
|
|
let compaction_id = context.compaction_id.clone();
|
|
let _ = context.writer.event_with_payload(
|
|
RawTraceEventContext::turn(&context.thread_id, &context.codex_turn_id),
|
|
RawPayloadKind::CompactionCheckpoint,
|
|
checkpoint,
|
|
move |checkpoint_payload| RawTraceEventPayload::CompactionInstalled {
|
|
compaction_id,
|
|
checkpoint_payload,
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
impl CompactionTraceAttempt {
|
|
/// Records the non-streaming compact endpoint response payload.
|
|
///
|
|
/// Compaction responses use the same response-item preservation rules as
|
|
/// inference streams: traces are evidence, while normal ResponseItem
|
|
/// serialization is shaped for future request construction.
|
|
pub fn record_completed(&self, output_items: &[ResponseItem]) {
|
|
let CompactionTraceAttemptState::Enabled(attempt) = &self.state else {
|
|
return;
|
|
};
|
|
let response_payload = TracedCompactionCompleted { output_items };
|
|
let ctx = &attempt.context;
|
|
let compaction_id = ctx.compaction_id.clone();
|
|
let compaction_request_id = attempt.compaction_request_id.clone();
|
|
let _ = ctx.writer.event_with_payload(
|
|
RawTraceEventContext::turn(&ctx.thread_id, &ctx.codex_turn_id),
|
|
RawPayloadKind::CompactionResponse,
|
|
&response_payload,
|
|
move |response_payload| RawTraceEventPayload::CompactionRequestCompleted {
|
|
compaction_id,
|
|
compaction_request_id,
|
|
response_payload,
|
|
},
|
|
);
|
|
}
|
|
|
|
/// Records the compact endpoint result without forcing callers to branch on trace events.
|
|
pub fn record_result<E: Display>(&self, result: Result<&[ResponseItem], E>) {
|
|
match result {
|
|
Ok(output_items) => self.record_completed(output_items),
|
|
Err(err) => self.record_failed(err),
|
|
}
|
|
}
|
|
|
|
/// Records pre-response failures from the compact endpoint.
|
|
pub fn record_failed(&self, error: impl Display) {
|
|
let CompactionTraceAttemptState::Enabled(attempt) = &self.state else {
|
|
return;
|
|
};
|
|
let _ = attempt.context.writer.enqueue_event(
|
|
RawTraceEventContext::turn(&attempt.context.thread_id, &attempt.context.codex_turn_id),
|
|
RawTraceEventPayload::CompactionRequestFailed {
|
|
compaction_id: attempt.context.compaction_id.clone(),
|
|
compaction_request_id: attempt.compaction_request_id.clone(),
|
|
error: error.to_string(),
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
fn next_compaction_request_id() -> CompactionRequestId {
|
|
let ordinal = NEXT_COMPACTION_REQUEST.fetch_add(1, Ordering::Relaxed);
|
|
format!("compaction_request:{ordinal}")
|
|
}
|