feat: memories config (#11731)

This commit is contained in:
jif-oai
2026-02-13 14:18:15 +00:00
committed by GitHub
parent 36541876f4
commit e00080cea3
9 changed files with 253 additions and 44 deletions
+56
View File
@@ -7,6 +7,8 @@ use crate::config::types::History;
use crate::config::types::McpServerConfig;
use crate::config::types::McpServerDisabledReason;
use crate::config::types::McpServerTransportConfig;
use crate::config::types::MemoriesConfig;
use crate::config::types::MemoriesToml;
use crate::config::types::Notice;
use crate::config::types::NotificationMethod;
use crate::config::types::Notifications;
@@ -289,6 +291,9 @@ pub struct Config {
/// Maximum number of agent threads that can be open concurrently.
pub agent_max_threads: Option<usize>,
/// Memories subsystem settings.
pub memories: MemoriesConfig,
/// Directory containing all Codex state (defaults to `~/.codex` but can be
/// overridden by the `CODEX_HOME` environment variable).
pub codex_home: PathBuf,
@@ -1006,6 +1011,9 @@ pub struct ConfigToml {
/// Agent-related settings (thread limits, etc.).
pub agents: Option<AgentsToml>,
/// Memories subsystem settings.
pub memories: Option<MemoriesToml>,
/// User-level skill config entries keyed by SKILL.md path.
pub skills: Option<SkillsConfig>,
@@ -1771,6 +1779,7 @@ impl Config {
.collect(),
tool_output_token_limit: cfg.tool_output_token_limit,
agent_max_threads,
memories: cfg.memories.unwrap_or_default().into(),
codex_home,
log_dir,
config_layer_stack,
@@ -1985,6 +1994,8 @@ mod tests {
use crate::config::types::FeedbackConfigToml;
use crate::config::types::HistoryPersistence;
use crate::config::types::McpServerTransportConfig;
use crate::config::types::MemoriesConfig;
use crate::config::types::MemoriesToml;
use crate::config::types::NotificationMethod;
use crate::config::types::Notifications;
use crate::config_loader::RequirementSource;
@@ -2068,6 +2079,47 @@ persistence = "none"
}),
history_no_persistence_cfg.history
);
let memories = r#"
[memories]
max_raw_memories_for_global = 512
max_rollout_age_days = 42
max_rollouts_per_startup = 9
min_rollout_idle_hours = 24
phase_1_model = "gpt-5-mini"
phase_2_model = "gpt-5"
"#;
let memories_cfg =
toml::from_str::<ConfigToml>(memories).expect("TOML deserialization should succeed");
assert_eq!(
Some(MemoriesToml {
max_raw_memories_for_global: Some(512),
max_rollout_age_days: Some(42),
max_rollouts_per_startup: Some(9),
min_rollout_idle_hours: Some(24),
phase_1_model: Some("gpt-5-mini".to_string()),
phase_2_model: Some("gpt-5".to_string()),
}),
memories_cfg.memories
);
let config = Config::load_from_base_config_with_overrides(
memories_cfg,
ConfigOverrides::default(),
tempdir().expect("tempdir").path().to_path_buf(),
)
.expect("load config from memories settings");
assert_eq!(
config.memories,
MemoriesConfig {
max_raw_memories_for_global: 512,
max_rollout_age_days: 42,
max_rollouts_per_startup: 9,
min_rollout_idle_hours: 24,
phase_1_model: Some("gpt-5-mini".to_string()),
phase_2_model: Some("gpt-5".to_string()),
}
);
}
#[test]
@@ -4047,6 +4099,7 @@ model_verbosity = "high"
project_doc_fallback_filenames: Vec::new(),
tool_output_token_limit: None,
agent_max_threads: DEFAULT_AGENT_MAX_THREADS,
memories: MemoriesConfig::default(),
codex_home: fixture.codex_home(),
log_dir: fixture.codex_home().join("log"),
config_layer_stack: Default::default(),
@@ -4156,6 +4209,7 @@ model_verbosity = "high"
project_doc_fallback_filenames: Vec::new(),
tool_output_token_limit: None,
agent_max_threads: DEFAULT_AGENT_MAX_THREADS,
memories: MemoriesConfig::default(),
codex_home: fixture.codex_home(),
log_dir: fixture.codex_home().join("log"),
config_layer_stack: Default::default(),
@@ -4263,6 +4317,7 @@ model_verbosity = "high"
project_doc_fallback_filenames: Vec::new(),
tool_output_token_limit: None,
agent_max_threads: DEFAULT_AGENT_MAX_THREADS,
memories: MemoriesConfig::default(),
codex_home: fixture.codex_home(),
log_dir: fixture.codex_home().join("log"),
config_layer_stack: Default::default(),
@@ -4356,6 +4411,7 @@ model_verbosity = "high"
project_doc_fallback_filenames: Vec::new(),
tool_output_token_limit: None,
agent_max_threads: DEFAULT_AGENT_MAX_THREADS,
memories: MemoriesConfig::default(),
codex_home: fixture.codex_home(),
log_dir: fixture.codex_home().join("log"),
config_layer_stack: Default::default(),
+72
View File
@@ -23,6 +23,10 @@ use serde::Serialize;
use serde::de::Error as SerdeError;
pub const DEFAULT_OTEL_ENVIRONMENT: &str = "dev";
pub const DEFAULT_MEMORIES_MAX_ROLLOUTS_PER_STARTUP: usize = 8;
pub const DEFAULT_MEMORIES_MAX_ROLLOUT_AGE_DAYS: i64 = 30;
pub const DEFAULT_MEMORIES_MIN_ROLLOUT_IDLE_HOURS: i64 = 12;
pub const DEFAULT_MEMORIES_MAX_RAW_MEMORIES_FOR_GLOBAL: usize = 1_024;
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "kebab-case")]
@@ -353,6 +357,74 @@ pub struct FeedbackConfigToml {
pub enabled: Option<bool>,
}
/// Memories settings loaded from config.toml.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)]
#[schemars(deny_unknown_fields)]
pub struct MemoriesToml {
/// Maximum number of recent raw memories retained for global consolidation.
pub max_raw_memories_for_global: Option<usize>,
/// Maximum age of the threads used for memories.
pub max_rollout_age_days: Option<i64>,
/// Maximum number of rollout candidates processed per pass.
pub max_rollouts_per_startup: Option<usize>,
/// Minimum idle time between last thread activity and memory creation (hours). > 12h recommended.
pub min_rollout_idle_hours: Option<i64>,
/// Model used for thread summarisation.
pub phase_1_model: Option<String>,
/// Model used for memory consolidation.
pub phase_2_model: Option<String>,
}
/// Effective memories settings after defaults are applied.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MemoriesConfig {
pub max_raw_memories_for_global: usize,
pub max_rollout_age_days: i64,
pub max_rollouts_per_startup: usize,
pub min_rollout_idle_hours: i64,
pub phase_1_model: Option<String>,
pub phase_2_model: Option<String>,
}
impl Default for MemoriesConfig {
fn default() -> Self {
Self {
max_raw_memories_for_global: DEFAULT_MEMORIES_MAX_RAW_MEMORIES_FOR_GLOBAL,
max_rollout_age_days: DEFAULT_MEMORIES_MAX_ROLLOUT_AGE_DAYS,
max_rollouts_per_startup: DEFAULT_MEMORIES_MAX_ROLLOUTS_PER_STARTUP,
min_rollout_idle_hours: DEFAULT_MEMORIES_MIN_ROLLOUT_IDLE_HOURS,
phase_1_model: None,
phase_2_model: None,
}
}
}
impl From<MemoriesToml> for MemoriesConfig {
fn from(toml: MemoriesToml) -> Self {
let defaults = Self::default();
Self {
max_raw_memories_for_global: toml
.max_raw_memories_for_global
.unwrap_or(defaults.max_raw_memories_for_global)
.min(4096),
max_rollout_age_days: toml
.max_rollout_age_days
.unwrap_or(defaults.max_rollout_age_days)
.clamp(0, 90),
max_rollouts_per_startup: toml
.max_rollouts_per_startup
.unwrap_or(defaults.max_rollouts_per_startup)
.min(128),
min_rollout_idle_hours: toml
.min_rollout_idle_hours
.unwrap_or(defaults.min_rollout_idle_hours)
.clamp(1, 48),
phase_1_model: toml.phase_1_model,
phase_2_model: toml.phase_2_model,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum AppDisabledReason {