mirror of
https://github.com/openai/codex.git
synced 2026-11-07 16:35:32 +00:00
feat: reload v2 agents on delivery (#26623)
## Summary This is the first small step toward making multi-agent v2 agents durable logical agents whose `ThreadManager` residency is only an implementation detail. This PR adds a narrow v2 reload-on-delivery hook: - If a known v2 agent target is already loaded, delivery is unchanged. - If the target is still registered but missing from `ThreadManager`, delivery reloads that exact v2 thread from durable rollout history before submitting the message. - If the target is unknown, closed, missing from storage, or not a v2 thread, delivery still fails as not found. The reload is wired only into existing-agent delivery paths: v2 `send_message` / `followup_task`, and legacy `send_input` when its target is a known v2 agent. ## Stack 1. **Reload on delivery**: load known unloaded v2 agents before `followup_task`, `send_message`, or `send_input` delivery. This PR. 2. **Residency LRU**: unload idle resident v2 agents from `ThreadManager` without making them closed or unreachable. 3. **Execution concurrency**: count active non-root turns, not logical agents or resident idle threads. 4. **Close semantics**: make v2 close interrupt-only and leave durable agent identity intact. 5. **Resume cleanup**: remove user-facing v2 resume semantics; addressing an unloaded durable agent reloads it implicitly. ## Validation - Ran `just fmt`. - Left broader tests and clippy to CI.
This commit is contained in:
4 files changed
+206
-6
No files matched your search
@@ -109,6 +109,79 @@ impl AgentControl {
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn ensure_v2_agent_loaded(
|
||||
&self,
|
||||
config: Config,
|
||||
thread_id: ThreadId,
|
||||
) -> CodexResult<()> {
|
||||
let state = self.upgrade()?;
|
||||
if state.get_thread(thread_id).await.is_ok() {
|
||||
return Ok(());
|
||||
}
|
||||
if self.state.agent_metadata_for_thread(thread_id).is_none() {
|
||||
return Err(CodexErr::ThreadNotFound(thread_id));
|
||||
}
|
||||
|
||||
let stored_thread = state
|
||||
.read_stored_thread(ReadThreadParams {
|
||||
thread_id,
|
||||
include_archived: true,
|
||||
include_history: true,
|
||||
})
|
||||
.await?;
|
||||
let stored_source = stored_thread.source.clone();
|
||||
let stored_parent_thread_id = stored_thread.parent_thread_id;
|
||||
let history = stored_thread
|
||||
.history
|
||||
.ok_or(CodexErr::ThreadNotFound(thread_id))?
|
||||
.items;
|
||||
let initial_history = InitialHistory::Resumed(ResumedHistory {
|
||||
conversation_id: thread_id,
|
||||
history,
|
||||
rollout_path: stored_thread.rollout_path,
|
||||
});
|
||||
if initial_history.get_multi_agent_version() != Some(MultiAgentVersion::V2) {
|
||||
return Err(CodexErr::ThreadNotFound(thread_id));
|
||||
}
|
||||
|
||||
let (session_source, _) = initial_history
|
||||
.get_resumed_session_sources()
|
||||
.unwrap_or((stored_source, None));
|
||||
let parent_thread_id = initial_history
|
||||
.get_resumed_parent_thread_id()
|
||||
.or(stored_parent_thread_id);
|
||||
let inherited_shell_snapshot = self
|
||||
.inherited_shell_snapshot_for_source(&state, Some(&session_source))
|
||||
.await;
|
||||
let inherited_exec_policy = self
|
||||
.inherited_exec_policy_for_source(&state, Some(&session_source), &config)
|
||||
.await;
|
||||
|
||||
match state
|
||||
.resume_thread_with_history_with_source(ResumeThreadWithHistoryOptions {
|
||||
config,
|
||||
initial_history,
|
||||
agent_control: self.clone(),
|
||||
session_source,
|
||||
parent_thread_id,
|
||||
inherited_shell_snapshot,
|
||||
inherited_exec_policy,
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(reloaded_thread) => {
|
||||
state.notify_thread_created(reloaded_thread.thread_id);
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => {
|
||||
if state.get_thread(thread_id).await.is_ok() {
|
||||
return Ok(());
|
||||
}
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn spawn_agent_internal(
|
||||
&self,
|
||||
config: Config,
|
||||
|
||||
Reference in new issue
Block a user