1use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11#[serde(rename_all = "snake_case")]
12pub enum MemoryTier {
13 Working,
15 Episodic,
17 Semantic,
19 Procedural,
21 Meta,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
27#[serde(rename_all = "snake_case")]
28pub enum CognitionKind {
29 Perceive,
31 Deliberate,
33 Decide,
35 Act,
37 Verify,
39 Reflect,
41 Consolidate,
43 Govern,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct MemCube {
55 pub id: String,
57
58 pub tier: MemoryTier,
60
61 pub kind: CognitionKind,
63
64 pub content: String,
66
67 pub source: String,
69
70 pub importance: f32,
72
73 pub confidence: f32,
75
76 pub decay_rate: f32,
78
79 pub caused_by: Vec<String>,
81
82 pub leads_to: Vec<String>,
84
85 pub evidence_for: Vec<String>,
87
88 pub created_at: u64,
90
91 pub last_accessed: u64,
93
94 pub access_count: u32,
96
97 pub session_id: Option<String>,
99}
100
101impl MemCube {
102 pub fn new(
104 tier: MemoryTier,
105 kind: CognitionKind,
106 content: impl Into<String>,
107 source: impl Into<String>,
108 ) -> Self {
109 let now = now_micros();
110 Self {
111 id: ulid::Ulid::new().to_string(),
112 tier,
113 kind,
114 content: content.into(),
115 source: source.into(),
116 importance: 0.5,
117 confidence: 0.5,
118 decay_rate: 0.01,
119 caused_by: Vec::new(),
120 leads_to: Vec::new(),
121 evidence_for: Vec::new(),
122 created_at: now,
123 last_accessed: now,
124 access_count: 0,
125 session_id: None,
126 }
127 }
128
129 pub fn relevance(&self) -> f32 {
134 let now = now_micros();
135 let age_hours = (now.saturating_sub(self.last_accessed)) as f64 / 3_600_000_000.0;
136 let decay = (-self.decay_rate as f64 * age_hours).exp() as f32;
137 self.importance * decay
138 }
139
140 pub fn touch(&mut self) {
142 self.last_accessed = now_micros();
143 self.access_count = self.access_count.saturating_add(1);
144 }
145}
146
147fn now_micros() -> u64 {
148 std::time::SystemTime::now()
149 .duration_since(std::time::UNIX_EPOCH)
150 .unwrap_or_default()
151 .as_micros() as u64
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn memcube_new_defaults() {
160 let cube = MemCube::new(
161 MemoryTier::Semantic,
162 CognitionKind::Consolidate,
163 "Rust is a systems language",
164 "test",
165 );
166 assert_eq!(cube.tier, MemoryTier::Semantic);
167 assert_eq!(cube.kind, CognitionKind::Consolidate);
168 assert_eq!(cube.content, "Rust is a systems language");
169 assert!((cube.importance - 0.5).abs() < f32::EPSILON);
170 assert!((cube.confidence - 0.5).abs() < f32::EPSILON);
171 assert_eq!(cube.access_count, 0);
172 assert!(cube.caused_by.is_empty());
173 }
174
175 #[test]
176 fn memcube_relevance_fresh() {
177 let cube = MemCube::new(MemoryTier::Episodic, CognitionKind::Perceive, "obs", "t");
178 let rel = cube.relevance();
180 assert!(
181 rel > 0.4,
182 "relevance should be near importance for fresh memory: {rel}"
183 );
184 }
185
186 #[test]
187 fn memcube_touch_increments() {
188 let mut cube = MemCube::new(MemoryTier::Procedural, CognitionKind::Act, "approach", "t");
189 let before = cube.last_accessed;
190 cube.touch();
191 assert_eq!(cube.access_count, 1);
192 assert!(cube.last_accessed >= before);
193 }
194
195 #[test]
196 fn memory_tier_serde_roundtrip() {
197 for tier in [
198 MemoryTier::Working,
199 MemoryTier::Episodic,
200 MemoryTier::Semantic,
201 MemoryTier::Procedural,
202 MemoryTier::Meta,
203 ] {
204 let json = serde_json::to_string(&tier).unwrap();
205 let back: MemoryTier = serde_json::from_str(&json).unwrap();
206 assert_eq!(tier, back);
207 }
208 }
209
210 #[test]
211 fn cognition_kind_serde_roundtrip() {
212 for kind in [
213 CognitionKind::Perceive,
214 CognitionKind::Deliberate,
215 CognitionKind::Decide,
216 CognitionKind::Act,
217 CognitionKind::Verify,
218 CognitionKind::Reflect,
219 CognitionKind::Consolidate,
220 CognitionKind::Govern,
221 ] {
222 let json = serde_json::to_string(&kind).unwrap();
223 let back: CognitionKind = serde_json::from_str(&json).unwrap();
224 assert_eq!(kind, back);
225 }
226 }
227
228 #[test]
229 fn memcube_full_serde_roundtrip() {
230 let mut cube = MemCube::new(
231 MemoryTier::Meta,
232 CognitionKind::Reflect,
233 "self-evaluation note",
234 "egri",
235 );
236 cube.caused_by = vec!["A".into()];
237 cube.leads_to = vec!["B".into()];
238 cube.session_id = Some("SESS001".into());
239
240 let json = serde_json::to_string(&cube).unwrap();
241 let back: MemCube = serde_json::from_str(&json).unwrap();
242 assert_eq!(back.tier, MemoryTier::Meta);
243 assert_eq!(back.kind, CognitionKind::Reflect);
244 assert_eq!(back.content, "self-evaluation note");
245 assert_eq!(back.caused_by, vec!["A"]);
246 assert_eq!(back.session_id, Some("SESS001".into()));
247 }
248
249 #[test]
250 fn unique_ids() {
251 let a = MemCube::new(MemoryTier::Working, CognitionKind::Perceive, "a", "t");
252 let b = MemCube::new(MemoryTier::Working, CognitionKind::Perceive, "b", "t");
253 assert_ne!(a.id, b.id);
254 }
255}