Restore agent roles when reloading v2 sub-agents (#33657)

## Why

Durable v2 sub-agents can be lazily reloaded after their root session resumes. The reload path restored the agent identity but did not reapply the agent's selected role configuration.

## What changed

- Recover the agent role from the resumed session source and apply it before reloading the sub-agent.
- Preserve the runtime approval policy, approval reviewer, working directory, and permission profile while applying the role.

## Testing

Extend the cold-resume integration test to verify that the reloaded worker retains its role-defined instructions, model, provider, reasoning effort, and permission profile.

GitOrigin-RevId: a26dbbd2599838ca317326a1223a6a0a9cb2a2ed
This commit is contained in:
jif
2026-07-16 18:20:14 +00:00
committed by copyberry
parent 8a7c854bff
commit b7983c2a07
2 changed files with 113 additions and 21 deletions
+42 -4
View File
@@ -1,5 +1,7 @@
use super::residency::is_v2_resident_session_source;
use super::*;
use crate::agent::role::apply_role_to_config;
use crate::config::PermissionProfileSnapshot;
use codex_extension_api::ExtensionDataInit;
const AGENT_NAMES: &str = include_str!("../agent_names.txt");
@@ -247,7 +249,7 @@ impl AgentControl {
pub(crate) async fn ensure_v2_agent_loaded(
&self,
config: Config,
mut config: Config,
thread_id: ThreadId,
) -> CodexResult<()> {
let state = self.upgrade()?;
@@ -279,13 +281,49 @@ impl AgentControl {
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));
if let Some(role_name) = session_source.get_agent_role() {
let runtime_approval_policy = config.permissions.approval_policy.value();
let runtime_approvals_reviewer = config.approvals_reviewer;
let runtime_cwd = config.cwd.clone();
let runtime_permission_profile = match config.permissions.active_permission_profile() {
Some(active_permission_profile) => {
PermissionProfileSnapshot::active_with_profile_workspace_roots(
config.permissions.permission_profile().clone(),
active_permission_profile,
config.permissions.profile_workspace_roots().to_vec(),
)
}
None => PermissionProfileSnapshot::legacy(
config.permissions.permission_profile().clone(),
),
};
apply_role_to_config(&mut config, Some(&role_name))
.await
.map_err(CodexErr::InvalidRequest)?;
config
.permissions
.approval_policy
.set(runtime_approval_policy)
.map_err(|err| {
CodexErr::InvalidRequest(format!("approval_policy is invalid: {err}"))
})?;
config.approvals_reviewer = runtime_approvals_reviewer;
config.cwd = runtime_cwd;
config
.permissions
.set_permission_profile_from_session_snapshot(runtime_permission_profile)
.map_err(|err| {
CodexErr::InvalidRequest(format!("permission_profile is invalid: {err}"))
})?;
}
let residency_slot = self
.reserve_v2_residency_slot(&state, &config, Some(thread_id))
.await?;
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);