Use workspace requirements for guardian prompt override (#14727)

## Summary
- move `guardian_developer_instructions` from managed config into
workspace-managed `requirements.toml`
- have guardian continue using the override when present and otherwise
fall back to the bundled local guardian prompt
- keep the generalized prompt-quality improvements in the shared
guardian default prompt
- update requirements parsing, layering, schema, and tests for the new
source of truth

## Context
This replaces the earlier managed-config / MDM rollout plan.

The intended rollout path is workspace-managed requirements, including
cloud enterprise policies, rather than backend model metadata, Statsig,
or Jamf-managed config. That keeps the default/fallback behavior local
to `codex-rs` while allowing faster policy updates through the
enterprise requirements plane.

This is intentionally an admin-managed policy input, not a user
preference: the guardian prompt should come either from the bundled
`codex-rs` default or from enterprise-managed `requirements.toml`, and
normal user/project/session config should not override it.

## Updating The OpenAI Prompt
After this lands, the OpenAI-specific guardian prompt should be updated
through the workspace Policies UI at `/codex/settings/policies` rather
than through Jamf or codex-backend model metadata.

Operationally:
- open the workspace Policies editor as a Codex admin
- edit the default `requirements.toml` policy, or a higher-precedence
group-scoped override if we ever want different behavior for a subset of
users
- set `guardian_developer_instructions = """..."""` to the full
OpenAI-specific guardian prompt text
- save the policy; codex-backend stores the raw TOML and `codex-rs`
fetches the effective requirements file from `/wham/config/requirements`

When updating the OpenAI-specific prompt, keep it aligned with the
shared default guardian policy in `codex-rs` except for intentional
OpenAI-only additions.

## Testing
- `cargo check --tests -p codex-core -p codex-config -p
codex-cloud-requirements --message-format short`
- `cargo run -p codex-core --bin codex-write-config-schema`
- `cargo fmt`
- `git diff --check`

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Charley Cunningham
2026-03-17 22:05:41 -07:00
committed by GitHub
parent 3ce879c646
commit 226241f035
14 changed files with 297 additions and 13 deletions

View File

@@ -299,6 +299,7 @@ pub struct ConfigRequirementsToml {
pub enforce_residency: Option<ResidencyRequirement>,
#[serde(rename = "experimental_network")]
pub network: Option<NetworkRequirementsToml>,
pub guardian_developer_instructions: Option<String>,
}
/// Value paired with the requirement source it came from, for better error
@@ -334,6 +335,7 @@ pub struct ConfigRequirementsWithSources {
pub rules: Option<Sourced<RequirementsExecPolicyToml>>,
pub enforce_residency: Option<Sourced<ResidencyRequirement>>,
pub network: Option<Sourced<NetworkRequirementsToml>>,
pub guardian_developer_instructions: Option<Sourced<String>>,
}
impl ConfigRequirementsWithSources {
@@ -364,9 +366,17 @@ impl ConfigRequirementsWithSources {
rules: _,
enforce_residency: _,
network: _,
guardian_developer_instructions: _,
} = &other;
let mut other = other;
if other
.guardian_developer_instructions
.as_deref()
.is_some_and(|value| value.trim().is_empty())
{
other.guardian_developer_instructions = None;
}
fill_missing_take!(
self,
other,
@@ -380,6 +390,7 @@ impl ConfigRequirementsWithSources {
rules,
enforce_residency,
network,
guardian_developer_instructions,
}
);
@@ -403,6 +414,7 @@ impl ConfigRequirementsWithSources {
rules,
enforce_residency,
network,
guardian_developer_instructions,
} = self;
ConfigRequirementsToml {
allowed_approval_policies: allowed_approval_policies.map(|sourced| sourced.value),
@@ -414,6 +426,8 @@ impl ConfigRequirementsWithSources {
rules: rules.map(|sourced| sourced.value),
enforce_residency: enforce_residency.map(|sourced| sourced.value),
network: network.map(|sourced| sourced.value),
guardian_developer_instructions: guardian_developer_instructions
.map(|sourced| sourced.value),
}
}
}
@@ -468,6 +482,10 @@ impl ConfigRequirementsToml {
&& self.rules.is_none()
&& self.enforce_residency.is_none()
&& self.network.is_none()
&& self
.guardian_developer_instructions
.as_deref()
.is_none_or(|value| value.trim().is_empty())
}
}
@@ -485,6 +503,7 @@ impl TryFrom<ConfigRequirementsWithSources> for ConfigRequirements {
rules,
enforce_residency,
network,
guardian_developer_instructions: _guardian_developer_instructions,
} = toml;
let approval_policy = match allowed_approval_policies {
@@ -705,6 +724,7 @@ mod tests {
rules,
enforce_residency,
network,
guardian_developer_instructions,
} = toml;
ConfigRequirementsWithSources {
allowed_approval_policies: allowed_approval_policies
@@ -721,6 +741,8 @@ mod tests {
enforce_residency: enforce_residency
.map(|value| Sourced::new(value, RequirementSource::Unknown)),
network: network.map(|value| Sourced::new(value, RequirementSource::Unknown)),
guardian_developer_instructions: guardian_developer_instructions
.map(|value| Sourced::new(value, RequirementSource::Unknown)),
}
}
@@ -743,6 +765,8 @@ mod tests {
};
let enforce_residency = ResidencyRequirement::Us;
let enforce_source = source.clone();
let guardian_developer_instructions =
"Use the company-managed guardian policy.".to_string();
// Intentionally constructed without `..Default::default()` so adding a new field to
// `ConfigRequirementsToml` forces this test to be updated.
@@ -756,6 +780,7 @@ mod tests {
rules: None,
enforce_residency: Some(enforce_residency),
network: None,
guardian_developer_instructions: Some(guardian_developer_instructions.clone()),
};
target.merge_unset_fields(source.clone(), other);
@@ -767,7 +792,7 @@ mod tests {
allowed_approval_policies,
source.clone()
)),
allowed_sandbox_modes: Some(Sourced::new(allowed_sandbox_modes, source)),
allowed_sandbox_modes: Some(Sourced::new(allowed_sandbox_modes, source.clone(),)),
allowed_web_search_modes: Some(Sourced::new(
allowed_web_search_modes,
enforce_source.clone(),
@@ -781,6 +806,10 @@ mod tests {
rules: None,
enforce_residency: Some(Sourced::new(enforce_residency, enforce_source)),
network: None,
guardian_developer_instructions: Some(Sourced::new(
guardian_developer_instructions,
source,
)),
}
);
}
@@ -815,6 +844,7 @@ mod tests {
rules: None,
enforce_residency: None,
network: None,
guardian_developer_instructions: None,
}
);
Ok(())
@@ -857,11 +887,78 @@ mod tests {
rules: None,
enforce_residency: None,
network: None,
guardian_developer_instructions: None,
}
);
Ok(())
}
#[test]
fn merge_unset_fields_ignores_blank_guardian_override() {
let mut target = ConfigRequirementsWithSources::default();
target.merge_unset_fields(
RequirementSource::CloudRequirements,
ConfigRequirementsToml {
guardian_developer_instructions: Some(" \n\t".to_string()),
..Default::default()
},
);
target.merge_unset_fields(
RequirementSource::SystemRequirementsToml {
file: system_requirements_toml_file_for_test()
.expect("system requirements.toml path"),
},
ConfigRequirementsToml {
guardian_developer_instructions: Some(
"Use the system guardian policy.".to_string(),
),
..Default::default()
},
);
assert_eq!(
target.guardian_developer_instructions,
Some(Sourced::new(
"Use the system guardian policy.".to_string(),
RequirementSource::SystemRequirementsToml {
file: system_requirements_toml_file_for_test()
.expect("system requirements.toml path"),
},
)),
);
}
#[test]
fn deserialize_guardian_developer_instructions() -> Result<()> {
let requirements: ConfigRequirementsToml = from_str(
r#"
guardian_developer_instructions = """
Use the cloud-managed guardian policy.
"""
"#,
)?;
assert_eq!(
requirements.guardian_developer_instructions.as_deref(),
Some("Use the cloud-managed guardian policy.\n")
);
Ok(())
}
#[test]
fn blank_guardian_developer_instructions_is_empty() -> Result<()> {
let requirements: ConfigRequirementsToml = from_str(
r#"
guardian_developer_instructions = """
"""
"#,
)?;
assert!(requirements.is_empty());
Ok(())
}
#[test]
fn deserialize_apps_requirements() -> Result<()> {
let toml_str = r#"