mirror of
https://github.com/openai/codex.git
synced 2026-10-31 19:28:49 +00:00
Preserve rollout tracing across sub-agent reloads (#57615)
## Why Reloading an evicted sub-agent disabled its rollout tracing, leaving later turns out of the trace. Replacement Code Mode sessions could also reuse cell IDs from earlier sessions. ## What changed - Reattach reloaded children to the root rollout trace and emit `ThreadResumed` without duplicating `ThreadStarted`. - Reopen existing threads during replay, preserving their identity and history while clearing stale runtime cell and terminal process mappings. - Allocate Code Mode cell generations across sessions for both gRPC and stdio hosts to prevent ID reuse after reload. - Record an aborted trace when session startup fails before the session loop begins. ## Testing Add regression coverage for child eviction and reload, including a failed reload, continued inference tracing, distinct cell IDs, and preserved mail and follow-up context. Extend replay tests for history preservation and reused runtime handles, and host tests for cell IDs across session replacement and reconnection. GitOrigin-RevId: 6c146c04b5cda9295afb7d973c0d5474b363a822
This commit is contained in:
committed by
copyberry
parent
c0e88f3c04
commit
68b67f0ff4
@@ -5,6 +5,7 @@ use std::sync::Mutex;
|
||||
use std::sync::PoisonError;
|
||||
use std::sync::Weak;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::AtomicU64;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -67,6 +68,8 @@ fn inject_span_traceparent<T>(request: &mut tonic::Request<T>, span: &tracing::S
|
||||
#[derive(Clone)]
|
||||
pub struct GrpcCodeModeSessionProvider {
|
||||
transport: Arc<SharedTransport>,
|
||||
// Reloaded threads share this counter rather than reusing a cell namespace.
|
||||
next_generation: Arc<AtomicU64>,
|
||||
}
|
||||
|
||||
impl GrpcCodeModeSessionProvider {
|
||||
@@ -94,6 +97,7 @@ impl GrpcCodeModeSessionProvider {
|
||||
fn from_transport(transport: SharedTransport) -> Self {
|
||||
Self {
|
||||
transport: Arc::new(transport),
|
||||
next_generation: Arc::new(AtomicU64::new(1)),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::PoisonError;
|
||||
use std::sync::atomic::AtomicU64;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use codex_code_mode_protocol::CodeModeSession;
|
||||
@@ -34,7 +33,6 @@ struct ReconnectInner {
|
||||
limits: CodeModeSessionCellExecutionLimits,
|
||||
binding: Mutex<Option<SessionBinding>>,
|
||||
opening_permit: Semaphore,
|
||||
next_generation: AtomicU64,
|
||||
shutdown_requested: CancellationToken,
|
||||
shutdown_result: Mutex<Option<ShutdownResultReceiver>>,
|
||||
}
|
||||
@@ -56,7 +54,6 @@ impl ReconnectableSession {
|
||||
limits,
|
||||
binding: Mutex::new(None),
|
||||
opening_permit: Semaphore::new(/*permits*/ 1),
|
||||
next_generation: AtomicU64::new(1),
|
||||
shutdown_requested: CancellationToken::new(),
|
||||
shutdown_result: Mutex::new(None),
|
||||
}),
|
||||
@@ -178,7 +175,10 @@ impl ReconnectInner {
|
||||
wait_for_watch(binding.session.inner.request_shutdown()).await?;
|
||||
}
|
||||
|
||||
let generation = self.next_generation.fetch_add(1, Ordering::Relaxed);
|
||||
let generation = self
|
||||
.provider
|
||||
.next_generation
|
||||
.fetch_add(1, Ordering::Relaxed);
|
||||
let session = tokio::select! {
|
||||
biased;
|
||||
_ = self.shutdown_requested.cancelled() => {
|
||||
|
||||
@@ -187,12 +187,14 @@ impl OwnedCodeModeHost {
|
||||
.cloned()
|
||||
}
|
||||
|
||||
fn allocate_session_id(&self) -> SessionId {
|
||||
let value = self.next_session_id.fetch_add(1, Ordering::Relaxed);
|
||||
match SessionId::new(format!("session-{value}")) {
|
||||
fn allocate_session(&self) -> RemoteSession {
|
||||
// The shared ordinal also keeps cell IDs distinct after a thread reload.
|
||||
let generation = self.next_session_id.fetch_add(1, Ordering::Relaxed);
|
||||
let id = match SessionId::new(format!("session-{generation}")) {
|
||||
Ok(session_id) => session_id,
|
||||
Err(_) => unreachable!("a generated code-mode session ID is nonempty"),
|
||||
}
|
||||
};
|
||||
RemoteSession { id, generation }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +220,6 @@ struct SessionInner {
|
||||
host: Arc<OwnedCodeModeHost>,
|
||||
limits: CodeModeSessionCellExecutionLimits,
|
||||
state: StdMutex<SessionState>,
|
||||
next_generation: AtomicU64,
|
||||
shutdown_requested: AtomicBool,
|
||||
shutdown_result: StdMutex<Option<ShutdownResultReceiver>>,
|
||||
retired_cleanups: StdMutex<Vec<SessionCleanup>>,
|
||||
@@ -245,7 +246,6 @@ impl ProcessOwnedCodeModeSession {
|
||||
host,
|
||||
limits,
|
||||
state: StdMutex::new(SessionState::New),
|
||||
next_generation: AtomicU64::new(1),
|
||||
shutdown_requested: AtomicBool::new(false),
|
||||
shutdown_result: StdMutex::new(None),
|
||||
retired_cleanups: StdMutex::new(Vec::new()),
|
||||
@@ -312,11 +312,7 @@ impl SessionInner {
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
match &*state {
|
||||
SessionState::New => {
|
||||
let generation = self.next_generation.fetch_add(1, Ordering::Relaxed);
|
||||
let remote = RemoteSession {
|
||||
id: self.host.allocate_session_id(),
|
||||
generation,
|
||||
};
|
||||
let remote = self.host.allocate_session();
|
||||
let (result_tx, result_rx) = watch::channel(None);
|
||||
*state = SessionState::Opening {
|
||||
remote: remote.clone(),
|
||||
|
||||
Reference in New Issue
Block a user