Record saved multi-agent opt-outs in session telemetry (#59478)

## Why

Saved `features.multi_agent = false` settings can be overridden by a higher-precedence session setting. Session telemetry should retain visibility into those saved opt-outs.

## What changed

Add `config.legacy_multi_agent_disabled_sources` to `codex.conversation_starts`, reporting opt-outs from user configuration, project configuration, and managed requirements. Emit comma-separated source labels, or `none` when no opt-outs are found.

## Testing

Add a regression test confirming that a user opt-out is still reported when a CLI override enables multi-agent support for the session.

GitOrigin-RevId: 488bd317d67e9ee87c5903ae51f466e0a63c05eb
This commit is contained in:
jif
2026-11-04 19:57:30 +00:00
committed by copyberry
parent b9099a145b
commit bd7cfa3f69
6 changed files with 67 additions and 0 deletions
+21
View File
@@ -10133,6 +10133,27 @@ job_max_runtime_seconds = 900
);
}
#[tokio::test]
async fn legacy_multi_agent_opt_out_survives_a_session_override() -> std::io::Result<()> {
let home = TempDir::new()?;
std::fs::write(
home.path().join(CONFIG_TOML_FILE),
"[features]\nmulti_agent = false\n",
)?;
let config = ConfigBuilder::without_managed_config_for_tests()
.codex_home(home.path().to_path_buf())
.fallback_cwd(Some(home.path().to_path_buf()))
.cli_overrides(vec![("features.multi_agent".to_string(), true.into())])
.build()
.await?;
assert!(config.features.enabled(Feature::Collab));
assert_eq!(
legacy_multi_agent_disabled_sources(&config.config_layer_stack),
["user"]
);
Ok(())
}
#[tokio::test]
async fn load_config_resolves_agent_controls() -> std::io::Result<()> {
let codex_home = TempDir::new()?;
+37
View File
@@ -1,6 +1,8 @@
//! Metrics derived from loaded configuration at session start.
use super::Config;
use codex_config::ConfigLayerSource;
use codex_config::ConfigLayerStack;
use codex_features::FEATURES;
use codex_features::Stage;
use codex_otel::SessionTelemetry;
@@ -24,3 +26,38 @@ pub(crate) fn emit_session_start_metrics(config: &Config, telemetry: &SessionTel
#[cfg(windows)]
crate::windows_system_config::emit_namespace_squatting_probe();
}
/// Report saved opt-outs even when a higher-precedence session setting overrides them.
pub(crate) fn legacy_multi_agent_disabled_sources(layers: &ConfigLayerStack) -> Vec<&'static str> {
let mut user = None;
let mut project = None;
for layer in layers.layers_low_to_high() {
let setting = match layer.name {
ConfigLayerSource::User { .. } => &mut user,
ConfigLayerSource::Project { .. } => &mut project,
_ => continue,
};
if let Some(value) = layer
.config
.get("features")
.and_then(|features| features.get("multi_agent"))
.and_then(toml::Value::as_bool)
{
*setting = Some(value);
}
}
let managed = layers
.requirements()
.feature_requirements
.as_ref()
.and_then(|requirements| requirements.value.entries.get("multi_agent"))
.copied();
[
("user", user),
("project", project),
("managed_requirement", managed),
]
.into_iter()
.filter_map(|(source, value)| (value == Some(false)).then_some(source))
.collect()
}
+1
View File
@@ -201,6 +201,7 @@ use codex_sandboxing::compatibility_sandbox_policy_for_permission_profile;
pub use codex_sandboxing::system_bwrap_warning;
pub use managed_features::ManagedFeatures;
pub(crate) use metrics::emit_session_start_metrics;
pub(crate) use metrics::legacy_multi_agent_disabled_sources;
pub use network_config::EnvironmentNetworkConfigError;
pub use network_config::NetworkConfigInputs;
pub use network_config::PreparedNetworkConfig;
+1
View File
@@ -1512,6 +1512,7 @@ impl Session {
.permissions
.legacy_sandbox_policy(session_configuration.cwd().as_path()),
mcp_server_names.iter().map(String::as_str).collect(),
&crate::config::legacy_multi_agent_disabled_sources(&config.config_layer_stack),
);
let default_shell = session_configuration
@@ -696,11 +696,17 @@ impl SessionTelemetry {
approval_policy: AskForApproval,
sandbox_policy: SandboxPolicy,
mcp_servers: Vec<&str>,
legacy_multi_agent_disabled_sources: &[&str],
) {
log_and_trace_event!(
self,
common: {
event.name = "codex.conversation_starts",
config.legacy_multi_agent_disabled_sources = if legacy_multi_agent_disabled_sources.is_empty() {
"none".to_string()
} else {
legacy_multi_agent_disabled_sources.join(",")
},
provider_name = %provider_name,
auth.env_openai_api_key_present = self.metadata.auth_env.openai_api_key_env_present,
auth.env_codex_api_key_present = self.metadata.auth_env.codex_api_key_env_present,
@@ -883,6 +883,7 @@ fn otel_export_routing_policy_routes_api_request_auth_observability() {
AskForApproval::Never,
SandboxPolicy::DangerFullAccess,
Vec::new(),
/*legacy_multi_agent_disabled_sources*/ &[],
);
let agent_identity_telemetry = AgentIdentityTelemetry {
agent_id: "agent-runtime-otel".to_string(),