mirror of
https://github.com/openai/codex.git
synced 2026-10-19 17:28:46 +00:00
## 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
103 lines
3.1 KiB
Rust
103 lines
3.1 KiB
Rust
use codex_core::CodexThread;
|
|
use codex_core::NewThread;
|
|
use codex_core::StartIfIdleSubmission;
|
|
use codex_core::StartThreadOptions;
|
|
use codex_core::ThreadManager;
|
|
use codex_core::TurnInputRequest;
|
|
use codex_core::config::Config;
|
|
use codex_protocol::ThreadId;
|
|
use codex_protocol::error::CodexErr;
|
|
use codex_protocol::error::Result as CodexResult;
|
|
use codex_protocol::protocol::W3cTraceContext;
|
|
use codex_protocol::user_input::UserInput;
|
|
use std::sync::Arc;
|
|
use std::sync::Weak;
|
|
|
|
/// A fully resolved agent invocation.
|
|
///
|
|
/// Agent discovery owns rendering `prompt`, including any selected skill
|
|
/// references. The runtime only starts that prompt in isolated forked context.
|
|
pub struct AgentInvocation {
|
|
pub config: Config,
|
|
pub prompt: String,
|
|
pub parent_trace: Option<W3cTraceContext>,
|
|
}
|
|
|
|
/// A spawned agent whose initial turn has been submitted.
|
|
pub struct AgentRun {
|
|
pub thread_id: ThreadId,
|
|
pub turn_id: String,
|
|
pub thread: Arc<CodexThread>,
|
|
}
|
|
|
|
/// Runs resolved agents in legacy threads forked by the owning [`ThreadManager`].
|
|
#[derive(Clone)]
|
|
pub struct AgentRunner {
|
|
thread_manager: Weak<ThreadManager>,
|
|
}
|
|
|
|
impl AgentRunner {
|
|
pub fn new(thread_manager: Weak<ThreadManager>) -> Self {
|
|
Self { thread_manager }
|
|
}
|
|
|
|
/// Starts a resolved agent in a fork of a legacy `parent_thread_id`.
|
|
///
|
|
/// Paginated parents are rejected by [`ThreadManager::spawn_legacy_subagent`].
|
|
pub async fn start(
|
|
&self,
|
|
parent_thread_id: ThreadId,
|
|
invocation: AgentInvocation,
|
|
) -> CodexResult<AgentRun> {
|
|
let AgentInvocation {
|
|
config,
|
|
prompt,
|
|
parent_trace,
|
|
} = invocation;
|
|
if prompt.trim().is_empty() {
|
|
return Err(CodexErr::InvalidRequest(
|
|
"agent prompt must not be empty".to_string(),
|
|
));
|
|
}
|
|
|
|
let thread_manager = self
|
|
.thread_manager
|
|
.upgrade()
|
|
.ok_or_else(|| CodexErr::UnsupportedOperation("thread manager dropped".to_string()))?;
|
|
let NewThread {
|
|
thread_id, thread, ..
|
|
} = thread_manager
|
|
.spawn_legacy_subagent(
|
|
parent_thread_id,
|
|
StartThreadOptions {
|
|
parent_trace: parent_trace.clone(),
|
|
..StartThreadOptions::new(config)
|
|
},
|
|
)
|
|
.await?;
|
|
let turn_id = match thread
|
|
.start_turn_if_idle(
|
|
TurnInputRequest::user_input(vec![UserInput::Text {
|
|
text: prompt,
|
|
text_elements: Vec::new(),
|
|
}])
|
|
.with_trace(parent_trace),
|
|
)
|
|
.await?
|
|
{
|
|
StartIfIdleSubmission::Started { turn_id, .. } => turn_id,
|
|
StartIfIdleSubmission::NotSubmitted { reason } => {
|
|
return Err(CodexErr::InvalidRequest(format!(
|
|
"agent prompt was not submitted: {reason:?}"
|
|
)));
|
|
}
|
|
};
|
|
|
|
Ok(AgentRun {
|
|
thread_id,
|
|
turn_id,
|
|
thread,
|
|
})
|
|
}
|
|
}
|