Decouple session isolation from subagent attribution (#44521)

## Why

Session isolation relied on Guardian source attribution. An explicit policy lets callers control inherited capabilities independently of how a session is attributed.

## What changed

- Add `SessionIsolation` with default `Inherit` and opt-in `Isolated` modes, captured at session startup through `ExtensionDataInit`.
- Use the policy to control inherited instructions, extensions, execution rules, and MCP resources. Isolated sessions retain managed execution rules and omit executor-discovered MCP servers.
- Explicitly isolate Guardian reviewers while preserving source-based fallback for older callers and saved reviewers.

## Testing

Extend delegate tests to cover isolation independently of attribution, update execution-policy coverage to supply the explicit policy, and assert that managed reviewers do not inherit the parent's configured MCP tools.

GitOrigin-RevId: f0ab42fa2f0237860ec661e72693ab191216afbf
This commit is contained in:
jif authored and copyberry committed 2026-09-10 12:52:27 +00:00
1 parent 9688359977
commit ed6dde9fda
14 files changed
+83 -18

No files matched your search

+1 -7
View File
@@ -135,12 +135,7 @@ impl Session {
)
.await;
let mcp_config = self
.project_selected_environment_mcp_servers(
&session_source,
config,
&environments,
mcp_projection,
)
.project_selected_environment_mcp_servers(config, &environments, mcp_projection)
.await
.config;
let local_process_cwd = environments
@@ -312,7 +307,6 @@ impl Session {
.await;
let mcp_projection = self
.project_selected_environment_mcp_servers(
&desired.session_source,
&desired.config,
&desired.environments,
mcp_projection,
+1 -3
View File
@@ -143,13 +143,12 @@ impl Session {
/// Adds effective executor-owned configuration from this exact thread snapshot.
pub(super) fn project_selected_environment_mcp_servers<'a>(
&'a self,
session_source: &'a SessionSource,
config: &'a Config,
environments: &'a TurnEnvironmentSnapshot,
mut projection: McpRuntimeProjection,
) -> BoxFuture<'a, McpRuntimeProjection> {
Box::pin(async move {
if crate::guardian::is_basic_session_source(session_source) {
if self.isolation == codex_extension_api::SessionIsolation::Isolated {
return projection;
}
@@ -306,7 +305,6 @@ impl Session {
) {
let mcp_projection = self
.project_selected_environment_mcp_servers(
&desired.session_source,
&desired.config,
&desired.environments,
mcp_projection,
+5 -1
View File
@@ -580,7 +580,11 @@ impl Session {
config
.startup_warnings
.extend(user_instruction_provider_warnings);
let exec_policy = if crate::guardian::is_basic_session_source(&session_source) {
let isolation = thread_extension_init
.get::<codex_extension_api::SessionIsolation>()
.map(|policy| *policy)
.unwrap_or_default();
let exec_policy = if isolation == codex_extension_api::SessionIsolation::Isolated {
let managed_policy = config
.config_layer_stack
.requirements()
+6
View File
@@ -58,6 +58,7 @@ pub(crate) struct Session {
/// session.
pub(super) features: ManagedFeatures,
pub(crate) guardian_context_mode: GuardianContextMode,
pub(super) isolation: codex_extension_api::SessionIsolation,
pub(crate) windows_sandbox_proxy_settings_mode:
codex_sandboxing::WindowsSandboxProxySettingsMode,
pub(super) multi_agent_version: OnceLock<MultiAgentVersion>,
@@ -860,6 +861,10 @@ impl Session {
// Publish the already resolved model before extensions make startup decisions.
// Turn construction refreshes this attachment when the selected model changes.
thread_extension_init.insert(model_info);
let isolation = thread_extension_init
.get::<codex_extension_api::SessionIsolation>()
.map(|policy| *policy)
.unwrap_or_default();
let mcp_thread_init = thread_extension_init.clone();
let thread_extension_data = codex_extension_api::ExtensionData::new_with_init(
thread_id.to_string(),
@@ -1582,6 +1587,7 @@ impl Session {
managed_network_proxy_refresh_lock: Semaphore::new(/*permits*/ 1),
features: config.features.clone(),
guardian_context_mode,
isolation,
windows_sandbox_proxy_settings_mode,
multi_agent_version,
mcp_refresh: McpRefresh::new(),
+2
View File
@@ -6701,6 +6701,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
managed_network_proxy_refresh_lock: Semaphore::new(/*permits*/ 1),
features: config.features.clone(),
guardian_context_mode: GuardianContextMode::from_features(&config.features),
isolation: codex_extension_api::SessionIsolation::Inherit,
windows_sandbox_proxy_settings_mode:
codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile,
multi_agent_version: OnceLock::from(config.multi_agent_version_from_features()),
@@ -8874,6 +8875,7 @@ where
managed_network_proxy_refresh_lock: Semaphore::new(/*permits*/ 1),
features: config.features.clone(),
guardian_context_mode: GuardianContextMode::from_features(&config.features),
isolation: codex_extension_api::SessionIsolation::Inherit,
windows_sandbox_proxy_settings_mode:
codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile,
multi_agent_version: OnceLock::from(config.multi_agent_version_from_features()),
@@ -1257,6 +1257,8 @@ async fn guardian_subagent_does_not_inherit_parent_exec_policy_rules() {
/*state_db*/ None,
));
let mut thread_extension_init = codex_extension_api::ExtensionDataInit::default();
thread_extension_init.insert(codex_extension_api::SessionIsolation::Isolated);
let (session, io) = Session::spawn(SessionSpawnArgs {
config,
allow_provider_model_fallback: false,
@@ -1291,7 +1293,7 @@ async fn guardian_subagent_does_not_inherit_parent_exec_policy_rules() {
user_shell_override: None,
parent_trace: None,
environment_selections: Vec::new(),
thread_extension_init: codex_extension_api::ExtensionDataInit::default(),
thread_extension_init,
client_mcp_extensions: ClientMcpExtensions::default(),
reserved_thread_id: None,
analytics_events_client: None,