Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file not shown.
Binary file not shown.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions codex-rs/app-server-protocol/schema/typescript/v2/index.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions codex-rs/app-server-protocol/src/protocol/v2/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ pub struct ConfigRequirements {
pub enforce_residency: Option<ResidencyRequirement>,
#[experimental("configRequirements/read.network")]
pub network: Option<NetworkRequirements>,
pub auto_review: Option<AutoReviewRequirements>,
pub models: Option<ModelsRequirements>,
#[schemars(with = "Option<String>")]
pub sqlite_home: Option<PathUri>,
Expand All @@ -407,6 +408,13 @@ pub struct ConfigRequirements {
pub windows_sandbox_private_desktop: Option<bool>,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct AutoReviewRequirements {
pub required_on_models: Option<Vec<String>>,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
Expand Down
1 change: 1 addition & 0 deletions codex-rs/app-server-protocol/src/protocol/v2/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1987,6 +1987,7 @@ fn config_requirements_granular_allowed_approval_policy_is_marked_experimental()
hooks: None,
enforce_residency: None,
network: None,
auto_review: None,
models: None,
sqlite_home: None,
log_dir: None,
Expand Down
11 changes: 10 additions & 1 deletion codex-rs/app-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ Example with notification opt-out:
- `externalAgentConfig/import/readHistories` — read completed import histories and connector candidates detected from successfully imported session histories. Successful session entries include the original imported title when one was available. Connector candidates include a normalized display `name`, the number of imported sessions that used the connector, and the source metadata field used for detection.
- `config/value/write` — write a single config key/value to the user's config.toml on disk; dotted paths such as `desktop.someKey` use the same generic write surface. Writes that overlap a managed requirement are rejected with `configRequirementReadonly`.
- `config/batchWrite` — apply multiple config edits atomically to the user's config.toml on disk, with optional `reloadUserConfig: true` to hot-reload loaded threads, including multiple `desktop.*` edits. Session-static model, reasoning-effort, Plan-mode reasoning-effort, service-tier, and personality defaults do not reload existing threads.
- `configRequirements/read` — fetch loaded requirements constraints from `requirements.toml` and/or MDM (or `null` if none are configured), including exact managed values (`sqliteHome`, `logDir`, `modelCatalogJson`, `checkForUpdateOnStartup`, `allowLoginShell`, `feedback.enabled`, and `windowsSandboxPrivateDesktop`), allow-lists (`allowedApprovalPolicies`, `allowedSandboxModes`, `allowedWebSearchModes`), the layered permission-profile allow map (`allowedPermissionProfiles`), the managed permission-profile default (`defaultPermissions`), lifecycle hook lockdown (`allowManagedHooksOnly`), remote-control policy (`allowRemoteControl`; `false` force-disables remote control while `true` or `null` preserves existing behavior), computer use policy (`computerUse`), Browser Use policy (`browserUse.disableAutoReview`), pinned feature values (`featureRequirements`, including the default-allowed `in_app_updates` policy that administrators can set to `false`), managed lifecycle hooks (`hooks`, including command handlers with optional `additionalContextLimit` and `mcp_tool` handlers with `server`, `tool`, `input`, `timeoutSec`, and `statusMessage`), `enforceResidency`, managed new-thread defaults (`models.newThread.model`, `models.newThread.modelReasoningEffort`, and `models.newThread.serviceTier`), and `network` constraints such as canonical domain/socket permissions plus `managedAllowedDomainsOnly` and `dangerFullAccessDenylistOnly`.
- `configRequirements/read` — fetch loaded requirements constraints from `requirements.toml` and/or MDM (or `null` if none are configured), including exact managed values (`sqliteHome`, `logDir`, `modelCatalogJson`, `checkForUpdateOnStartup`, `allowLoginShell`, `feedback.enabled`, and `windowsSandboxPrivateDesktop`), allow-lists (`allowedApprovalPolicies`, `allowedSandboxModes`, `allowedWebSearchModes`), the layered permission-profile allow map (`allowedPermissionProfiles`), the managed permission-profile default (`defaultPermissions`), lifecycle hook lockdown (`allowManagedHooksOnly`), remote-control policy (`allowRemoteControl`; `false` force-disables remote control while `true` or `null` preserves existing behavior), computer use policy (`computerUse`), Browser Use policy (`browserUse.disableAutoReview`), pinned feature values (`featureRequirements`, including the default-allowed `in_app_updates` policy that administrators can set to `false`), managed lifecycle hooks (`hooks`, including command handlers with optional `additionalContextLimit` and `mcp_tool` handlers with `server`, `tool`, `input`, `timeoutSec`, and `statusMessage`), `enforceResidency`, managed automatic review (`autoReview.requiredOnModels`), model defaults (`models.newThread.model`, `models.newThread.modelReasoningEffort`, and `models.newThread.serviceTier`), and `network` constraints such as canonical domain/socket permissions plus `managedAllowedDomainsOnly` and `dangerFullAccessDenylistOnly`.

### Example: Start or resume a thread

Expand Down Expand Up @@ -866,6 +866,15 @@ You can optionally specify config overrides on the new turn. If specified, these
- `"user"` — default. Review approval requests directly in the client.
- `"auto_review"` — route approval requests to a carefully prompted subagent, which gathers relevant context and applies a risk-based decision framework before approving or denying the request. The legacy value `"guardian_subagent"` is still accepted for compatibility.

Managed `requirements.toml` can require automatic review for specific models:

```toml
[auto_review]
required_on_models = ["protected-model"]
```

Listed models always start with `approvalPolicy: "on-request"` and `approvalsReviewer: "auto_review"`, even when clients provide incompatible startup values. Full Access is automatically downgraded to workspace-write access. Incompatible runtime overrides or disabled Guardian automatic review are rejected.

```json
{ "method": "turn/start", "id": 30, "params": {
"threadId": "thr_123",
Expand Down
25 changes: 20 additions & 5 deletions codex-rs/app-server/src/request_processors/config_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::error_code::invalid_request;
use crate::outgoing_message::ConnectionRequestId;
use crate::outgoing_message::OutgoingMessageSender;
use codex_analytics::AnalyticsEventsClient;
use codex_app_server_protocol::AutoReviewRequirements;
use codex_app_server_protocol::BrowserUseRequirements;
use codex_app_server_protocol::ClientResponsePayload;
use codex_app_server_protocol::ComputerUseRequirements;
Expand Down Expand Up @@ -415,6 +416,11 @@ fn map_requirements_toml_to_api(requirements: ConfigRequirementsToml) -> ConfigR
.enforce_residency
.map(map_residency_requirement_to_api),
network: requirements.network.map(map_network_requirements_to_api),
auto_review: requirements
.auto_review
.map(|auto_review| AutoReviewRequirements {
required_on_models: auto_review.required_on_models,
}),
models: requirements.models.map(|models| ModelsRequirements {
new_thread: models.new_thread.map(|new_thread| NewThreadModelDefaults {
model: new_thread.model,
Expand Down Expand Up @@ -648,6 +654,7 @@ mod tests {
use super::map_requirements_toml_to_api;
use codex_app_server_protocol::FeedbackRequirements;
use codex_app_server_protocol::WindowsSandboxSetupMode;
use codex_config::AutoReviewRequirementsToml;
use codex_config::ComputerUseRequirementsToml;
use codex_config::ConfigRequirementsToml;
use codex_config::ModelsRequirementsToml;
Expand Down Expand Up @@ -717,8 +724,11 @@ mod tests {
}

#[test]
fn requirements_api_includes_new_thread_model_defaults() {
fn requirements_api_includes_model_auto_review_and_new_thread_defaults() {
let mapped = map_requirements_toml_to_api(ConfigRequirementsToml {
auto_review: Some(AutoReviewRequirementsToml {
required_on_models: Some(vec!["gpt-protected".to_string()]),
}),
models: Some(ModelsRequirementsToml {
new_thread: Some(NewThreadModelDefaultsToml {
model: Some("gpt-managed".to_string()),
Expand All @@ -729,10 +739,15 @@ mod tests {
..ConfigRequirementsToml::default()
});

let defaults = mapped
.models
.and_then(|models| models.new_thread)
.expect("new-thread defaults");
assert_eq!(
mapped
.auto_review
.expect("managed automatic-review requirements")
.required_on_models,
Some(vec!["gpt-protected".to_string()])
);
let models = mapped.models.expect("managed model requirements");
let defaults = models.new_thread.expect("new-thread defaults");
assert_eq!(defaults.model.as_deref(), Some("gpt-managed"));
assert_eq!(
defaults.model_reasoning_effort,
Expand Down
24 changes: 18 additions & 6 deletions codex-rs/app-server/tests/suite/v2/config_rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,11 +179,15 @@ in_app_updates = false
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn config_requirements_read_includes_new_thread_model_defaults() -> Result<()> {
async fn config_requirements_read_includes_model_auto_review_and_new_thread_defaults() -> Result<()>
{
let codex_home = TempDir::new()?;
std::fs::write(
codex_home.path().join("requirements.toml"),
r#"
[auto_review]
required_on_models = ["gpt-protected", "gpt-sensitive"]

[models.new_thread]
model = "gpt-managed"
model_reasoning_effort = "medium"
Expand All @@ -200,11 +204,19 @@ service_tier = "fast"
let response: ConfigRequirementsReadResponse =
timeout(DEFAULT_READ_TIMEOUT, mcp.read_response(request_id)).await??;

let defaults = response
.requirements
.and_then(|requirements| requirements.models)
.and_then(|models| models.new_thread)
.expect("managed new-thread defaults");
let requirements = response.requirements.expect("managed requirements");
assert_eq!(
requirements
.auto_review
.expect("managed automatic-review requirements")
.required_on_models,
Some(vec![
"gpt-protected".to_string(),
"gpt-sensitive".to_string()
])
);
let models = requirements.models.expect("managed model requirements");
let defaults = models.new_thread.expect("managed new-thread defaults");
assert_eq!(defaults.model.as_deref(), Some("gpt-managed"));
assert_eq!(
defaults.model_reasoning_effort,
Expand Down
1 change: 1 addition & 0 deletions codex-rs/app-server/tests/suite/v2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ mod mcp_server_elicitation;
mod mcp_server_status;
mod mcp_tool;
mod memory_reset;
mod model_auto_review;
mod model_list;
mod model_provider_capabilities_read;
mod multi_agent_v2_developer_instructions;
Expand Down
Loading
Loading