feat(core, tracing): add a span representing a turn (#13424)

This is PR 3 of the app-server tracing rollout.

PRs https://github.com/openai/codex/pull/13285 and
https://github.com/openai/codex/pull/13368 gave us inbound request spans
in app-server and propagated trace context through Submission. This
change finishes the next piece in core: when a request actually starts a
turn, we now create a core-owned long-lived span that stays open for the
real lifetime of the turn.

What changed:
- `Session::spawn_task` can now optionally create a long-lived turn span
and run the spawned task inside it
- `turn/start` uses that path, so normal turn execution stays under a
single core-owned span after the async handoff
- `review/start` uses the same pattern
- added a unit test that verifies the spawned turn task inherits the
submission dispatch trace ancestry

**Why**
The app-server request span is intentionally short-lived. Once work
crosses into core, we still want one span that covers the actual
execution window until completion or interruption. This keeps that
ownership where it belongs: in the layer that owns the runtime
lifecycle.
This commit is contained in:
Owen Lin
2026-03-04 11:09:17 -08:00
committed by GitHub
parent 54a1c81d73
commit 27724f6ead
8 changed files with 158 additions and 11 deletions

View File

@@ -17,6 +17,10 @@ impl SessionTask for CompactTask {
TaskKind::Compact
}
fn span_name(&self) -> &'static str {
"session_task.compact"
}
async fn run(
self: Arc<Self>,
session: Arc<SessionTaskContext>,

View File

@@ -32,6 +32,10 @@ impl SessionTask for GhostSnapshotTask {
TaskKind::Regular
}
fn span_name(&self) -> &'static str {
"session_task.ghost_snapshot"
}
async fn run(
self: Arc<Self>,
session: Arc<SessionTaskContext>,

View File

@@ -14,7 +14,7 @@ use tokio::sync::Notify;
use tokio_util::sync::CancellationToken;
use tokio_util::task::AbortOnDropHandle;
use tracing::Instrument;
use tracing::Span;
use tracing::info_span;
use tracing::trace;
use tracing::warn;
@@ -89,6 +89,9 @@ pub(crate) trait SessionTask: Send + Sync + 'static {
/// surface it in telemetry and UI.
fn kind(&self) -> TaskKind;
/// Returns the tracing name for a spawned task span.
fn span_name(&self) -> &'static str;
/// Executes the task until completion or cancellation.
///
/// Implementations typically stream protocol events using `session` and
@@ -127,6 +130,7 @@ impl Session {
let task: Arc<dyn SessionTask> = Arc::new(task);
let task_kind = task.kind();
let span_name = task.span_name();
let cancellation_token = CancellationToken::new();
let done = Arc::new(Notify::new());
@@ -137,7 +141,15 @@ impl Session {
let ctx = Arc::clone(&turn_context);
let task_for_run = Arc::clone(&task);
let task_cancellation_token = cancellation_token.child_token();
let session_span = Span::current();
// Task-owned turn spans keep a core-owned span open for the
// full task lifecycle after the submission dispatch span ends.
let task_span = info_span!(
"turn",
otel.name = span_name,
thread.id = %self.conversation_id,
turn.id = %turn_context.sub_id,
model = %turn_context.model_info.slug,
);
tokio::spawn(
async move {
let ctx_for_finish = Arc::clone(&ctx);
@@ -158,7 +170,7 @@ impl Session {
}
done_clone.notify_waiters();
}
.instrument(session_span),
.instrument(task_span),
)
};

View File

@@ -68,6 +68,10 @@ impl SessionTask for RegularTask {
TaskKind::Regular
}
fn span_name(&self) -> &'static str {
"session_task.turn"
}
async fn run(
self: Arc<Self>,
session: Arc<SessionTaskContext>,

View File

@@ -43,6 +43,10 @@ impl SessionTask for ReviewTask {
TaskKind::Review
}
fn span_name(&self) -> &'static str {
"session_task.review"
}
async fn run(
self: Arc<Self>,
session: Arc<SessionTaskContext>,

View File

@@ -31,6 +31,10 @@ impl SessionTask for UndoTask {
TaskKind::Regular
}
fn span_name(&self) -> &'static str {
"session_task.undo"
}
async fn run(
self: Arc<Self>,
session: Arc<SessionTaskContext>,

View File

@@ -66,6 +66,10 @@ impl SessionTask for UserShellCommandTask {
TaskKind::Regular
}
fn span_name(&self) -> &'static str {
"session_task.user_shell"
}
async fn run(
self: Arc<Self>,
session: Arc<SessionTaskContext>,