Skip to main content

agentic_memory/v3/
migration.rs

1//! V2 to V3 migration — convert .amem files to V3 immortal log.
2
3use super::block::*;
4use super::engine::MemoryEngineV3;
5use std::path::Path;
6
7/// Migrate V2 .amem file to V3 immortal log
8pub struct V2ToV3Migration;
9
10impl V2ToV3Migration {
11    /// Migrate a V2 memory file to V3
12    ///
13    /// Reads the V2 graph and converts each node to an appropriate V3 block.
14    /// V2 nodes map to V3 block types as follows:
15    ///   - Episode/Concept/Reflection -> Text blocks
16    ///   - Fact/Preference -> Decision blocks
17    ///   - Procedure -> Tool blocks
18    pub fn migrate(
19        v2_path: &Path,
20        v3_engine: &MemoryEngineV3,
21    ) -> Result<MigrationReport, std::io::Error> {
22        let mut report = MigrationReport::default();
23
24        // Check format
25        if !Self::is_v2_format(v2_path) {
26            return Err(std::io::Error::new(
27                std::io::ErrorKind::InvalidData,
28                "Not a V2 .amem file (missing AMEM magic bytes)",
29            ));
30        }
31
32        // Try loading V2 graph using the existing format module
33        #[cfg(feature = "format")]
34        {
35            use crate::format::AmemReader;
36
37            match AmemReader::read_from_file(v2_path) {
38                Ok(graph) => {
39                    report.v2_nodes = graph.node_count();
40                    report.v2_edges = graph.edge_count();
41
42                    // Convert each V2 node to a V3 block
43                    for node in graph.nodes() {
44                        match v3_engine.capture_user_message(&node.content, None) {
45                            Ok(_) => report.blocks_created += 1,
46                            Err(e) => report.errors.push(format!("Node {}: {}", node.id, e)),
47                        }
48                    }
49
50                    // Mark migration complete
51                    let _ = v3_engine.capture_boundary(
52                        BoundaryType::SessionStart,
53                        0,
54                        0,
55                        &format!(
56                            "Migrated from V2: {} nodes, {} edges",
57                            report.v2_nodes, report.v2_edges
58                        ),
59                        Some("V3 immortal mode active"),
60                    );
61
62                    report.success = report.errors.is_empty();
63                }
64                Err(e) => {
65                    report.errors.push(format!("Failed to read V2 file: {}", e));
66                }
67            }
68        }
69
70        #[cfg(not(feature = "format"))]
71        {
72            let _ = v3_engine;
73            report
74                .errors
75                .push("V2 migration requires 'format' feature".to_string());
76        }
77
78        Ok(report)
79    }
80
81    /// Check if a file is V2 format
82    pub fn is_v2_format(path: &Path) -> bool {
83        if let Ok(data) = std::fs::read(path) {
84            // V2 starts with AMEM magic
85            data.len() >= 4 && &data[0..4] == b"AMEM"
86        } else {
87            false
88        }
89    }
90
91    /// Check if a file is V3 format
92    pub fn is_v3_format(path: &Path) -> bool {
93        if let Ok(data) = std::fs::read(path) {
94            // V3 immortal log is JSON-based with length prefixes
95            // Check first block can be deserialized
96            if data.len() >= 4 {
97                let block_len = u32::from_le_bytes([data[0], data[1], data[2], data[3]]) as usize;
98                if data.len() >= 4 + block_len && block_len > 0 {
99                    return serde_json::from_slice::<Block>(&data[4..4 + block_len]).is_ok();
100                }
101            }
102            false
103        } else {
104            false
105        }
106    }
107}
108
109/// Migration report
110#[derive(Debug, Default)]
111pub struct MigrationReport {
112    pub success: bool,
113    pub v2_nodes: usize,
114    pub v2_edges: usize,
115    pub blocks_created: usize,
116    pub errors: Vec<String>,
117}