Default fresh memory profiles to v2 (#56211)

## Why

Profiles without a v1 memory workspace have no legacy context to preserve or consolidate before using v2.

## What changed

- Select v2 when memories are enabled and the `memories` directory is absent. Preserve explicit `memories.version` settings, the `memories.dual_write = false` opt-out, and the v1 default for existing workspaces or filesystem check errors.
- Report `v2_ready` immediately for profiles without a v1 workspace. Keep the consolidation count and valid-summary requirements for profiles with a v1 workspace.
- Make resetting an absent memory workspace a no-op so it does not create an unused v1 directory.

## Testing

Extend coverage for default and explicit version selection, opt-outs, version-specific memory reads, fresh-profile readiness and reset behavior, and running only the v2 pipeline for fresh profiles.

GitOrigin-RevId: a8fb0844962ae9266ffbe88af97e8dc4a99ac15f
This commit is contained in:
jif
2026-10-22 16:26:03 +00:00
committed by copyberry
parent 8ba7aed55f
commit a390571745
9 changed files with 131 additions and 29 deletions
+29 -1
View File
@@ -397,6 +397,34 @@ consolidation_model = "gpt-5.2"
);
}
#[tokio::test]
async fn fresh_memory_profiles_preserve_explicit_version_and_transition_opt_out() {
for (settings, expected) in [
("", codex_protocol::MemoryVersion::V2),
("version = 'v1'", codex_protocol::MemoryVersion::V1),
("dual_write = false", codex_protocol::MemoryVersion::V1),
(
"version = 'v2'\ndual_write = false",
codex_protocol::MemoryVersion::V2,
),
] {
let home = tempdir().expect("tempdir");
let cfg = toml::from_str::<ConfigToml>(&format!(
"[features]\nmemories = true\n[memories]\n{settings}"
))
.expect("memory config");
let config = Config::load_from_base_config_with_overrides(
cfg,
ConfigOverrides::default(),
home.abs(),
)
.await
.expect("load config");
assert_eq!(config.memories.version, expected, "{settings}");
assert!(!home.path().join("memories").exists());
}
}
#[tokio::test]
async fn goal_max_token_budget_requires_positive_integer() {
let config_toml = toml::from_str::<ConfigToml>("[goals]\nmax_goal_token_budget = 25000\n")
@@ -6301,7 +6329,7 @@ async fn memory_tool_makes_memories_root_readable_without_creating_or_widening_w
-> std::io::Result<()> {
let codex_home = TempDir::new()?;
let cwd = TempDir::new()?;
let memories_root = codex_home.path().join("memories");
let memories_root = codex_home.path().join("memories_v2");
let memories_root_abs = memories_root.abs();
let config = Config::load_from_base_config_with_overrides(
+15 -1
View File
@@ -97,6 +97,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_prompts::ResolvedModelMessages;
use codex_protocol::MemoryVersion;
use codex_protocol::config_types::AltScreenMode;
use codex_protocol::config_types::AutoCompactTokenLimitScope;
use codex_protocol::config_types::ForcedLoginMethod;
@@ -3646,7 +3647,20 @@ impl Config {
));
}
let memories_config: MemoriesConfig = cfg.memories.clone().unwrap_or_default().into();
let mut memories = cfg.memories.clone().unwrap_or_default();
// Fresh profiles have no V1 context to preserve or warm up before using V2.
// Keep explicit selections and transition opt-outs, and fail closed on IO errors.
if features.enabled(Feature::MemoryTool)
&& memories.version.is_none()
&& memories.dual_write != Some(false)
&& matches!(
tokio::fs::try_exists(codex_home.join(MemoryVersion::V1.directory_name())).await,
Ok(false)
)
{
memories.version = Some(MemoryVersion::V2);
}
let memories_config: MemoriesConfig = memories.into();
let memories_root = codex_home.join(memories_config.version.directory_name());
let profiles_are_active = effective_permission_selection.profiles_are_active(