Attribute command and plugin analytics to the invoking model (#45445)

## Why

Command execution and plugin measurement events lack model and reasoning-effort labels. Attribution needs to reflect the step that invoked the command, even when model settings change before a background process finishes.

## What changed

- Add `model_slug` and `reasoning_effort` to command execution and plugin measurement analytics.
- Capture model context from resolved step settings and carry it through execution, approval, Guardian review, and plugin metrics collection.
- Preserve the first command-start model context when subsequent start notifications arrive.
- Keep the carried context out of serialized protocol items and generated schemas.

## Testing

Extend analytics tests to cover model switches before invocation and during background execution, default reasoning effort, Guardian-denied commands, and repeated start notifications retaining the original model context.

GitOrigin-RevId: af90e1c0d39bab625f2e89786085b61a9b96c0ce
This commit is contained in:
jwang-openai
2026-09-14 15:30:22 +00:00
committed by copyberry
parent b6a5d5bb14
commit 4d8eca1ff3
40 changed files with 355 additions and 43 deletions
@@ -178,6 +178,7 @@ pub(crate) fn spawn_exit_watcher(
let session_ref = Arc::clone(&context.session);
let turn_ref = Arc::clone(&context.step_context.turn);
let model_info = Arc::clone(&context.step_context.settings.model_info);
let model_context = context.step_context.model_context();
let call_id = context.call_id.clone();
let exit_token = process.cancellation_token();
let output_drained = process.output_drained_notify();
@@ -223,6 +224,7 @@ pub(crate) fn spawn_exit_watcher(
exit_code,
&session_ref,
&turn_ref,
&model_context,
&call_id,
)
.await;
@@ -276,11 +276,13 @@ struct InitialExecCommandGuard {
impl InitialExecCommandGuard {
async fn finish_plugin_metrics(&mut self, context: &UnifiedExecContext, exit_code: i32) {
let model_context = context.step_context.model_context();
finish_and_track_measurements(
self.metrics_sidecar.take(),
exit_code,
&context.session,
&context.step_context.turn,
&model_context,
&context.call_id,
)
.await;
@@ -530,13 +532,15 @@ impl UnifiedExecProcessManager {
});
let transcript = Arc::new(tokio::sync::Mutex::new(HeadTailBuffer::default()));
let event_ctx = ToolEventCtx::new(
let model_context = context.step_context.model_context();
let mut event_ctx = ToolEventCtx::new(
context.session.as_ref(),
context.step_context.turn.as_ref(),
&context.step_context.settings.model_info,
&context.call_id,
/*turn_diff_tracker*/ None,
);
event_ctx.model_context = Some(&model_context);
let plugin_attribution = if request.turn_environment.environment.is_remote() {
let file_system = request.turn_environment.environment.get_filesystem();
context
@@ -727,6 +731,7 @@ impl UnifiedExecProcessManager {
exit_code.unwrap_or(-1),
&context.session,
&context.step_context.turn,
&model_context,
&context.call_id,
)
.await;