Skip to main content

mentedb_core/
memory.rs

1//! MemoryNode: the fundamental unit of storage in MenteDB.
2
3use serde::{Deserialize, Serialize};
4
5use crate::types::*;
6use crate::types::{AgentId, MemoryId, SpaceId, UserId};
7
8/// Default `user_id` for memories deserialized from data written before the
9/// `user_id` axis existed: the nil (shared/global) user. This must be an
10/// explicit function, not `#[serde(default)]`, because `UserId::default()`
11/// mints a fresh random id, which would make old memories look owned by a
12/// random user instead of shared.
13fn default_user_id() -> UserId {
14    UserId::nil()
15}
16
17/// The type classification of a memory.
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
19pub enum MemoryType {
20    /// A specific event or interaction.
21    Episodic,
22    /// A general fact or concept.
23    Semantic,
24    /// A learned skill or procedure.
25    Procedural,
26    /// What NOT to do: learned from failures.
27    AntiPattern,
28    /// A pre-computed reasoning chain or decision template.
29    Reasoning,
30    /// A correction record: "was X, actually Y because Z".
31    Correction,
32}
33
34/// A memory node: the atomic unit of knowledge in MenteDB.
35///
36/// Combines vector embeddings, graph connections, temporal properties,
37/// and flexible attributes in a single primitive.
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct MemoryNode {
40    /// Unique identifier.
41    pub id: MemoryId,
42    /// The agent that owns this memory.
43    pub agent_id: AgentId,
44    /// The end user that owns this memory (orthogonal to `agent_id`).
45    ///
46    /// A memory belongs to both a user and an agent; a scoped query sees it
47    /// only when it is visible on both axes. Defaults to the nil (shared)
48    /// user, both for `new()` and for data written before this field existed.
49    #[serde(default = "default_user_id")]
50    pub user_id: UserId,
51    /// Memory type classification.
52    pub memory_type: MemoryType,
53    /// Embedding vector for semantic similarity search.
54    pub embedding: Embedding,
55    /// Human-readable content.
56    pub content: String,
57    /// When this memory was created.
58    pub created_at: Timestamp,
59    /// When this memory was last accessed.
60    pub accessed_at: Timestamp,
61    /// How many times this memory has been accessed.
62    pub access_count: u32,
63    /// Current salience score (decays over time).
64    pub salience: Salience,
65    /// Confidence in this memory's accuracy.
66    pub confidence: Confidence,
67    /// The memory space this belongs to.
68    pub space_id: SpaceId,
69    /// Flexible key-value attributes.
70    pub attributes: std::collections::HashMap<String, AttributeValue>,
71    /// Tags for bitmap indexing.
72    pub tags: Vec<String>,
73    /// When this fact became true in the real world.
74    /// None means valid since creation.
75    #[serde(default, skip_serializing_if = "Option::is_none")]
76    pub valid_from: Option<Timestamp>,
77    /// When this fact stopped being true.
78    /// None means still valid.
79    #[serde(default, skip_serializing_if = "Option::is_none")]
80    pub valid_until: Option<Timestamp>,
81}
82
83impl MemoryNode {
84    /// Create a new memory node with the given content and embedding.
85    pub fn new(
86        agent_id: AgentId,
87        memory_type: MemoryType,
88        content: String,
89        embedding: Embedding,
90    ) -> Self {
91        let now = std::time::SystemTime::now()
92            .duration_since(std::time::UNIX_EPOCH)
93            .unwrap_or_default()
94            .as_micros() as u64;
95
96        Self {
97            id: MemoryId::new(),
98            agent_id,
99            user_id: UserId::nil(),
100            memory_type,
101            embedding,
102            content,
103            created_at: now,
104            accessed_at: now,
105            access_count: 0,
106            salience: 1.0,
107            confidence: 1.0,
108            space_id: SpaceId::nil(),
109            attributes: std::collections::HashMap::new(),
110            tags: Vec::new(),
111            valid_from: None,
112            valid_until: None,
113        }
114    }
115
116    /// Set the owning end user, returning the node (builder style).
117    ///
118    /// Parallels the `agent_id` constructor argument on the orthogonal user
119    /// axis. Leave unset (nil) for shared/global memories.
120    pub fn with_user_id(mut self, user_id: UserId) -> Self {
121        self.user_id = user_id;
122        self
123    }
124}
125
126impl MemoryNode {
127    /// Returns true if this memory is temporally valid at the given timestamp.
128    pub fn is_valid_at(&self, at: Timestamp) -> bool {
129        let from = self.valid_from.unwrap_or(0);
130        match self.valid_until {
131            Some(until) => at >= from && at < until,
132            None => at >= from,
133        }
134    }
135
136    /// Mark this memory as no longer valid.
137    pub fn invalidate(&mut self, at: Timestamp) {
138        self.valid_until = Some(at);
139    }
140
141    /// Returns true if this memory has been invalidated.
142    pub fn is_invalidated(&self) -> bool {
143        self.valid_until.is_some()
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150    use crate::types::{AgentId, UserId};
151
152    #[test]
153    fn new_memory_has_no_temporal_bounds() {
154        let node = MemoryNode::new(
155            AgentId::new(),
156            MemoryType::Semantic,
157            "test".to_string(),
158            vec![1.0],
159        );
160        assert_eq!(node.valid_from, None);
161        assert_eq!(node.valid_until, None);
162        assert!(!node.is_invalidated());
163    }
164
165    #[test]
166    fn invalidate_memory() {
167        let mut node = MemoryNode::new(
168            AgentId::new(),
169            MemoryType::Semantic,
170            "Alice works at Acme".to_string(),
171            vec![1.0],
172        );
173        assert!(node.is_valid_at(node.created_at));
174
175        node.invalidate(node.created_at + 1_000_000);
176        assert!(node.is_invalidated());
177        assert!(node.is_valid_at(node.created_at));
178        assert!(!node.is_valid_at(node.created_at + 1_000_000));
179    }
180
181    #[test]
182    fn serde_backward_compatible() {
183        let json = r#"{"id":"00000000-0000-0000-0000-000000000001","agent_id":"00000000-0000-0000-0000-000000000002","memory_type":"Semantic","embedding":[1.0],"content":"test","created_at":1000,"accessed_at":1000,"access_count":0,"salience":1.0,"confidence":1.0,"space_id":"00000000-0000-0000-0000-000000000000","attributes":{},"tags":[]}"#;
184        let node: MemoryNode = serde_json::from_str(json).unwrap();
185        assert_eq!(node.valid_from, None);
186        assert_eq!(node.valid_until, None);
187        assert!(node.is_valid_at(5000));
188        // Data written before the user_id axis existed must deserialize as the
189        // nil (shared) user, never a random one. A random default would make
190        // legacy memories look privately owned and vanish from every scoped
191        // query.
192        assert!(
193            node.user_id.is_nil(),
194            "legacy memory (no user_id) must default to the nil user"
195        );
196    }
197
198    #[test]
199    fn with_user_id_sets_owner() {
200        let user = UserId::new();
201        let node = MemoryNode::new(
202            AgentId::new(),
203            MemoryType::Semantic,
204            "test".to_string(),
205            vec![1.0],
206        )
207        .with_user_id(user);
208        assert_eq!(node.user_id, user);
209    }
210}
211
212/// Flexible attribute values for memory metadata.
213#[derive(Debug, Clone, Serialize, Deserialize)]
214pub enum AttributeValue {
215    /// A string value.
216    String(String),
217    /// An integer value.
218    Integer(i64),
219    /// A floating-point value.
220    Float(f64),
221    /// A boolean value.
222    Boolean(bool),
223    /// Raw bytes.
224    Bytes(Vec<u8>),
225}