Support memory v2 in external-agent memory imports (#56414)

## Why

External-agent memory imports previously wrote only to the v1 workspace and queued only v1 consolidation, leaving the v2 pipeline without imported resources.

## What changed

- Always import selected resources into `memories_v2`. Also update `memories` when v1 is selected or a legacy workspace exists.
- Read `memories.version` for detection and import, detect pending changes across the applicable namespaces, and enqueue consolidation separately for each updated namespace.
- Combine project results across namespaces so cleanup in either can succeed while failures in either are reported.
- Adapt imported-memory consolidation instructions to the active pipeline's output format.

## Testing

Expand app-server integration coverage for v2 profiles, explicit v1 selection, default profiles, and existing legacy workspaces. Verify resource copies, consolidation queues, repeat detection, and cleanup of deleted or unscoped projects.

GitOrigin-RevId: 7e9206942d63fd55b7f2a7f5f9210b687da6a973
This commit is contained in:
jif
2026-10-23 09:06:13 +00:00
committed by copyberry
parent d6817454d7
commit e6ebdfe00b
8 changed files with 259 additions and 109 deletions
@@ -1,18 +1,28 @@
use crate::discover_external_memory_files;
use crate::memory_import::memory_versions;
use crate::memory_import::projects_needing_import;
use crate::memory_import::resources_root;
use crate::model::ExternalAgentConfigMigrationItem;
use crate::model::ExternalAgentConfigMigrationItemType;
use crate::model::MigrationDetails;
use codex_protocol::MemoryVersion;
use std::collections::BTreeSet;
use std::io;
use std::path::Path;
pub(super) fn detect(
codex_home: &Path,
external_agent_home: &Path,
selection: MemoryVersion,
) -> io::Result<Option<ExternalAgentConfigMigrationItem>> {
let memory_files = discover_external_memory_files(external_agent_home)?;
let memory = projects_needing_import(codex_home, &memory_files)?;
let mut memory = BTreeSet::new();
for version in memory_versions(codex_home, selection)? {
memory.extend(projects_needing_import(
&codex_home.join(version.directory_name()),
&memory_files,
)?);
}
if memory.is_empty() {
return Ok(None);
}
@@ -22,7 +32,7 @@ pub(super) fn detect(
description: format!(
"Import memory files from {} to {}",
external_agent_home.join("projects").display(),
resources_root(codex_home).display()
resources_root(&codex_home.join(MemoryVersion::V2.directory_name())).display()
),
cwd: None,
details: Some(MigrationDetails {
@@ -59,7 +59,11 @@ impl ExternalAgentConfigService {
if params.include_home
&& params.include_memory
&& self.source.supports_memory()
&& let Some(item) = memory::detect(&self.codex_home, &self.external_agent_home)?
&& let Some(item) = memory::detect(
&self.codex_home,
&self.external_agent_home,
self.memory_version,
)?
{
items.push(item);
emit_migration_metric(