Add vanilla context mode

This commit is contained in:
Gustav von Zitzewitz
2026-05-03 15:17:02 +02:00
parent 35aaa5d9fc
commit 9885b406a7
44 changed files with 657 additions and 67 deletions
@@ -533,6 +533,13 @@
}
]
},
"ContextMode": {
"enum": [
"default",
"vanilla"
],
"type": "string"
},
"DeviceKeyCreateParams": {
"description": "Create a controller-local device key with a random key id.",
"properties": {
@@ -3457,6 +3464,16 @@
"null"
]
},
"contextMode": {
"anyOf": [
{
"$ref": "#/definitions/ContextMode"
},
{
"type": "null"
}
]
},
"cwd": {
"type": [
"string",
@@ -3861,6 +3878,16 @@
"null"
]
},
"contextMode": {
"anyOf": [
{
"$ref": "#/definitions/ContextMode"
},
{
"type": "null"
}
]
},
"cwd": {
"type": [
"string",
@@ -4039,6 +4066,16 @@
"null"
]
},
"contextMode": {
"anyOf": [
{
"$ref": "#/definitions/ContextMode"
},
{
"type": "null"
}
]
},
"cwd": {
"type": [
"string",
@@ -7820,6 +7820,13 @@
"title": "ContextCompactedNotification",
"type": "object"
},
"ContextMode": {
"enum": [
"default",
"vanilla"
],
"type": "string"
},
"CreditsSnapshot": {
"properties": {
"balance": {
@@ -15123,6 +15130,16 @@
"null"
]
},
"contextMode": {
"anyOf": [
{
"$ref": "#/definitions/v2/ContextMode"
},
{
"type": "null"
}
]
},
"cwd": {
"type": [
"string",
@@ -16623,6 +16640,16 @@
"null"
]
},
"contextMode": {
"anyOf": [
{
"$ref": "#/definitions/v2/ContextMode"
},
{
"type": "null"
}
]
},
"cwd": {
"type": [
"string",
@@ -16912,6 +16939,16 @@
"null"
]
},
"contextMode": {
"anyOf": [
{
"$ref": "#/definitions/v2/ContextMode"
},
{
"type": "null"
}
]
},
"cwd": {
"type": [
"string",
@@ -4318,6 +4318,13 @@
"title": "ContextCompactedNotification",
"type": "object"
},
"ContextMode": {
"enum": [
"default",
"vanilla"
],
"type": "string"
},
"CreditsSnapshot": {
"properties": {
"balance": {
@@ -13009,6 +13016,16 @@
"null"
]
},
"contextMode": {
"anyOf": [
{
"$ref": "#/definitions/ContextMode"
},
{
"type": "null"
}
]
},
"cwd": {
"type": [
"string",
@@ -14509,6 +14526,16 @@
"null"
]
},
"contextMode": {
"anyOf": [
{
"$ref": "#/definitions/ContextMode"
},
{
"type": "null"
}
]
},
"cwd": {
"type": [
"string",
@@ -14798,6 +14825,16 @@
"null"
]
},
"contextMode": {
"anyOf": [
{
"$ref": "#/definitions/ContextMode"
},
{
"type": "null"
}
]
},
"cwd": {
"type": [
"string",
@@ -64,6 +64,13 @@
}
]
},
"ContextMode": {
"enum": [
"default",
"vanilla"
],
"type": "string"
},
"PermissionProfileModificationParams": {
"oneOf": [
{
@@ -175,6 +182,16 @@
"null"
]
},
"contextMode": {
"anyOf": [
{
"$ref": "#/definitions/ContextMode"
},
{
"type": "null"
}
]
},
"cwd": {
"type": [
"string",
@@ -138,6 +138,13 @@
}
]
},
"ContextMode": {
"enum": [
"default",
"vanilla"
],
"type": "string"
},
"FunctionCallOutputBody": {
"anyOf": [
{
@@ -1033,6 +1040,16 @@
"null"
]
},
"contextMode": {
"anyOf": [
{
"$ref": "#/definitions/ContextMode"
},
{
"type": "null"
}
]
},
"cwd": {
"type": [
"string",
@@ -64,6 +64,13 @@
}
]
},
"ContextMode": {
"enum": [
"default",
"vanilla"
],
"type": "string"
},
"DynamicToolSpec": {
"properties": {
"deferLoading": {
@@ -230,6 +237,16 @@
"null"
]
},
"contextMode": {
"anyOf": [
{
"$ref": "#/definitions/ContextMode"
},
{
"type": "null"
}
]
},
"cwd": {
"type": [
"string",
@@ -0,0 +1,5 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ContextMode = "default" | "vanilla";
@@ -5,6 +5,7 @@ import type { ServiceTier } from "../ServiceTier";
import type { JsonValue } from "../serde_json/JsonValue";
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
import type { AskForApproval } from "./AskForApproval";
import type { ContextMode } from "./ContextMode";
import type { SandboxMode } from "./SandboxMode";
/**
@@ -23,4 +24,4 @@ model?: string | null, modelProvider?: string | null, serviceTier?: ServiceTier
* Override where approval requests are routed for review on this thread
* and subsequent turns.
*/
approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, ephemeral?: boolean};
approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, contextMode?: ContextMode | null, ephemeral?: boolean};
@@ -6,6 +6,7 @@ import type { ServiceTier } from "../ServiceTier";
import type { JsonValue } from "../serde_json/JsonValue";
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
import type { AskForApproval } from "./AskForApproval";
import type { ContextMode } from "./ContextMode";
import type { SandboxMode } from "./SandboxMode";
/**
@@ -26,4 +27,4 @@ model?: string | null, modelProvider?: string | null, serviceTier?: ServiceTier
* Override where approval requests are routed for review on this thread
* and subsequent turns.
*/
approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, personality?: Personality | null};
approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, contextMode?: ContextMode | null, personality?: Personality | null};
@@ -6,6 +6,7 @@ import type { ServiceTier } from "../ServiceTier";
import type { JsonValue } from "../serde_json/JsonValue";
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
import type { AskForApproval } from "./AskForApproval";
import type { ContextMode } from "./ContextMode";
import type { SandboxMode } from "./SandboxMode";
import type { ThreadStartSource } from "./ThreadStartSource";
@@ -13,4 +14,4 @@ export type ThreadStartParams = {model?: string | null, modelProvider?: string |
* Override where approval requests are routed for review on this thread
* and subsequent turns.
*/
approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, serviceName?: string | null, baseInstructions?: string | null, developerInstructions?: string | null, personality?: Personality | null, ephemeral?: boolean | null, sessionStartSource?: ThreadStartSource | null};
approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, serviceName?: string | null, baseInstructions?: string | null, developerInstructions?: string | null, contextMode?: ContextMode | null, personality?: Personality | null, ephemeral?: boolean | null, sessionStartSource?: ThreadStartSource | null};
@@ -77,6 +77,7 @@ export type { ConfigWriteResponse } from "./ConfigWriteResponse";
export type { ConfiguredHookHandler } from "./ConfiguredHookHandler";
export type { ConfiguredHookMatcherGroup } from "./ConfiguredHookMatcherGroup";
export type { ContextCompactedNotification } from "./ContextCompactedNotification";
export type { ContextMode } from "./ContextMode";
export type { CreditsSnapshot } from "./CreditsSnapshot";
export type { DeprecationNoticeNotification } from "./DeprecationNoticeNotification";
export type { DeviceKeyAlgorithm } from "./DeviceKeyAlgorithm";
@@ -21,6 +21,7 @@ use codex_protocol::approvals::NetworkPolicyRuleAction as CoreNetworkPolicyRuleA
use codex_protocol::config_types::ApprovalsReviewer as CoreApprovalsReviewer;
use codex_protocol::config_types::CollaborationMode;
use codex_protocol::config_types::CollaborationModeMask as CoreCollaborationModeMask;
use codex_protocol::config_types::ContextMode as CoreContextMode;
use codex_protocol::config_types::ForcedLoginMethod;
use codex_protocol::config_types::ModeKind;
use codex_protocol::config_types::Personality;
@@ -414,6 +415,13 @@ impl From<CoreSandboxMode> for SandboxMode {
}
}
v2_enum_from_core!(
pub enum ContextMode from CoreContextMode {
Default,
Vanilla
}
);
v2_enum_from_core!(
pub enum ReviewDelivery from codex_protocol::protocol::ReviewDelivery {
Inline, Detached
@@ -3584,6 +3592,8 @@ pub struct ThreadStartParams {
#[ts(optional = nullable)]
pub developer_instructions: Option<String>,
#[ts(optional = nullable)]
pub context_mode: Option<ContextMode>,
#[ts(optional = nullable)]
pub personality: Option<Personality>,
#[ts(optional = nullable)]
pub ephemeral: Option<bool>,
@@ -3735,6 +3745,8 @@ pub struct ThreadResumeParams {
#[ts(optional = nullable)]
pub developer_instructions: Option<String>,
#[ts(optional = nullable)]
pub context_mode: Option<ContextMode>,
#[ts(optional = nullable)]
pub personality: Option<Personality>,
/// When true, return only thread metadata and live-resume state without
/// populating `thread.turns`. This is useful when the client plans to call
@@ -3839,6 +3851,8 @@ pub struct ThreadForkParams {
pub base_instructions: Option<String>,
#[ts(optional = nullable)]
pub developer_instructions: Option<String>,
#[ts(optional = nullable)]
pub context_mode: Option<ContextMode>,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub ephemeral: bool,
/// When true, return only thread metadata and live fork state without
@@ -262,6 +262,7 @@ use codex_core::ThreadConfigSnapshot;
use codex_core::ThreadManager;
use codex_core::config::Config;
use codex_core::config::ConfigOverrides;
use codex_core::config::ContextMode as CoreContextMode;
use codex_core::config::NetworkProxyAuditMetadata;
use codex_core::config::edit::ConfigEdit;
use codex_core::config::edit::ConfigEditsBuilder;
@@ -706,6 +707,9 @@ fn environment_selection_error_message(err: CodexErr) -> String {
impl CodexMessageProcessor {
async fn instruction_sources_from_config(config: &Config) -> Vec<AbsolutePathBuf> {
if !config.context_mode.includes_context_enrichment() {
return Vec::new();
}
codex_core::AgentsMdManager::new(config)
.instruction_sources(LOCAL_FS.as_ref())
.await
@@ -2510,6 +2514,7 @@ impl CodexMessageProcessor {
service_name,
base_instructions,
developer_instructions,
context_mode,
dynamic_tools,
mock_experimental_field: _mock_experimental_field,
experimental_raw_events,
@@ -2546,6 +2551,7 @@ impl CodexMessageProcessor {
permissions,
base_instructions,
developer_instructions,
context_mode,
personality,
);
typesafe_overrides.ephemeral = ephemeral;
@@ -2605,6 +2611,7 @@ impl CodexMessageProcessor {
/*permissions*/ None,
/*base_instructions*/ None,
/*developer_instructions*/ None,
/*context_mode*/ None,
/*personality*/ None,
);
let config = self
@@ -2975,6 +2982,7 @@ impl CodexMessageProcessor {
permissions: Option<PermissionProfileSelectionParams>,
base_instructions: Option<String>,
developer_instructions: Option<String>,
context_mode: Option<codex_app_server_protocol::ContextMode>,
personality: Option<Personality>,
) -> ConfigOverrides {
let mut overrides = ConfigOverrides {
@@ -2991,6 +2999,7 @@ impl CodexMessageProcessor {
main_execve_wrapper_exe: self.arg0_paths.main_execve_wrapper_exe.clone(),
base_instructions,
developer_instructions,
context_mode: context_mode.map(core_context_mode),
personality,
..Default::default()
};
@@ -4355,6 +4364,7 @@ impl CodexMessageProcessor {
config: mut request_overrides,
base_instructions,
developer_instructions,
context_mode,
personality,
exclude_turns,
persist_extended_history,
@@ -4389,6 +4399,7 @@ impl CodexMessageProcessor {
permissions,
base_instructions,
developer_instructions,
context_mode,
personality,
);
self.load_and_apply_persisted_resume_metadata(
@@ -4397,6 +4408,7 @@ impl CodexMessageProcessor {
&mut typesafe_overrides,
)
.await;
apply_context_mode_from_history(&thread_history, &mut typesafe_overrides);
// Derive a Config using the same logic as new conversation, honoring overrides if provided.
let config = match self
@@ -4599,7 +4611,7 @@ impl CodexMessageProcessor {
if let (Some(requested_path), Some(active_path)) = (
params.path.as_ref(),
existing_thread.rollout_path().as_ref(),
) && requested_path != active_path
) && !path_utils::paths_match_after_normalization(requested_path, active_path)
{
return Err(invalid_request(format!(
"cannot resume running thread {existing_thread_id} with stale path: requested `{}`, active `{}`",
@@ -4957,6 +4969,7 @@ impl CodexMessageProcessor {
config: cli_overrides,
base_instructions,
developer_instructions,
context_mode,
ephemeral,
exclude_turns,
persist_extended_history,
@@ -5021,9 +5034,11 @@ impl CodexMessageProcessor {
permissions,
base_instructions,
developer_instructions,
context_mode,
/*personality*/ None,
);
typesafe_overrides.ephemeral = ephemeral.then_some(true);
apply_context_mode_from_items(&history_items, &mut typesafe_overrides);
// Derive a Config using the same logic as new conversation, honoring overrides if provided.
let config = self
.config_manager
@@ -8748,6 +8763,10 @@ fn collect_resume_override_mismatches(
"developerInstructions override was provided and ignored while running".to_string(),
);
}
if request.context_mode.is_some() {
mismatch_details
.push("contextMode override was provided and ignored while running".to_string());
}
if request.persist_extended_history {
mismatch_details.push(
"persistExtendedHistory override was provided and ignored while running".to_string(),
@@ -8777,6 +8796,44 @@ fn merge_persisted_resume_metadata(
}
}
fn apply_context_mode_from_history(
thread_history: &InitialHistory,
typesafe_overrides: &mut ConfigOverrides,
) {
if typesafe_overrides.context_mode.is_none()
&& let Some(context_mode) = thread_history.get_context_mode()
{
typesafe_overrides.context_mode = Some(core_context_mode_from_protocol(context_mode));
}
}
fn apply_context_mode_from_items(
history_items: &[RolloutItem],
typesafe_overrides: &mut ConfigOverrides,
) {
if typesafe_overrides.context_mode.is_none()
&& let Some(context_mode) = history_items.iter().find_map(|item| match item {
RolloutItem::SessionMeta(meta_line) => Some(meta_line.meta.context_mode),
_ => None,
})
{
typesafe_overrides.context_mode = Some(core_context_mode_from_protocol(context_mode));
}
}
fn core_context_mode(mode: codex_app_server_protocol::ContextMode) -> CoreContextMode {
core_context_mode_from_protocol(mode.to_core())
}
fn core_context_mode_from_protocol(
mode: codex_protocol::config_types::ContextMode,
) -> CoreContextMode {
match mode {
codex_protocol::config_types::ContextMode::Default => CoreContextMode::Default,
codex_protocol::config_types::ContextMode::Vanilla => CoreContextMode::Vanilla,
}
}
fn has_model_resume_override(
request_overrides: Option<&HashMap<String, serde_json::Value>>,
typesafe_overrides: &ConfigOverrides,
@@ -10537,6 +10594,7 @@ mod tests {
config: None,
base_instructions: None,
developer_instructions: None,
context_mode: None,
personality: None,
exclude_turns: false,
persist_extended_history: false,
@@ -10553,6 +10611,7 @@ mod tests {
ephemeral: false,
reasoning_effort: None,
personality: None,
context_mode: CoreContextMode::Default,
session_source: SessionSource::Cli,
};
@@ -10562,6 +10621,95 @@ mod tests {
);
}
#[test]
fn collect_resume_override_mismatches_includes_context_mode() {
let cwd = test_path_buf("/tmp").abs();
let request = ThreadResumeParams {
thread_id: "thread-1".to_string(),
context_mode: Some(codex_app_server_protocol::ContextMode::Vanilla),
..ThreadResumeParams::default()
};
let config_snapshot = ThreadConfigSnapshot {
model: "gpt-5".to_string(),
model_provider_id: "openai".to_string(),
service_tier: None,
approval_policy: codex_protocol::protocol::AskForApproval::OnRequest,
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer::User,
permission_profile: codex_protocol::models::PermissionProfile::Disabled,
active_permission_profile: None,
cwd,
ephemeral: false,
reasoning_effort: None,
personality: None,
context_mode: CoreContextMode::Default,
session_source: SessionSource::Cli,
};
assert_eq!(
collect_resume_override_mismatches(&request, &config_snapshot),
vec!["contextMode override was provided and ignored while running".to_string()]
);
}
#[test]
fn apply_context_mode_from_history_uses_stored_mode_when_omitted() {
let thread_id = ThreadId::new();
let thread_history = InitialHistory::Resumed(codex_protocol::protocol::ResumedHistory {
conversation_id: thread_id,
history: vec![session_meta_item(
codex_protocol::config_types::ContextMode::Vanilla,
)],
rollout_path: None,
});
let mut overrides = ConfigOverrides::default();
apply_context_mode_from_history(&thread_history, &mut overrides);
assert_eq!(overrides.context_mode, Some(CoreContextMode::Vanilla));
}
#[test]
fn apply_context_mode_from_history_preserves_explicit_mode() {
let thread_id = ThreadId::new();
let thread_history = InitialHistory::Resumed(codex_protocol::protocol::ResumedHistory {
conversation_id: thread_id,
history: vec![session_meta_item(
codex_protocol::config_types::ContextMode::Vanilla,
)],
rollout_path: None,
});
let mut overrides = ConfigOverrides {
context_mode: Some(CoreContextMode::Default),
..Default::default()
};
apply_context_mode_from_history(&thread_history, &mut overrides);
assert_eq!(overrides.context_mode, Some(CoreContextMode::Default));
}
#[test]
fn apply_context_mode_from_items_uses_stored_mode_when_omitted() {
let history_items = vec![session_meta_item(
codex_protocol::config_types::ContextMode::Vanilla,
)];
let mut overrides = ConfigOverrides::default();
apply_context_mode_from_items(&history_items, &mut overrides);
assert_eq!(overrides.context_mode, Some(CoreContextMode::Vanilla));
}
fn session_meta_item(context_mode: codex_protocol::config_types::ContextMode) -> RolloutItem {
RolloutItem::SessionMeta(SessionMetaLine {
meta: SessionMeta {
context_mode,
..SessionMeta::default()
},
git: None,
})
}
fn test_thread_metadata(
model: Option<&str>,
reasoning_effort: Option<ReasoningEffort>,
@@ -142,6 +142,7 @@ pub fn create_fake_rollout_with_source(
agent_nickname: None,
agent_role: None,
model_provider: model_provider.map(str::to_string),
context_mode: codex_protocol::config_types::ContextMode::Default,
base_instructions: None,
dynamic_tools: None,
memory_mode: None,
@@ -225,6 +226,7 @@ pub fn create_fake_rollout_with_text_elements(
agent_nickname: None,
agent_role: None,
model_provider: model_provider.map(str::to_string),
context_mode: codex_protocol::config_types::ContextMode::Default,
base_instructions: None,
dynamic_tools: None,
memory_mode: None,
@@ -672,6 +672,7 @@ async fn skills_changed_notification_is_emitted_after_skill_change() -> Result<(
service_name: None,
base_instructions: None,
developer_instructions: None,
context_mode: None,
personality: None,
ephemeral: None,
session_start_source: None,
@@ -1115,6 +1115,7 @@ async fn seed_pathless_store_thread(
metadata: ThreadPersistenceMetadata {
cwd: None,
model_provider: "test-provider".to_string(),
context_mode: codex_protocol::config_types::ContextMode::Default,
memory_mode: ThreadMemoryMode::Disabled,
},
event_persistence_mode: ThreadEventPersistenceMode::default(),
@@ -1182,6 +1182,7 @@ stream_max_retries = 0
agent_nickname: None,
agent_role: None,
model_provider: Some("mock_provider".to_string()),
context_mode: codex_protocol::config_types::ContextMode::Default,
base_instructions: None,
dynamic_tools: None,
memory_mode: None,
+2
View File
@@ -1,5 +1,6 @@
use crate::agent::AgentStatus;
use crate::config::ConstraintResult;
use crate::config::ContextMode;
use crate::file_watcher::WatchRegistration;
use crate::goals::GoalRuntimeEvent;
use crate::session::Codex;
@@ -58,6 +59,7 @@ pub struct ThreadConfigSnapshot {
pub ephemeral: bool,
pub reasoning_effort: Option<ReasoningEffort>,
pub personality: Option<Personality>,
pub context_mode: ContextMode,
pub session_source: SessionSource,
}
+45
View File
@@ -4851,6 +4851,47 @@ async fn cli_override_sets_compact_prompt() -> std::io::Result<()> {
Ok(())
}
#[tokio::test]
async fn vanilla_context_mode_disables_context_enrichment_config() -> std::io::Result<()> {
let codex_home = TempDir::new()?;
let cfg = toml::from_str::<ConfigToml>(
r#"
instructions = "CFG_BASE_INSTRUCTIONS_MARKER"
developer_instructions = "CFG_DEVELOPER_INSTRUCTIONS_MARKER"
include_apps_instructions = true
include_environment_context = true
[skills]
include_instructions = true
"#,
)
.expect("config TOML should parse");
let config = Config::load_from_base_config_with_overrides(
cfg,
ConfigOverrides {
base_instructions: Some("OVERRIDE_BASE_INSTRUCTIONS_MARKER".to_string()),
developer_instructions: Some("OVERRIDE_DEVELOPER_INSTRUCTIONS_MARKER".to_string()),
context_mode: Some(ContextMode::Vanilla),
personality: Some(Personality::Pragmatic),
..Default::default()
},
codex_home.abs(),
)
.await?;
assert_eq!(config.context_mode, ContextMode::Vanilla);
assert_eq!(config.base_instructions, None);
assert_eq!(config.developer_instructions, None);
assert_eq!(config.personality, None);
assert!(config.include_permissions_instructions);
assert!(!config.include_apps_instructions);
assert!(!config.include_skill_instructions);
assert!(!config.include_environment_context);
Ok(())
}
#[tokio::test]
async fn loads_compact_prompt_from_file() -> std::io::Result<()> {
let codex_home = TempDir::new()?;
@@ -6422,6 +6463,7 @@ async fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> {
experimental_thread_store: ThreadStoreConfig::Local,
base_instructions: None,
developer_instructions: None,
context_mode: ContextMode::Default,
guardian_policy_config: None,
include_permissions_instructions: true,
include_apps_instructions: true,
@@ -6624,6 +6666,7 @@ async fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> {
experimental_thread_store: ThreadStoreConfig::Local,
base_instructions: None,
developer_instructions: None,
context_mode: ContextMode::Default,
guardian_policy_config: None,
include_permissions_instructions: true,
include_apps_instructions: true,
@@ -6780,6 +6823,7 @@ async fn test_precedence_fixture_with_zdr_profile() -> std::io::Result<()> {
experimental_thread_store: ThreadStoreConfig::Local,
base_instructions: None,
developer_instructions: None,
context_mode: ContextMode::Default,
guardian_policy_config: None,
include_permissions_instructions: true,
include_apps_instructions: true,
@@ -6921,6 +6965,7 @@ async fn test_precedence_fixture_with_gpt5_profile() -> std::io::Result<()> {
experimental_thread_store: ThreadStoreConfig::Local,
base_instructions: None,
developer_instructions: None,
context_mode: ContextMode::Default,
guardian_policy_config: None,
include_permissions_instructions: true,
include_apps_instructions: true,
+85 -37
View File
@@ -78,6 +78,7 @@ use codex_model_provider_info::built_in_model_providers;
use codex_model_provider_info::merge_configured_model_providers;
use codex_models_manager::ModelsManagerConfig;
use codex_protocol::config_types::AltScreenMode;
use codex_protocol::config_types::ContextMode as ProtocolContextMode;
use codex_protocol::config_types::ForcedLoginMethod;
use codex_protocol::config_types::Personality;
use codex_protocol::config_types::ReasoningSummary;
@@ -451,6 +452,9 @@ pub struct Config {
/// Developer instructions override injected as a separate message.
pub developer_instructions: Option<String>,
/// Controls whether Codex loads project/user context into the model prompt.
pub context_mode: ContextMode,
/// Guardian-specific policy config override from requirements.toml or config.toml.
/// This is inserted into the fixed guardian prompt template under the
/// `# Policy Configuration` section rather than replacing the whole
@@ -812,6 +816,28 @@ pub struct Config {
pub otel: codex_config::types::OtelConfig,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ContextMode {
#[default]
Default,
Vanilla,
}
impl ContextMode {
pub fn includes_context_enrichment(self) -> bool {
self == ContextMode::Default
}
}
impl From<ContextMode> for ProtocolContextMode {
fn from(value: ContextMode) -> Self {
match value {
ContextMode::Default => ProtocolContextMode::Default,
ContextMode::Vanilla => ProtocolContextMode::Vanilla,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct MultiAgentV2Config {
pub max_concurrent_threads_per_session: usize,
@@ -1796,6 +1822,7 @@ pub struct ConfigOverrides {
pub zsh_path: Option<PathBuf>,
pub base_instructions: Option<String>,
pub developer_instructions: Option<String>,
pub context_mode: Option<ContextMode>,
pub personality: Option<Personality>,
pub compact_prompt: Option<String>,
pub include_apply_patch_tool: Option<bool>,
@@ -2038,8 +2065,6 @@ impl Config {
guardian_policy_config_source: _,
} = config_layer_stack.requirements().clone();
let user_instructions = AgentsMdManager::load_global_instructions(Some(&codex_home))
.map(|loaded| loaded.contents);
let mut startup_warnings = config_layer_stack
.startup_warnings()
.unwrap_or_default()
@@ -2064,6 +2089,7 @@ impl Config {
zsh_path: zsh_path_override,
base_instructions,
developer_instructions,
context_mode,
personality,
compact_prompt,
include_apply_patch_tool: include_apply_patch_tool_override,
@@ -2085,6 +2111,14 @@ impl Config {
"`sandbox_mode` and `default_permissions` overrides cannot both be set",
));
}
let context_mode = context_mode.unwrap_or_default();
let includes_context_enrichment = context_mode.includes_context_enrichment();
let user_instructions = includes_context_enrichment
.then(|| {
AgentsMdManager::load_global_instructions(Some(&codex_home))
.map(|loaded| loaded.contents)
})
.flatten();
if permission_profile.is_some() && default_permissions_override.is_some() {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
@@ -2681,37 +2715,46 @@ impl Config {
// Load base instructions override from a file if specified. If the
// path is relative, resolve it against the effective cwd so the
// behaviour matches other path-like config values.
let model_instructions_path = config_profile
.model_instructions_file
.as_ref()
.or(cfg.model_instructions_file.as_ref());
let file_base_instructions = Self::try_read_non_empty_file(
fs,
model_instructions_path,
"model instructions file",
)
.await?;
let base_instructions = base_instructions
.or(file_base_instructions)
.or(cfg.instructions.clone());
let developer_instructions = developer_instructions.or(cfg.developer_instructions);
let base_instructions = if includes_context_enrichment {
let model_instructions_path = config_profile
.model_instructions_file
.as_ref()
.or(cfg.model_instructions_file.as_ref());
let file_base_instructions = Self::try_read_non_empty_file(
fs,
model_instructions_path,
"model instructions file",
)
.await?;
base_instructions
.or(file_base_instructions)
.or(cfg.instructions.clone())
} else {
None
};
let developer_instructions = includes_context_enrichment
.then(|| developer_instructions.or(cfg.developer_instructions))
.flatten();
let include_permissions_instructions = config_profile
.include_permissions_instructions
.or(cfg.include_permissions_instructions)
.unwrap_or(true);
let include_apps_instructions = config_profile
.include_apps_instructions
.or(cfg.include_apps_instructions)
.unwrap_or(true);
let include_skill_instructions = cfg
.skills
.as_ref()
.and_then(|skills| skills.include_instructions)
.unwrap_or(true);
let include_environment_context = config_profile
.include_environment_context
.or(cfg.include_environment_context)
.unwrap_or(true);
let include_apps_instructions = includes_context_enrichment
&& config_profile
.include_apps_instructions
.or(cfg.include_apps_instructions)
.unwrap_or(true);
let include_skill_instructions = includes_context_enrichment
&& cfg
.skills
.as_ref()
.and_then(|skills| skills.include_instructions)
.unwrap_or(true);
let include_environment_context = includes_context_enrichment
&& config_profile
.include_environment_context
.or(cfg.include_environment_context)
.unwrap_or(true);
let guardian_policy_config =
guardian_policy_config_from_requirements(config_layer_stack.requirements_toml())
.or_else(|| {
@@ -2721,14 +2764,18 @@ impl Config {
auto_review.policy.as_deref(),
))
});
let personality = personality
.or(config_profile.personality)
.or(cfg.personality)
.or_else(|| {
features
.enabled(Feature::Personality)
.then_some(Personality::Pragmatic)
});
let personality = includes_context_enrichment
.then(|| {
personality
.or(config_profile.personality)
.or(cfg.personality)
.or_else(|| {
features
.enabled(Feature::Personality)
.then_some(Personality::Pragmatic)
})
})
.flatten();
let experimental_compact_prompt_path = config_profile
.experimental_compact_prompt_file
@@ -2919,6 +2966,7 @@ impl Config {
base_instructions,
personality,
developer_instructions,
context_mode,
compact_prompt,
commit_attribution,
include_permissions_instructions,
@@ -54,6 +54,7 @@ async fn write_rollout_with_user_event(dir: &Path, thread_id: ThreadId) -> io::R
agent_nickname: None,
agent_role: None,
model_provider: None,
context_mode: codex_protocol::config_types::ContextMode::Default,
base_instructions: None,
dynamic_tools: None,
memory_mode: None,
+50 -25
View File
@@ -474,9 +474,13 @@ impl Codex {
let fs = environment_selections.primary_filesystem();
let plugins_input = config.plugins_config_input();
let plugin_outcome = plugins_manager.plugins_for_config(&plugins_input).await;
let effective_skill_roots = plugin_outcome.effective_skill_roots();
let skills_input = skills_load_input_from_config(&config, effective_skill_roots);
let loaded_skills = skills_manager.skills_for_config(&skills_input, fs).await;
let loaded_skills = if config.context_mode.includes_context_enrichment() {
let effective_skill_roots = plugin_outcome.effective_skill_roots();
let skills_input = skills_load_input_from_config(&config, effective_skill_roots);
skills_manager.skills_for_config(&skills_input, fs).await
} else {
SkillLoadOutcome::default()
};
for err in &loaded_skills.errors {
error!(
@@ -495,9 +499,13 @@ impl Codex {
}
let primary_environment = environment_selections.primary_environment();
let user_instructions = AgentsMdManager::new(&config)
.user_instructions(primary_environment.as_deref())
.await;
let user_instructions = if config.context_mode.includes_context_enrichment() {
AgentsMdManager::new(&config)
.user_instructions(primary_environment.as_deref())
.await
} else {
None
};
let exec_policy = if crate::guardian::is_guardian_reviewer_source(&session_source) {
// Guardian review should rely on the built-in shell safety checks,
@@ -598,6 +606,7 @@ impl Codex {
developer_instructions: config.developer_instructions.clone(),
user_instructions,
personality: config.personality,
context_mode: config.context_mode,
base_instructions,
compact_prompt: config.compact_prompt.clone(),
approval_policy: config.permissions.approval_policy.clone(),
@@ -2578,16 +2587,22 @@ impl Session {
}
let separate_guardian_developer_message =
crate::guardian::is_guardian_reviewer_source(&session_source);
let includes_context_enrichment = turn_context
.config
.context_mode
.includes_context_enrichment();
// Keep the guardian policy prompt out of the aggregated developer bundle so it
// stays isolated as its own top-level developer message for guardian subagents.
if !separate_guardian_developer_message
if includes_context_enrichment
&& !separate_guardian_developer_message
&& let Some(developer_instructions) = turn_context.developer_instructions.as_deref()
&& !developer_instructions.is_empty()
{
developer_sections.push(developer_instructions.to_string());
}
// Add developer instructions for memories.
if turn_context.features.enabled(Feature::MemoryTool)
if includes_context_enrichment
&& turn_context.features.enabled(Feature::MemoryTool)
&& turn_context.config.memories.use_memories
&& let Some(memory_prompt) =
build_memory_tool_developer_instructions(&turn_context.config.codex_home).await
@@ -2595,8 +2610,9 @@ impl Session {
developer_sections.push(memory_prompt);
}
// Add developer instructions from collaboration_mode if they exist and are non-empty
if let Some(collab_instructions) =
CollaborationModeInstructions::from_collaboration_mode(&collaboration_mode)
if includes_context_enrichment
&& let Some(collab_instructions) =
CollaborationModeInstructions::from_collaboration_mode(&collaboration_mode)
{
developer_sections.push(collab_instructions.render());
}
@@ -2607,7 +2623,8 @@ impl Session {
) {
developer_sections.push(realtime_update);
}
if self.features.enabled(Feature::Personality)
if includes_context_enrichment
&& self.features.enabled(Feature::Personality)
&& let Some(personality) = turn_context.personality
{
let model_info = turn_context.model_info.clone();
@@ -2624,7 +2641,10 @@ impl Session {
.push(PersonalitySpecInstructions::new(personality_message).render());
}
}
if turn_context.config.include_apps_instructions && turn_context.apps_enabled() {
if includes_context_enrichment
&& turn_context.config.include_apps_instructions
&& turn_context.apps_enabled()
{
let mcp_connection_manager = self.services.mcp_connection_manager.read().await;
let accessible_and_enabled_connectors =
connectors::list_accessible_and_enabled_connectors_from_manager(
@@ -2638,7 +2658,7 @@ impl Session {
developer_sections.push(apps_instructions.render());
}
}
if turn_context.config.include_skill_instructions {
if includes_context_enrichment && turn_context.config.include_skill_instructions {
let available_skills = build_available_skills(
&turn_context.turn_skills.outcome,
default_skill_metadata_budget(turn_context.model_info.context_window),
@@ -2661,24 +2681,29 @@ impl Session {
developer_sections.push(skills_instructions.render());
}
}
let loaded_plugins = self
.services
.plugins_manager
.plugins_for_config(&turn_context.config.plugins_config_input())
.await;
if let Some(plugin_instructions) =
AvailablePluginsInstructions::from_plugins(loaded_plugins.capability_summaries())
{
developer_sections.push(plugin_instructions.render());
if includes_context_enrichment {
let loaded_plugins = self
.services
.plugins_manager
.plugins_for_config(&turn_context.config.plugins_config_input())
.await;
if let Some(plugin_instructions) =
AvailablePluginsInstructions::from_plugins(loaded_plugins.capability_summaries())
{
developer_sections.push(plugin_instructions.render());
}
}
if turn_context.features.enabled(Feature::CodexGitCommit)
if includes_context_enrichment
&& turn_context.features.enabled(Feature::CodexGitCommit)
&& let Some(commit_message_instruction) = commit_message_trailer_instruction(
turn_context.config.commit_attribution.as_deref(),
)
{
developer_sections.push(commit_message_instruction);
}
if let Some(user_instructions) = turn_context.user_instructions.as_deref() {
if includes_context_enrichment
&& let Some(user_instructions) = turn_context.user_instructions.as_deref()
{
contextual_user_sections.push(
UserInstructions {
text: user_instructions.to_string(),
@@ -2687,7 +2712,7 @@ impl Session {
.render(),
);
}
if turn_context.config.include_environment_context {
if includes_context_enrichment && turn_context.config.include_environment_context {
let subagents = self
.services
.agent_control
+11
View File
@@ -1,6 +1,7 @@
use super::*;
use crate::goals::GoalRuntimeState;
use codex_otel::LEGACY_NOTIFY_CONFIGURED_METRIC;
use codex_protocol::config_types::ContextMode as ProtocolContextMode;
use codex_protocol::permissions::FileSystemPath;
use codex_protocol::permissions::FileSystemSpecialPath;
use codex_protocol::protocol::TurnEnvironmentSelection;
@@ -51,6 +52,9 @@ pub(crate) struct SessionConfiguration {
/// Personality preference for the model.
pub(super) personality: Option<Personality>,
/// Controls whether Codex loads project/user context into the model prompt.
pub(super) context_mode: crate::config::ContextMode,
/// Base instructions for the session.
pub(super) base_instructions: String,
@@ -141,6 +145,7 @@ impl SessionConfiguration {
ephemeral: self.original_config_do_not_use.ephemeral,
reasoning_effort: self.collaboration_mode.reasoning_effort(),
personality: self.personality,
context_mode: self.context_mode,
session_source: self.session_source.clone(),
}
}
@@ -400,6 +405,9 @@ impl Session {
metadata: ThreadPersistenceMetadata {
cwd: Some(config.cwd.to_path_buf()),
model_provider: config.model_provider_id.clone(),
context_mode: ProtocolContextMode::from(
session_configuration.context_mode,
),
memory_mode: if config.memories.generate_memories {
ThreadMemoryMode::Enabled
} else {
@@ -422,6 +430,9 @@ impl Session {
metadata: ThreadPersistenceMetadata {
cwd: Some(config.cwd.to_path_buf()),
model_provider: config.model_provider_id.clone(),
context_mode: ProtocolContextMode::from(
session_configuration.context_mode,
),
memory_mode: if config.memories.generate_memories {
ThreadMemoryMode::Enabled
} else {
+48
View File
@@ -2354,6 +2354,7 @@ async fn set_rate_limits_retains_previous_credits() {
user_instructions: config.user_instructions.clone(),
service_tier: None,
personality: config.personality,
context_mode: config.context_mode,
base_instructions: config
.base_instructions
.clone()
@@ -2457,6 +2458,7 @@ async fn set_rate_limits_updates_plan_type_when_present() {
user_instructions: config.user_instructions.clone(),
service_tier: None,
personality: config.personality,
context_mode: config.context_mode,
base_instructions: config
.base_instructions
.clone()
@@ -2733,6 +2735,7 @@ async fn attach_thread_persistence(session: &mut Session) -> PathBuf {
metadata: ThreadPersistenceMetadata {
cwd: Some(config.cwd.to_path_buf()),
model_provider: config.model_provider_id.clone(),
context_mode: codex_protocol::config_types::ContextMode::Default,
memory_mode: if config.memories.generate_memories {
ThreadMemoryMode::Enabled
} else {
@@ -2915,6 +2918,7 @@ pub(crate) async fn make_session_configuration_for_tests() -> SessionConfigurati
user_instructions: config.user_instructions.clone(),
service_tier: None,
personality: config.personality,
context_mode: config.context_mode,
base_instructions: config
.base_instructions
.clone()
@@ -3438,6 +3442,7 @@ async fn session_new_fails_when_zsh_fork_enabled_without_zsh_path() {
user_instructions: config.user_instructions.clone(),
service_tier: None,
personality: config.personality,
context_mode: config.context_mode,
base_instructions: config
.base_instructions
.clone()
@@ -3543,6 +3548,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
user_instructions: config.user_instructions.clone(),
service_tier: None,
personality: config.personality,
context_mode: config.context_mode,
base_instructions: config
.base_instructions
.clone()
@@ -3755,6 +3761,7 @@ async fn make_session_with_config_and_rx(
user_instructions: config.user_instructions.clone(),
service_tier: None,
personality: config.personality,
context_mode: config.context_mode,
base_instructions: config
.base_instructions
.clone()
@@ -4726,6 +4733,7 @@ async fn shutdown_complete_does_not_append_to_thread_store_after_shutdown() {
metadata: ThreadPersistenceMetadata {
cwd: Some(config.cwd.to_path_buf()),
model_provider: config.model_provider_id.clone(),
context_mode: codex_protocol::config_types::ContextMode::Default,
memory_mode: if config.memories.generate_memories {
ThreadMemoryMode::Enabled
} else {
@@ -5025,6 +5033,7 @@ where
user_instructions: config.user_instructions.clone(),
service_tier: None,
personality: config.personality,
context_mode: config.context_mode,
base_instructions: config
.base_instructions
.clone()
@@ -5808,6 +5817,45 @@ async fn build_initial_context_trims_skill_metadata_from_context_window_budget()
);
}
#[tokio::test]
async fn build_initial_context_omits_context_enrichment_in_vanilla_mode() {
let (session, mut turn_context) = make_session_and_context().await;
let mut config = (*turn_context.config).clone();
config.context_mode = crate::config::ContextMode::Vanilla;
config.include_apps_instructions = true;
config.include_skill_instructions = true;
config.include_environment_context = true;
turn_context.config = Arc::new(config);
turn_context.developer_instructions = Some("VANILLA_DEVELOPER_INSTRUCTIONS_MARKER".to_string());
turn_context.user_instructions = Some("VANILLA_USER_INSTRUCTIONS_MARKER".to_string());
turn_context.personality = Some(codex_protocol::config_types::Personality::Pragmatic);
let mut outcome = SkillLoadOutcome::default();
outcome.skills = vec![SkillMetadata {
name: "vanilla-repo-skill".to_string(),
description: "VANILLA_SKILL_DESCRIPTION_MARKER".to_string(),
short_description: None,
interface: None,
dependencies: None,
policy: None,
path_to_skills_md: test_path_buf("/tmp/vanilla-repo-skill/SKILL.md").abs(),
scope: SkillScope::Repo,
}];
turn_context.turn_skills = TurnSkillsContext::new(Arc::new(outcome));
let initial_context = session.build_initial_context(&turn_context).await;
let visible_text = developer_input_texts(&initial_context)
.into_iter()
.chain(user_input_texts(&initial_context))
.collect::<Vec<_>>()
.join("\n");
assert!(!visible_text.contains("VANILLA_DEVELOPER_INSTRUCTIONS_MARKER"));
assert!(!visible_text.contains("VANILLA_USER_INSTRUCTIONS_MARKER"));
assert!(!visible_text.contains("vanilla-repo-skill"));
assert!(!visible_text.contains("VANILLA_SKILL_DESCRIPTION_MARKER"));
assert!(!visible_text.contains("<environment_context>"));
}
#[test]
fn emit_thread_start_skill_metrics_records_enabled_kept_and_truncated_values() {
let session_telemetry = test_session_telemetry_without_metadata();
@@ -70,6 +70,7 @@ async fn write_rollout_with_user_event(dir: &Path, thread_id: ThreadId) -> io::R
agent_nickname: None,
agent_role: None,
model_provider: None,
context_mode: codex_protocol::config_types::ContextMode::Default,
base_instructions: None,
dynamic_tools: None,
memory_mode: None,
@@ -115,6 +116,7 @@ async fn write_rollout_with_meta_only(dir: &Path, thread_id: ThreadId) -> io::Re
agent_nickname: None,
agent_role: None,
model_provider: None,
context_mode: codex_protocol::config_types::ContextMode::Default,
base_instructions: None,
dynamic_tools: None,
memory_mode: None,
@@ -174,6 +174,7 @@ async fn find_locates_rollout_file_written_by_recorder() -> std::io::Result<()>
/*forked_from_id*/ None,
SessionSource::Exec,
BaseInstructions::default(),
codex_protocol::config_types::ContextMode::Default,
Vec::new(),
EventPersistenceMode::Limited,
),
@@ -148,6 +148,7 @@ async fn backfill_scans_existing_rollouts() -> Result<()> {
agent_nickname: None,
agent_role: None,
model_provider: None,
context_mode: codex_protocol::config_types::ContextMode::Default,
base_instructions: None,
dynamic_tools: Some(dynamic_tools_for_hook),
memory_mode: None,
+1
View File
@@ -412,6 +412,7 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
zsh_path: None,
base_instructions: None,
developer_instructions: None,
context_mode: None,
personality: None,
compact_prompt: None,
include_apply_patch_tool: None,
+14
View File
@@ -77,6 +77,20 @@ pub enum SandboxMode {
DangerFullAccess,
}
#[derive(Debug, Serialize, Deserialize, Default, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
pub enum ContextMode {
#[default]
Default,
Vanilla,
}
impl ContextMode {
pub fn is_default(&self) -> bool {
*self == ContextMode::Default
}
}
#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Display, TS)]
#[strum(serialize_all = "snake_case")]
#[ts(type = r#""user" | "auto_review" | "guardian_subagent""#)]
+20
View File
@@ -18,6 +18,7 @@ use crate::ThreadId;
use crate::approvals::ElicitationRequestEvent;
use crate::config_types::ApprovalsReviewer;
use crate::config_types::CollaborationMode;
use crate::config_types::ContextMode;
use crate::config_types::ModeKind;
use crate::config_types::Personality;
use crate::config_types::ReasoningSummary as ReasoningSummaryConfig;
@@ -2526,6 +2527,22 @@ impl InitialHistory {
}),
}
}
pub fn get_context_mode(&self) -> Option<ContextMode> {
match self {
InitialHistory::New | InitialHistory::Cleared => None,
InitialHistory::Resumed(resumed) => {
resumed.history.iter().find_map(|item| match item {
RolloutItem::SessionMeta(meta_line) => Some(meta_line.meta.context_mode),
_ => None,
})
}
InitialHistory::Forked(items) => items.iter().find_map(|item| match item {
RolloutItem::SessionMeta(meta_line) => Some(meta_line.meta.context_mode),
_ => None,
}),
}
}
}
fn session_cwd_from_items(items: &[RolloutItem]) -> Option<PathBuf> {
@@ -2731,6 +2748,8 @@ pub struct SessionMeta {
#[serde(skip_serializing_if = "Option::is_none")]
pub agent_path: Option<String>,
pub model_provider: Option<String>,
#[serde(default, skip_serializing_if = "ContextMode::is_default")]
pub context_mode: ContextMode,
/// base_instructions for the session. This *should* always be present when creating a new session,
/// but may be missing for older sessions. If not present, fall back to rendering the base_instructions
/// from ModelsManager.
@@ -2755,6 +2774,7 @@ impl Default for SessionMeta {
agent_role: None,
agent_path: None,
model_provider: None,
context_mode: ContextMode::Default,
base_instructions: None,
dynamic_tools: None,
memory_mode: None,
+3
View File
@@ -44,6 +44,7 @@ async fn extract_metadata_from_rollout_uses_session_meta() {
agent_nickname: None,
agent_role: None,
model_provider: Some("openai".to_string()),
context_mode: codex_protocol::config_types::ContextMode::Default,
base_instructions: None,
dynamic_tools: None,
memory_mode: None,
@@ -95,6 +96,7 @@ async fn extract_metadata_from_rollout_returns_latest_memory_mode() {
agent_nickname: None,
agent_role: None,
model_provider: Some("openai".to_string()),
context_mode: codex_protocol::config_types::ContextMode::Default,
base_instructions: None,
dynamic_tools: None,
memory_mode: None,
@@ -354,6 +356,7 @@ fn write_rollout_in_sessions_with_cwd(
agent_nickname: None,
agent_role: None,
model_provider: Some("test-provider".to_string()),
context_mode: codex_protocol::config_types::ContextMode::Default,
base_instructions: None,
dynamic_tools: None,
memory_mode: None,
+6
View File
@@ -12,6 +12,7 @@ use std::sync::Mutex;
use chrono::SecondsFormat;
use chrono::Utc;
use codex_protocol::ThreadId;
use codex_protocol::config_types::ContextMode;
use codex_protocol::dynamic_tools::DynamicToolSpec;
use codex_protocol::models::BaseInstructions;
use codex_utils_string::truncate_middle_chars;
@@ -90,6 +91,7 @@ pub enum RolloutRecorderParams {
forked_from_id: Option<ThreadId>,
source: SessionSource,
base_instructions: BaseInstructions,
context_mode: ContextMode,
dynamic_tools: Vec<DynamicToolSpec>,
event_persistence_mode: EventPersistenceMode,
},
@@ -166,6 +168,7 @@ impl RolloutRecorderParams {
forked_from_id: Option<ThreadId>,
source: SessionSource,
base_instructions: BaseInstructions,
context_mode: ContextMode,
dynamic_tools: Vec<DynamicToolSpec>,
event_persistence_mode: EventPersistenceMode,
) -> Self {
@@ -174,6 +177,7 @@ impl RolloutRecorderParams {
forked_from_id,
source,
base_instructions,
context_mode,
dynamic_tools,
event_persistence_mode,
}
@@ -661,6 +665,7 @@ impl RolloutRecorder {
forked_from_id,
source,
base_instructions,
context_mode,
dynamic_tools,
event_persistence_mode,
} => {
@@ -689,6 +694,7 @@ impl RolloutRecorder {
agent_path: source.get_agent_path().map(Into::into),
source,
model_provider: Some(config.model_provider_id().to_string()),
context_mode,
base_instructions: Some(base_instructions),
dynamic_tools: if dynamic_tools.is_empty() {
None
+3
View File
@@ -232,6 +232,7 @@ async fn recorder_materializes_on_flush_with_pending_items() -> std::io::Result<
/*forked_from_id*/ None,
SessionSource::Exec,
BaseInstructions::default(),
codex_protocol::config_types::ContextMode::Default,
Vec::new(),
EventPersistenceMode::Limited,
),
@@ -312,6 +313,7 @@ async fn persist_reports_filesystem_error_and_retries_buffered_items() -> std::i
/*forked_from_id*/ None,
SessionSource::Exec,
BaseInstructions::default(),
codex_protocol::config_types::ContextMode::Default,
Vec::new(),
EventPersistenceMode::Limited,
),
@@ -411,6 +413,7 @@ async fn metadata_irrelevant_events_touch_state_db_updated_at() -> std::io::Resu
/*forked_from_id*/ None,
SessionSource::Cli,
BaseInstructions::default(),
codex_protocol::config_types::ContextMode::Default,
Vec::new(),
EventPersistenceMode::Limited,
),
@@ -36,6 +36,7 @@ fn write_rollout_with_metadata(path: &Path, thread_id: ThreadId) -> std::io::Res
agent_nickname: None,
agent_role: None,
model_provider: Some("test-provider".into()),
context_mode: codex_protocol::config_types::ContextMode::Default,
base_instructions: None,
dynamic_tools: None,
memory_mode: None,
+1
View File
@@ -1149,6 +1149,7 @@ async fn test_updated_at_uses_file_mtime() -> Result<()> {
agent_nickname: None,
agent_role: None,
model_provider: Some("test-provider".into()),
context_mode: codex_protocol::config_types::ContextMode::Default,
base_instructions: None,
dynamic_tools: None,
memory_mode: None,
+2
View File
@@ -282,6 +282,7 @@ mod tests {
agent_nickname: None,
agent_role: None,
model_provider: Some("openai".to_string()),
context_mode: codex_protocol::config_types::ContextMode::Default,
base_instructions: None,
dynamic_tools: None,
memory_mode: None,
@@ -415,6 +416,7 @@ mod tests {
agent_nickname: None,
agent_role: None,
model_provider: Some("openai".to_string()),
context_mode: codex_protocol::config_types::ContextMode::Default,
base_instructions: None,
dynamic_tools: None,
memory_mode: None,
+2
View File
@@ -1362,6 +1362,7 @@ mod tests {
agent_nickname: None,
agent_role: None,
model_provider: None,
context_mode: codex_protocol::config_types::ContextMode::Default,
base_instructions: None,
dynamic_tools: None,
memory_mode: Some("polluted".to_string()),
@@ -1420,6 +1421,7 @@ mod tests {
agent_nickname: None,
agent_role: None,
model_provider: None,
context_mode: codex_protocol::config_types::ContextMode::Default,
base_instructions: None,
dynamic_tools: None,
memory_mode: None,
@@ -159,6 +159,7 @@ fn new_config(model: Option<String>, arg0_paths: Arg0DispatchPaths) -> anyhow::R
model_auto_compact_token_limit: None,
model_provider_id,
model_provider,
context_mode: Default::default(),
personality: None,
permissions: Permissions {
approval_policy: Constrained::allow_any(AskForApproval::Never),
@@ -35,6 +35,7 @@ pub(super) async fn create_thread(
params.forked_from_id,
params.source,
params.base_instructions,
params.metadata.context_mode,
params.dynamic_tools,
event_persistence_mode(params.event_persistence_mode),
),
+3 -1
View File
@@ -216,7 +216,7 @@ impl ThreadStore for LocalThreadStore {
return read_thread::read_thread_by_rollout_path(
self,
rollout_path,
/*include_archived*/ true,
params.include_archived,
/*include_history*/ true,
)
.await?
@@ -533,6 +533,7 @@ mod tests {
metadata: ThreadPersistenceMetadata {
cwd: None,
model_provider: "test-provider".to_string(),
context_mode: codex_protocol::config_types::ContextMode::Default,
memory_mode: ThreadMemoryMode::Enabled,
},
event_persistence_mode: ThreadEventPersistenceMode::Limited,
@@ -762,6 +763,7 @@ mod tests {
ThreadPersistenceMetadata {
cwd: Some(std::env::current_dir().expect("cwd")),
model_provider: "test-provider".to_string(),
context_mode: codex_protocol::config_types::ContextMode::Default,
memory_mode: ThreadMemoryMode::Enabled,
}
}
@@ -71,6 +71,11 @@ pub(super) async fn read_thread(
})?;
let mut thread = read_thread_from_rollout_path(store, path).await?;
if !params.include_archived && thread.archived_at.is_some() {
return Err(ThreadStoreError::InvalidRequest {
message: format!("thread {thread_id} is archived"),
});
}
attach_history_if_requested(&mut thread, params.include_history).await?;
Ok(thread)
}
@@ -523,6 +523,7 @@ mod tests {
ThreadPersistenceMetadata {
cwd: Some(std::env::current_dir().expect("cwd")),
model_provider: "test-provider".to_string(),
context_mode: codex_protocol::config_types::ContextMode::Default,
memory_mode: ThreadMemoryMode::Enabled,
}
}
+2
View File
@@ -350,6 +350,7 @@ mod tests {
let metadata = ThreadPersistenceMetadata {
cwd: Some(PathBuf::from("/workspace")),
model_provider: "test-provider".to_string(),
context_mode: codex_protocol::config_types::ContextMode::Default,
memory_mode: ThreadMemoryMode::Enabled,
};
@@ -382,6 +383,7 @@ mod tests {
let metadata = ThreadPersistenceMetadata {
cwd: Some(PathBuf::from("/workspace")),
model_provider: "test-provider".to_string(),
context_mode: codex_protocol::config_types::ContextMode::Default,
memory_mode: ThreadMemoryMode::Disabled,
};
+3
View File
@@ -3,6 +3,7 @@ use std::path::PathBuf;
use chrono::DateTime;
use chrono::Utc;
use codex_protocol::ThreadId;
use codex_protocol::config_types::ContextMode;
use codex_protocol::dynamic_tools::DynamicToolSpec;
use codex_protocol::models::BaseInstructions;
use codex_protocol::openai_models::ReasoningEffort;
@@ -35,6 +36,8 @@ pub struct ThreadPersistenceMetadata {
pub cwd: Option<PathBuf>,
/// Model provider associated with the thread.
pub model_provider: String,
/// Context mode associated with the thread.
pub context_mode: ContextMode,
/// Memory mode associated with the live thread.
pub memory_mode: MemoryMode,
}