Compare commits

...

1 Commits

Author SHA1 Message Date
gt-oai
92e094f9b8 Add additional dev instructions 2026-02-06 17:54:26 +00:00
17 changed files with 153 additions and 4 deletions

View File

@@ -10957,6 +10957,12 @@
"type": "null"
}
]
},
"requiredDeveloperInstructions": {
"type": [
"string",
"null"
]
}
},
"type": "object"

View File

@@ -39,6 +39,12 @@
"type": "null"
}
]
},
"requiredDeveloperInstructions": {
"type": [
"string",
"null"
]
}
},
"type": "object"

View File

@@ -5,4 +5,4 @@ import type { AskForApproval } from "./AskForApproval";
import type { ResidencyRequirement } from "./ResidencyRequirement";
import type { SandboxMode } from "./SandboxMode";
export type ConfigRequirements = { allowedApprovalPolicies: Array<AskForApproval> | null, allowedSandboxModes: Array<SandboxMode> | null, enforceResidency: ResidencyRequirement | null, };
export type ConfigRequirements = { allowedApprovalPolicies: Array<AskForApproval> | null, allowedSandboxModes: Array<SandboxMode> | null, enforceResidency: ResidencyRequirement | null, requiredDeveloperInstructions: string | null, };

View File

@@ -501,6 +501,7 @@ pub struct ConfigRequirements {
pub allowed_approval_policies: Option<Vec<AskForApproval>>,
pub allowed_sandbox_modes: Option<Vec<SandboxMode>>,
pub enforce_residency: Option<ResidencyRequirement>,
pub required_developer_instructions: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]

View File

@@ -116,7 +116,7 @@ Example (from OpenAI's official VSCode extension):
- `config/read` — fetch the effective config on disk after resolving config layering.
- `config/value/write` — write a single config key/value to the user's config.toml on disk.
- `config/batchWrite` — apply multiple config edits atomically to the user's config.toml on disk.
- `configRequirements/read` — fetch the loaded requirements allow-lists and `enforceResidency` from `requirements.toml` and/or MDM (or `null` if none are configured).
- `configRequirements/read` — fetch the loaded requirements allow-lists, `enforceResidency`, and `requiredDeveloperInstructions` from `requirements.toml` and/or MDM (or `null` if none are configured).
### Example: Start or resume a thread

View File

@@ -118,6 +118,7 @@ fn map_requirements_toml_to_api(requirements: ConfigRequirementsToml) -> ConfigR
enforce_residency: requirements
.enforce_residency
.map(map_residency_requirement_to_api),
required_developer_instructions: requirements.additional_developer_instructions,
}
}
@@ -180,6 +181,7 @@ mod tests {
mcp_servers: None,
rules: None,
enforce_residency: Some(CoreResidencyRequirement::Us),
additional_developer_instructions: Some("Follow policy".to_string()),
};
let mapped = map_requirements_toml_to_api(requirements);
@@ -199,5 +201,9 @@ mod tests {
mapped.enforce_residency,
Some(codex_app_server_protocol::ResidencyRequirement::Us),
);
assert_eq!(
mapped.required_developer_instructions,
Some("Follow policy".to_string()),
);
}
}

View File

@@ -570,6 +570,7 @@ fn append_rollout_turn_context(path: &Path, timestamp: &str, model: &str) -> std
summary: ReasoningSummary::Auto,
user_instructions: None,
developer_instructions: None,
required_developer_instructions: None,
final_output_json_schema: None,
truncation_policy: None,
}),

View File

@@ -384,6 +384,7 @@ mod tests {
mcp_servers: None,
rules: None,
enforce_residency: None,
additional_developer_instructions: None,
})
);
}
@@ -424,6 +425,7 @@ mod tests {
mcp_servers: None,
rules: None,
enforce_residency: None,
additional_developer_instructions: None,
})
);
}
@@ -467,6 +469,7 @@ mod tests {
mcp_servers: None,
rules: None,
enforce_residency: None,
additional_developer_instructions: None,
})
);
assert_eq!(fetcher.request_count.load(Ordering::SeqCst), 2);

View File

@@ -369,6 +369,7 @@ impl Codex {
collaboration_mode,
model_reasoning_summary: config.model_reasoning_summary,
developer_instructions: config.developer_instructions.clone(),
required_developer_instructions: config.required_developer_instructions.clone(),
user_instructions,
personality: config.personality,
base_instructions,
@@ -518,6 +519,7 @@ pub(crate) struct TurnContext {
/// instead of `std::env::current_dir()`.
pub(crate) cwd: PathBuf,
pub(crate) developer_instructions: Option<String>,
pub(crate) required_developer_instructions: Constrained<Option<String>>,
pub(crate) compact_prompt: Option<String>,
pub(crate) user_instructions: Option<String>,
pub(crate) collaboration_mode: CollaborationMode,
@@ -599,6 +601,7 @@ pub(crate) struct SessionConfiguration {
/// Developer instructions that supplement the base instructions.
developer_instructions: Option<String>,
required_developer_instructions: Constrained<Option<String>>,
/// Model instructions that are appended to the base instructions.
user_instructions: Option<String>,
@@ -809,6 +812,9 @@ impl Session {
session_source,
cwd,
developer_instructions: session_configuration.developer_instructions.clone(),
required_developer_instructions: session_configuration
.required_developer_instructions
.clone(),
compact_prompt: session_configuration.compact_prompt.clone(),
user_instructions: session_configuration.user_instructions.clone(),
collaboration_mode: session_configuration.collaboration_mode.clone(),
@@ -2117,8 +2123,14 @@ impl Session {
)
.into(),
);
if let Some(developer_instructions) = turn_context.developer_instructions.as_deref() {
items.push(DeveloperInstructions::new(developer_instructions.to_string()).into());
if let Some(developer_instructions) = Self::merge_developer_instructions(
turn_context.developer_instructions.as_deref(),
turn_context
.required_developer_instructions
.get()
.as_deref(),
) {
items.push(DeveloperInstructions::new(developer_instructions).into());
}
// Add developer instructions from collaboration_mode if they exist and are non-empty
let (collaboration_mode, base_instructions) = {
@@ -2164,6 +2176,22 @@ impl Session {
items
}
fn merge_developer_instructions(
developer_instructions: Option<&str>,
required_developer_instructions: Option<&str>,
) -> Option<String> {
match (developer_instructions, required_developer_instructions) {
(Some(developer_instructions), Some(required_developer_instructions)) => Some(format!(
"{developer_instructions}\n\n{required_developer_instructions}"
)),
(Some(developer_instructions), None) => Some(developer_instructions.to_string()),
(None, Some(required_developer_instructions)) => {
Some(required_developer_instructions.to_string())
}
(None, None) => None,
}
}
pub(crate) async fn persist_rollout_items(&self, items: &[RolloutItem]) {
let recorder = {
let guard = self.services.rollout.lock().await;
@@ -3538,6 +3566,7 @@ async fn spawn_review_thread(
features: parent_turn_context.features.clone(),
ghost_snapshot: parent_turn_context.ghost_snapshot.clone(),
developer_instructions: None,
required_developer_instructions: Constrained::allow_any(None),
user_instructions: None,
compact_prompt: parent_turn_context.compact_prompt.clone(),
collaboration_mode: parent_turn_context.collaboration_mode.clone(),
@@ -4581,6 +4610,7 @@ async fn try_run_sampling_request(
summary: turn_context.reasoning_summary,
user_instructions: turn_context.user_instructions.clone(),
developer_instructions: turn_context.developer_instructions.clone(),
required_developer_instructions: turn_context.required_developer_instructions.get().clone(),
final_output_json_schema: turn_context.final_output_json_schema.clone(),
truncation_policy: Some(turn_context.truncation_policy.into()),
});
@@ -5401,6 +5431,7 @@ mod tests {
collaboration_mode,
model_reasoning_summary: config.model_reasoning_summary,
developer_instructions: config.developer_instructions.clone(),
required_developer_instructions: config.required_developer_instructions.clone(),
user_instructions: config.user_instructions.clone(),
personality: config.personality,
base_instructions: config
@@ -5484,6 +5515,7 @@ mod tests {
collaboration_mode,
model_reasoning_summary: config.model_reasoning_summary,
developer_instructions: config.developer_instructions.clone(),
required_developer_instructions: config.required_developer_instructions.clone(),
user_instructions: config.user_instructions.clone(),
personality: config.personality,
base_instructions: config
@@ -5757,6 +5789,7 @@ mod tests {
collaboration_mode,
model_reasoning_summary: config.model_reasoning_summary,
developer_instructions: config.developer_instructions.clone(),
required_developer_instructions: config.required_developer_instructions.clone(),
user_instructions: config.user_instructions.clone(),
personality: config.personality,
base_instructions: config
@@ -5887,6 +5920,7 @@ mod tests {
collaboration_mode,
model_reasoning_summary: config.model_reasoning_summary,
developer_instructions: config.developer_instructions.clone(),
required_developer_instructions: config.required_developer_instructions.clone(),
user_instructions: config.user_instructions.clone(),
personality: config.personality,
base_instructions: config

View File

@@ -105,6 +105,7 @@ async fn run_compact_task_inner(
summary: turn_context.reasoning_summary,
user_instructions: turn_context.user_instructions.clone(),
developer_instructions: turn_context.developer_instructions.clone(),
required_developer_instructions: turn_context.required_developer_instructions.get().clone(),
final_output_json_schema: turn_context.final_output_json_schema.clone(),
truncation_policy: Some(turn_context.truncation_policy.into()),
});

View File

@@ -178,6 +178,8 @@ pub struct Config {
/// Developer instructions override injected as a separate message.
pub developer_instructions: Option<String>,
/// Required developer instructions from requirements.
pub required_developer_instructions: Constrained<Option<String>>,
/// Compact prompt override.
pub compact_prompt: Option<String>,
@@ -1624,6 +1626,7 @@ impl Config {
mcp_servers,
exec_policy: _,
enforce_residency,
required_developer_instructions,
} = requirements;
apply_requirement_constrained_value(
@@ -1662,6 +1665,7 @@ impl Config {
base_instructions,
personality,
developer_instructions,
required_developer_instructions: required_developer_instructions.value,
compact_prompt,
// The config.toml omits "_mode" because it's a config file. However, "_mode"
// is important in code to differentiate the mode from the store implementation.
@@ -3974,6 +3978,7 @@ model_verbosity = "high"
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
base_instructions: None,
developer_instructions: None,
required_developer_instructions: Constrained::allow_any(None),
compact_prompt: None,
forced_chatgpt_workspace_id: None,
forced_login_method: None,
@@ -4062,6 +4067,7 @@ model_verbosity = "high"
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
base_instructions: None,
developer_instructions: None,
required_developer_instructions: Constrained::allow_any(None),
compact_prompt: None,
forced_chatgpt_workspace_id: None,
forced_login_method: None,
@@ -4165,6 +4171,7 @@ model_verbosity = "high"
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
base_instructions: None,
developer_instructions: None,
required_developer_instructions: Constrained::allow_any(None),
compact_prompt: None,
forced_chatgpt_workspace_id: None,
forced_login_method: None,
@@ -4254,6 +4261,7 @@ model_verbosity = "high"
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
base_instructions: None,
developer_instructions: None,
required_developer_instructions: Constrained::allow_any(None),
compact_prompt: None,
forced_chatgpt_workspace_id: None,
forced_login_method: None,
@@ -4808,6 +4816,7 @@ mcp_oauth_callback_port = 5678
mcp_servers: None,
rules: None,
enforce_residency: None,
additional_developer_instructions: None,
};
let config = ConfigBuilder::default()

View File

@@ -79,6 +79,7 @@ pub struct ConfigRequirements {
pub mcp_servers: Option<Sourced<BTreeMap<String, McpServerRequirement>>>,
pub(crate) exec_policy: Option<Sourced<RequirementsExecPolicy>>,
pub enforce_residency: ConstrainedWithSource<Option<ResidencyRequirement>>,
pub required_developer_instructions: ConstrainedWithSource<Option<String>>,
}
impl Default for ConfigRequirements {
@@ -95,6 +96,10 @@ impl Default for ConfigRequirements {
mcp_servers: None,
exec_policy: None,
enforce_residency: ConstrainedWithSource::new(Constrained::allow_any(None), None),
required_developer_instructions: ConstrainedWithSource::new(
Constrained::allow_any(None),
None,
),
}
}
}
@@ -125,6 +130,7 @@ pub struct ConfigRequirementsToml {
pub mcp_servers: Option<BTreeMap<String, McpServerRequirement>>,
pub rules: Option<RequirementsExecPolicyToml>,
pub enforce_residency: Option<ResidencyRequirement>,
pub additional_developer_instructions: Option<String>,
}
/// Value paired with the requirement source it came from, for better error
@@ -156,6 +162,7 @@ pub struct ConfigRequirementsWithSources {
pub mcp_servers: Option<Sourced<BTreeMap<String, McpServerRequirement>>>,
pub rules: Option<Sourced<RequirementsExecPolicyToml>>,
pub enforce_residency: Option<Sourced<ResidencyRequirement>>,
pub additional_developer_instructions: Option<Sourced<String>>,
}
impl ConfigRequirementsWithSources {
@@ -189,6 +196,7 @@ impl ConfigRequirementsWithSources {
mcp_servers,
rules,
enforce_residency,
additional_developer_instructions,
}
);
}
@@ -200,6 +208,7 @@ impl ConfigRequirementsWithSources {
mcp_servers,
rules,
enforce_residency,
additional_developer_instructions,
} = self;
ConfigRequirementsToml {
allowed_approval_policies: allowed_approval_policies.map(|sourced| sourced.value),
@@ -207,6 +216,8 @@ impl ConfigRequirementsWithSources {
mcp_servers: mcp_servers.map(|sourced| sourced.value),
rules: rules.map(|sourced| sourced.value),
enforce_residency: enforce_residency.map(|sourced| sourced.value),
additional_developer_instructions: additional_developer_instructions
.map(|sourced| sourced.value),
}
}
}
@@ -251,6 +262,7 @@ impl ConfigRequirementsToml {
&& self.mcp_servers.is_none()
&& self.rules.is_none()
&& self.enforce_residency.is_none()
&& self.additional_developer_instructions.is_none()
}
}
@@ -264,6 +276,7 @@ impl TryFrom<ConfigRequirementsWithSources> for ConfigRequirements {
mcp_servers,
rules,
enforce_residency,
additional_developer_instructions,
} = toml;
let approval_policy = match allowed_approval_policies {
@@ -380,12 +393,36 @@ impl TryFrom<ConfigRequirementsWithSources> for ConfigRequirements {
}
None => ConstrainedWithSource::new(Constrained::allow_any(None), None),
};
let required_developer_instructions = match additional_developer_instructions {
Some(Sourced {
value: required_developer_instructions,
source: requirement_source,
}) => {
let required = Some(required_developer_instructions);
let requirement_source_for_error = requirement_source.clone();
let constrained = Constrained::new(required.clone(), move |candidate| {
if candidate == &required {
Ok(())
} else {
Err(ConstraintError::InvalidValue {
field_name: "required_developer_instructions",
candidate: format!("{candidate:?}"),
allowed: format!("{required:?}"),
requirement_source: requirement_source_for_error.clone(),
})
}
})?;
ConstrainedWithSource::new(constrained, Some(requirement_source))
}
None => ConstrainedWithSource::new(Constrained::allow_any(None), None),
};
Ok(ConfigRequirements {
approval_policy,
sandbox_policy,
mcp_servers,
exec_policy,
enforce_residency,
required_developer_instructions,
})
}
}
@@ -413,6 +450,7 @@ mod tests {
mcp_servers,
rules,
enforce_residency,
additional_developer_instructions,
} = toml;
ConfigRequirementsWithSources {
allowed_approval_policies: allowed_approval_policies
@@ -423,6 +461,8 @@ mod tests {
rules: rules.map(|value| Sourced::new(value, RequirementSource::Unknown)),
enforce_residency: enforce_residency
.map(|value| Sourced::new(value, RequirementSource::Unknown)),
additional_developer_instructions: additional_developer_instructions
.map(|value| Sourced::new(value, RequirementSource::Unknown)),
}
}
@@ -437,7 +477,9 @@ mod tests {
SandboxModeRequirement::DangerFullAccess,
];
let enforce_residency = ResidencyRequirement::Us;
let required_developer_instructions = "use concise responses".to_string();
let enforce_source = source.clone();
let additional_source = source.clone();
// Intentionally constructed without `..Default::default()` so adding a new field to
// `ConfigRequirementsToml` forces this test to be updated.
@@ -447,6 +489,7 @@ mod tests {
mcp_servers: None,
rules: None,
enforce_residency: Some(enforce_residency),
additional_developer_instructions: Some(required_developer_instructions.clone()),
};
target.merge_unset_fields(source.clone(), other);
@@ -462,6 +505,10 @@ mod tests {
mcp_servers: None,
rules: None,
enforce_residency: Some(Sourced::new(enforce_residency, enforce_source)),
additional_developer_instructions: Some(Sourced::new(
required_developer_instructions,
additional_source,
)),
}
);
}
@@ -492,6 +539,7 @@ mod tests {
mcp_servers: None,
rules: None,
enforce_residency: None,
additional_developer_instructions: None,
}
);
Ok(())
@@ -530,6 +578,7 @@ mod tests {
mcp_servers: None,
rules: None,
enforce_residency: None,
additional_developer_instructions: None,
}
);
Ok(())
@@ -616,6 +665,7 @@ mod tests {
allowed_approval_policies = ["on-request"]
allowed_sandbox_modes = ["read-only"]
enforce_residency = "us"
additional_developer_instructions = "follow policy"
"#,
)?;
@@ -633,6 +683,13 @@ mod tests {
Some(source_location.clone())
);
assert_eq!(requirements.enforce_residency.source, Some(source_location));
assert_eq!(
requirements
.required_developer_instructions
.get()
.as_deref(),
Some("follow policy")
);
Ok(())
}

View File

@@ -539,6 +539,7 @@ allowed_approval_policies = ["on-request"]
mcp_servers: None,
rules: None,
enforce_residency: None,
additional_developer_instructions: None,
})
}),
)
@@ -585,6 +586,7 @@ allowed_approval_policies = ["on-request"]
mcp_servers: None,
rules: None,
enforce_residency: None,
additional_developer_instructions: None,
},
);
load_requirements_toml(&mut config_requirements_toml, &requirements_file).await?;
@@ -620,6 +622,7 @@ async fn load_config_layers_includes_cloud_requirements() -> anyhow::Result<()>
mcp_servers: None,
rules: None,
enforce_residency: None,
additional_developer_instructions: None,
};
let expected = requirements.clone();
let cloud_requirements = CloudRequirementsLoader::new(async move { Some(requirements) });

View File

@@ -32,6 +32,7 @@ fn resume_history(
summary: config.model_reasoning_summary,
user_instructions: None,
developer_instructions: None,
required_developer_instructions: None,
final_output_json_schema: None,
truncation_policy: None,
};

View File

@@ -1734,6 +1734,8 @@ pub struct TurnContextItem {
#[serde(skip_serializing_if = "Option::is_none")]
pub developer_instructions: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub required_developer_instructions: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub final_output_json_schema: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub truncation_policy: Option<TruncationPolicy>,

View File

@@ -99,6 +99,16 @@ fn render_debug_config_lines(stack: &ConfigLayerStack) -> Vec<Line<'static>> {
));
}
if let Some(required_developer_instructions) =
requirements_toml.additional_developer_instructions.as_ref()
{
requirement_lines.push(requirement_line(
"required_developer_instructions",
required_developer_instructions.clone(),
requirements.required_developer_instructions.source.as_ref(),
));
}
if requirement_lines.is_empty() {
lines.push(" <none>".dim().into());
} else {
@@ -287,6 +297,10 @@ mod tests {
Constrained::allow_any(Some(ResidencyRequirement::Us)),
Some(RequirementSource::CloudRequirements),
);
requirements.required_developer_instructions = ConstrainedWithSource::new(
Constrained::allow_any(Some("Always include source links.".to_string())),
Some(RequirementSource::CloudRequirements),
);
let requirements_toml = ConfigRequirementsToml {
allowed_approval_policies: Some(vec![AskForApproval::OnRequest]),
@@ -301,6 +315,7 @@ mod tests {
)])),
rules: None,
enforce_residency: Some(ResidencyRequirement::Us),
additional_developer_instructions: Some("Always include source links.".to_string()),
};
let user_file = if cfg!(windows) {
@@ -333,6 +348,9 @@ mod tests {
);
assert!(rendered.contains("mcp_servers: docs (source: MDM managed_config.toml (legacy))"));
assert!(rendered.contains("enforce_residency: us (source: cloud requirements)"));
assert!(rendered.contains(
"required_developer_instructions: Always include source links. (source: cloud requirements)"
));
assert!(!rendered.contains(" - rules:"));
}
}

View File

@@ -1027,6 +1027,7 @@ mod tests {
summary: config.model_reasoning_summary,
user_instructions: None,
developer_instructions: None,
required_developer_instructions: None,
final_output_json_schema: None,
truncation_policy: None,
}