mirror of
https://github.com/openai/codex.git
synced 2026-10-30 19:18:49 +00:00
Expose and persist turn lineage across the app server (#51415)
## Why Clients starting delegated work need to attribute descendant turns to the original turn and retain that attribution through completion and history reads. ## What changed - Accept optional `parentTurnId` and `rootTurnId` in `turn/start`, and expose `rootTurnId` on returned turns and lifecycle notifications. - Default new turns to their own root when none is supplied. Preserve the active turn's root when steering, ignoring incoming lineage fields. - Carry root IDs through completion, interruption, recovery, and fork boundaries, and persist them for paginated history reads. Leave unknown roots null in older history. - Update protocol schemas and generated TypeScript and Python bindings. ## Testing Extend coverage for default and explicit roots, steering without changing lineage, interrupted and recovered turns, history reads after restart in both history modes, nested fork pagination, and compatibility with older database writers. GitOrigin-RevId: f1a570a12bd11742929fd2e8b017e5d35aa07dcb
This commit is contained in:
@@ -820,6 +820,7 @@ impl Session {
|
||||
self.emit_turn_abort_lifecycle(reason.clone(), turn_context.extension_data.as_ref())
|
||||
.await;
|
||||
EventMsg::TurnAborted(TurnAbortedEvent {
|
||||
root_turn_id: Some(turn_context.root_turn_id()),
|
||||
turn_id: Some(turn_context.sub_id.clone()),
|
||||
reason,
|
||||
error: None,
|
||||
@@ -836,6 +837,7 @@ impl Session {
|
||||
self.emit_turn_stop_lifecycle(turn_context.extension_data.as_ref())
|
||||
.await;
|
||||
EventMsg::TurnComplete(TurnCompleteEvent {
|
||||
root_turn_id: Some(turn_context.root_turn_id()),
|
||||
turn_id: turn_context.sub_id.clone(),
|
||||
last_agent_message,
|
||||
error,
|
||||
@@ -1003,6 +1005,7 @@ impl Session {
|
||||
self.emit_turn_abort_lifecycle(reason.clone(), task.turn_context.extension_data.as_ref())
|
||||
.await;
|
||||
let event = EventMsg::TurnAborted(TurnAbortedEvent {
|
||||
root_turn_id: Some(task.turn_context.root_turn_id()),
|
||||
turn_id: Some(task.turn_context.sub_id.clone()),
|
||||
reason,
|
||||
error,
|
||||
|
||||
@@ -137,7 +137,7 @@ async fn turn_completion_metrics_follow_model_switch(scenario: UsageScenario) {
|
||||
.with_runtime_reader(),
|
||||
)
|
||||
.expect("in-memory metrics client");
|
||||
let (mut session, mut turn_context, _receiver) = make_session_and_context_with_rx().await;
|
||||
let (mut session, mut turn_context, receiver) = make_session_and_context_with_rx().await;
|
||||
let session_telemetry = session
|
||||
.services
|
||||
.session_telemetry
|
||||
@@ -243,7 +243,21 @@ async fn turn_completion_metrics_follow_model_switch(scenario: UsageScenario) {
|
||||
} else {
|
||||
Ok(None)
|
||||
};
|
||||
let expected_root = turn_context.root_turn_id();
|
||||
session.on_task_finished(turn_context, task_result).await;
|
||||
let mut terminal_root = None;
|
||||
while let Ok(event) = receiver.try_recv() {
|
||||
match event.msg {
|
||||
codex_protocol::protocol::EventMsg::TurnComplete(event) => {
|
||||
terminal_root = event.root_turn_id;
|
||||
}
|
||||
codex_protocol::protocol::EventMsg::TurnAborted(event) => {
|
||||
terminal_root = event.root_turn_id;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
assert_eq!(terminal_root, Some(expected_root));
|
||||
|
||||
let snapshot = metrics.snapshot().expect("runtime metrics snapshot");
|
||||
let token_usage_metric = find_metric(&snapshot, TURN_TOKEN_USAGE_METRIC);
|
||||
|
||||
Reference in New Issue
Block a user