Capture direct-tool critical paths behind a feature flag (#57201)

## What changed

Add the default-disabled `critical_path_timing` feature to capture dependency paths for root-agent turns. Connect direct tool dispatch, execution-lock handoffs, and completed tool batches to turn timing so overlapping work shares its common history without double counting.

Omit captures for unsupported dependencies, including CodeMode, agent communication, approval review, and user waits. Settle aborted tasks before freezing timing, invalidate captures on forced teardown, and apply runtime feature changes only to future turns.

## Testing

Add coverage for serial and overlapping tool dispatch, execution-lock dependency handoffs, cancellation, unsupported paths, frozen timing after completion, and configuration refresh boundaries.

GitOrigin-RevId: 71458b654b1c4a05799886928b449c948ea33109
This commit is contained in:
taohong-oai
2026-10-26 19:19:10 +00:00
committed by copyberry
parent 36021e1ad5
commit f371f9701f
31 changed files with 1436 additions and 106 deletions
+6
View File
@@ -997,10 +997,16 @@ impl Session {
},
_ = tokio::time::sleep(Duration::from_millis(GRACEFULL_INTERRUPTION_TIMEOUT_MS)) => {
warn!("task {sub_id} didn't complete gracefully after {}ms", GRACEFULL_INTERRUPTION_TIMEOUT_MS);
// Forced teardown can abort independently spawned tool dispatches
// whose guards are destroyed later. Do not publish complete totals
// for a dependency graph that has not settled.
task.turn_context.turn_timing_state.invalidate_critical_path();
}
}
task.handle.abort();
// Aborting schedules destruction; join before freezing the turn's timing.
let _ = task.handle.await;
session_task
.abort(Arc::clone(self), Arc::clone(&task.turn_context))
+70
View File
@@ -494,3 +494,73 @@ fn emit_compact_metric_records_auto_local() {
])
);
}
struct UncooperativeTimedTask {
entered: Arc<tokio::sync::Notify>,
}
impl SessionTask for UncooperativeTimedTask {
fn kind(&self) -> TaskKind {
TaskKind::Regular
}
fn span_name(&self) -> &'static str {
"session_task.uncooperative_timing"
}
async fn run(
self: Arc<Self>,
_session: Arc<Session>,
context: Arc<TurnContext>,
_input: Vec<TurnInput>,
_cancellation_token: CancellationToken,
) -> SessionTaskResult {
let timing = &context.turn_timing_state;
timing.enable_critical_path();
let batch = codex_direct_tool_timing::ToolBatchTiming::default();
let now = std::time::Instant::now();
let _blocking;
let _work = timing.direct_tool_timings.start_turn_direct_call(
"uncooperative",
&context.sub_id,
batch.clone(),
now,
timing.snapshot_path(now),
);
_blocking = timing.begin_tool_blocking(Some(batch));
let _review = _work.start_review();
self.entered.notify_one();
std::future::pending().await
}
}
#[tokio::test(flavor = "current_thread")]
async fn hard_abort_drops_timing_guards_before_profile_completion() {
let (session, context, _receiver) = make_session_and_context_with_rx().await;
let entered = Arc::new(tokio::sync::Notify::new());
session
.spawn_task(
Arc::clone(&context),
Vec::new(),
UncooperativeTimedTask {
entered: Arc::clone(&entered),
},
)
.await;
entered.notified().await;
session
.abort_all_tasks(codex_protocol::protocol::TurnAbortReason::Interrupted)
.await;
// The task's review and tool guards are gone before abort completion.
assert!(
context
.turn_timing_state
.direct_tool_timings
.owner_for_turn("uncooperative", &context.sub_id)
.is_none()
);
assert_eq!(
context
.turn_timing_state
.snapshot_path(std::time::Instant::now()),
None
);
}
+8
View File
@@ -44,6 +44,14 @@ impl SessionTask for RegularTask {
input: Vec<TurnInput>,
cancellation_token: CancellationToken,
) -> SessionTaskResult {
if ctx
.config
.features
.enabled(codex_features::Feature::CriticalPathTiming)
&& !ctx.session_source.is_non_root_agent()
{
ctx.turn_timing_state.enable_critical_path();
}
let run_turn_span = trace_span!(
"run_turn",
conversation.id = %sess.thread_id,