Configure memory consolidation service tiers independently (#57568)

## Why

Memory consolidation inherits the session's service tier. Give background consolidation its own setting, defaulting to Standard.

## What changed

Add `memories.consolidation_service_tier` with a default of `"default"` and apply it to the consolidation agent. Normalize recognized tier aliases, including `"fast"` to `"priority"`, and expose the setting in the configuration schema.

## Testing

Add coverage for configuration parsing and alias normalization, Standard consolidation despite an Ultrafast session tier, and an explicit `"flex"` consolidation override.

GitOrigin-RevId: 58031ad56196152d212531d56e7fe9813f83317e
This commit is contained in:
mandal-OAI
2026-10-27 21:51:26 +00:00
committed by copyberry
parent f00c70f5d0
commit 02bc25c89f
5 changed files with 66 additions and 22 deletions
+12
View File
@@ -21,6 +21,7 @@ pub use codex_protocol::config_types::AltScreenMode;
pub use codex_protocol::config_types::ApprovalsReviewer;
pub use codex_protocol::config_types::ModeKind;
pub use codex_protocol::config_types::Personality;
use codex_protocol::config_types::SERVICE_TIER_DEFAULT_REQUEST_VALUE;
pub use codex_protocol::config_types::ServiceTier;
use codex_protocol::config_types::ToolExposureSurface;
pub use codex_protocol::config_types::WebSearchMode;
@@ -353,6 +354,8 @@ pub struct MemoriesToml {
pub extract_model: Option<String>,
/// Model used for memory consolidation.
pub consolidation_model: Option<String>,
/// Service tier for memory consolidation. Defaults to Standard (`"default"`).
pub consolidation_service_tier: Option<String>,
}
/// Effective memories settings after defaults are applied.
@@ -372,6 +375,7 @@ pub struct MemoriesConfig {
pub min_rate_limit_remaining_percent: i64,
pub extract_model: Option<String>,
pub consolidation_model: Option<String>,
pub consolidation_service_tier: String,
}
impl Default for MemoriesConfig {
@@ -391,6 +395,7 @@ impl Default for MemoriesConfig {
min_rate_limit_remaining_percent: DEFAULT_MEMORIES_MIN_RATE_LIMIT_REMAINING_PERCENT,
extract_model: None,
consolidation_model: None,
consolidation_service_tier: SERVICE_TIER_DEFAULT_REQUEST_VALUE.to_string(),
}
}
}
@@ -439,6 +444,13 @@ impl From<MemoriesToml> for MemoriesConfig {
.clamp(0, 100),
extract_model: toml.extract_model,
consolidation_model: toml.consolidation_model,
consolidation_service_tier: toml
.consolidation_service_tier
.map(|tier| {
ServiceTier::from_request_value(&tier)
.map_or(tier, |tier| tier.request_value().to_string())
})
.unwrap_or(defaults.consolidation_service_tier),
}
}
}
+4
View File
@@ -2840,6 +2840,10 @@
"description": "Model used for memory consolidation.",
"type": "string"
},
"consolidation_service_tier": {
"description": "Service tier for memory consolidation. Defaults to Standard (`\"default\"`).",
"type": "string"
},
"dedicated_tools": {
"description": "When `true`, expose dedicated memory tools through the extension tool surface.",
"type": "boolean"
+3
View File
@@ -336,6 +336,7 @@ min_rollout_idle_hours = 24
min_rate_limit_remaining_percent = 12
extract_model = "gpt-5-mini"
consolidation_model = "gpt-5.2"
consolidation_service_tier = "fast"
"#;
let memories_cfg =
toml::from_str::<ConfigToml>(memories).expect("TOML deserialization should succeed");
@@ -355,6 +356,7 @@ consolidation_model = "gpt-5.2"
min_rate_limit_remaining_percent: Some(12),
extract_model: Some("gpt-5-mini".to_string()),
consolidation_model: Some("gpt-5.2".to_string()),
consolidation_service_tier: Some("fast".to_string()),
}),
memories_cfg.memories
);
@@ -383,6 +385,7 @@ consolidation_model = "gpt-5.2"
min_rate_limit_remaining_percent: 12,
extract_model: Some("gpt-5-mini".to_string()),
consolidation_model: Some("gpt-5.2".to_string()),
consolidation_service_tier: "priority".to_string(),
}
);
+1
View File
@@ -365,6 +365,7 @@ mod agent {
.unwrap_or_else(|| provider.memory_consolidation_preferred_model().to_string()),
);
agent_config.model_reasoning_effort = Some(crate::stage_two::REASONING_EFFORT);
agent_config.service_tier = Some(config.memories.consolidation_service_tier.clone());
Some(agent_config)
}
+46 -22
View File
@@ -24,6 +24,8 @@ use codex_protocol::ThreadId;
use codex_protocol::config_types::ServiceTier;
use codex_protocol::models::ContentItem;
use codex_protocol::models::ResponseItem;
use codex_protocol::openai_models::ModelInfo;
use codex_protocol::openai_models::ModelServiceTier;
use codex_protocol::openai_models::ModelsResponse;
use codex_protocol::openai_models::ReasoningEffort;
use codex_protocol::protocol::EventMsg;
@@ -48,6 +50,7 @@ use core_test_support::responses::sse;
use core_test_support::responses::sse_failed;
use core_test_support::responses::start_mock_server;
use core_test_support::test_codex::TestCodex;
use core_test_support::test_codex::TestCodexBuilder;
use core_test_support::test_codex::test_codex;
use core_test_support::wait_for_event;
use pretty_assertions::assert_eq;
@@ -86,7 +89,9 @@ async fn dual_write_prepares_both_roots_without_importing_old_notes() -> anyhow:
tokio::fs::write(old_notes.join("old.md"), "old memory").await?;
let mut memories = startup_test_memories_config();
memories.dual_write = true;
let test = build_test_codex_with_memories_config(&server, Arc::clone(&home), memories).await?;
let test = memory_test_codex(Arc::clone(&home), memories)
.build(&server)
.await?;
trigger_memories_startup(&test).await;
wait_for_dir(&home.path().join("memories/extensions/ad_hoc")).await?;
wait_for_dir(&home.path().join("memories_v2/extensions/ad_hoc")).await?;
@@ -788,7 +793,7 @@ async fn memories_startup_phase1_provider_default_drives_request_model() -> anyh
}
#[tokio::test]
async fn memories_startup_phase2_provider_default_drives_request_model() -> anyhow::Result<()> {
async fn memories_startup_phase2_uses_provider_model_and_standard_tier() -> anyhow::Result<()> {
let server = start_mock_server().await;
let home = Arc::new(TempDir::new()?);
let request =
@@ -799,6 +804,7 @@ async fn memories_startup_phase2_provider_default_drives_request_model() -> anyh
request.body_json()["model"].as_str(),
Some(MOCK_PROVIDER_PHASE_TWO_MODEL)
);
assert_eq!(request.body_json().get("service_tier"), None);
Ok(())
}
@@ -843,7 +849,9 @@ async fn run_memory_phase_one_model_request_test(
memories: MemoriesConfig,
) -> anyhow::Result<ResponsesRequest> {
let version = memories.version;
let test = build_test_codex_with_memories_config(server, Arc::clone(&home), memories).await?;
let test = memory_test_codex(Arc::clone(&home), memories)
.build(server)
.await?;
let provider = Arc::new(MockMemoryModelProvider::new(
test.config.model_provider.clone(),
Some(test.thread_manager.auth_manager()),
@@ -1055,7 +1063,27 @@ async fn run_memory_phase_two_model_request_test(
memories: MemoriesConfig,
) -> anyhow::Result<ResponsesRequest> {
let version = memories.version;
let test = build_test_codex_with_memories_config(server, home.clone(), memories).await?;
// Both models support Ultrafast so an inherited tier would reach the wire.
let advertise_ultrafast = |model: &mut ModelInfo| {
model.service_tiers.push(ModelServiceTier {
id: "ultrafast".to_string(),
name: "Ultrafast".to_string(),
description: String::new(),
});
model.default_service_tier = Some("ultrafast".to_string());
};
let test = memory_test_codex(Arc::clone(&home), memories)
.with_model_info_override(MOCK_PROVIDER_PHASE_TWO_MODEL, advertise_ultrafast)
.with_model_info_override("mock.interactive", advertise_ultrafast)
.with_config(|config| {
config
.features
.enable(Feature::UltrafastMode)
.expect("enable Ultrafast");
config.service_tier = Some("ultrafast".to_string());
})
.build_with_auto_env(server)
.await?;
let provider = Arc::new(MockMemoryModelProvider::new(
test.config.model_provider.clone(),
Some(test.thread_manager.auth_manager()),
@@ -1165,27 +1193,21 @@ async fn build_test_codex(
server: &wiremock::MockServer,
home: Arc<TempDir>,
) -> anyhow::Result<TestCodex> {
build_test_codex_with_memories_config(server, home, startup_test_memories_config()).await
}
async fn build_test_codex_with_memories_config(
server: &wiremock::MockServer,
home: Arc<TempDir>,
memories: MemoriesConfig,
) -> anyhow::Result<TestCodex> {
test_codex()
.with_home(home)
.with_config(move |config| {
config
.features
.enable(Feature::Sqlite)
.expect("test config should allow feature update");
config.memories = memories;
})
memory_test_codex(home, startup_test_memories_config())
.build(server)
.await
}
fn memory_test_codex(home: Arc<TempDir>, memories: MemoriesConfig) -> TestCodexBuilder {
test_codex().with_home(home).with_config(move |config| {
config
.features
.enable(Feature::Sqlite)
.expect("test config should allow feature update");
config.memories = memories;
})
}
async fn init_state_db(home: &Arc<TempDir>) -> anyhow::Result<Arc<codex_state::StateRuntime>> {
let db = codex_state::StateRuntime::init(
codex_state::SqliteConfig::new_for_testing(home.path().abs()),
@@ -1613,6 +1635,8 @@ async fn memories_startup_phase2_v2_consolidates_without_a_handbook() -> anyhow:
let home = Arc::new(TempDir::new()?);
let mut memories = startup_test_memories_config();
memories.version = codex_protocol::MemoryVersion::V2;
run_memory_phase_two_model_request_test(&server, home, memories).await?;
memories.consolidation_service_tier = "flex".to_string();
let request = run_memory_phase_two_model_request_test(&server, home, memories).await?;
assert_eq!(request.body_json()["service_tier"], "flex");
Ok(())
}