1use serde::{Deserialize, Serialize};
6use crate::trust::TrustData;
7
8pub use crate::trust::Confidence;
10
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
13#[serde(rename_all = "camelCase")]
14pub enum MemLevel {
15 Activity,
16 Fact,
17 Cornerstone,
18}
19
20impl MemLevel {
21 pub fn as_str(&self) -> &'static str {
22 match self {
23 MemLevel::Activity => "activity",
24 MemLevel::Fact => "fact",
25 MemLevel::Cornerstone => "cornerstone",
26 }
27 }
28
29 pub fn from_str(s: &str) -> Option<Self> {
30 match s {
31 "activity" => Some(MemLevel::Activity),
32 "fact" => Some(MemLevel::Fact),
33 "cornerstone" => Some(MemLevel::Cornerstone),
34 _ => None,
35 }
36 }
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
41#[serde(rename_all = "camelCase")]
42pub enum MemoryZone {
43 Core,
45 Work,
47 Project,
49 Episode,
51 General,
53}
54
55impl MemoryZone {
56 pub fn as_str(&self) -> &'static str {
57 match self {
58 MemoryZone::Core => "core",
59 MemoryZone::Work => "work",
60 MemoryZone::Project => "project",
61 MemoryZone::Episode => "episode",
62 MemoryZone::General => "general",
63 }
64 }
65
66 pub fn from_str(s: &str) -> Option<Self> {
67 match s {
68 "core" => Some(MemoryZone::Core),
69 "work" => Some(MemoryZone::Work),
70 "project" => Some(MemoryZone::Project),
71 "episode" => Some(MemoryZone::Episode),
72 "general" => Some(MemoryZone::General),
73 _ => None,
74 }
75 }
76}
77
78impl Default for MemoryZone {
79 fn default() -> Self {
80 MemoryZone::General
81 }
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct Mem {
87 pub id: String,
89 pub level: MemLevel,
91 #[serde(default)]
93 pub zone: MemoryZone,
94 pub content: String,
96 #[serde(default = "default_weight")]
98 pub weight: f64,
99 #[serde(default)]
101 pub tags: Vec<String>,
102 #[serde(skip_serializing_if = "Option::is_none")]
104 pub source: Option<String>,
105 #[serde(skip_serializing_if = "Option::is_none")]
107 pub metadata: Option<serde_json::Value>,
108 #[serde(skip_serializing_if = "Option::is_none")]
110 pub last_matched: Option<String>,
111 #[serde(default)]
113 pub match_count: u32,
114 #[serde(default)]
116 pub loaded_count: u32,
117 #[serde(default)]
119 pub referenced_count: u32,
120 #[serde(skip_serializing_if = "Option::is_none")]
122 pub last_loaded: Option<String>,
123 #[serde(skip_serializing_if = "Option::is_none")]
125 pub last_referenced: Option<String>,
126 #[serde(skip_serializing_if = "Option::is_none")]
128 pub supersedes: Option<Vec<String>>,
129 #[serde(skip_serializing_if = "Option::is_none")]
131 pub superseded_at: Option<String>,
132 #[serde(skip_serializing_if = "Option::is_none")]
134 pub superseded_by: Option<String>,
135 #[serde(skip_serializing_if = "Option::is_none")]
137 pub trust: Option<TrustData>,
138 pub created_at: String,
140 pub updated_at: String,
142}
143
144fn default_weight() -> f64 { 0.5 }
145
146impl Mem {
147 pub fn short_id(&self) -> &str {
149 if self.id.len() > 8 {
150 &self.id[self.id.len() - 8..]
151 } else {
152 &self.id
153 }
154 }
155
156 pub fn is_superseded(&self) -> bool {
158 self.superseded_by.is_some()
159 }
160}
161
162#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct Warning {
165 #[serde(rename = "memoryId")]
166 pub memory_id: String,
167 #[serde(rename = "cornerstoneContent")]
168 pub cornerstone_content: String,
169 pub violation: String,
170 pub severity: String,
171 pub timestamp: String,
172}
173
174#[derive(Debug, Clone, Serialize, Deserialize)]
176pub struct SessionInfo {
177 pub id: String,
178 #[serde(rename = "startedAt")]
179 pub started_at: String,
180 #[serde(skip_serializing_if = "Option::is_none")]
181 #[serde(rename = "endedAt")]
182 pub ended_at: Option<String>,
183 #[serde(rename = "activityCount", default)]
184 pub activity_count: u32,
185 #[serde(skip_serializing_if = "Option::is_none")]
186 pub summary: Option<String>,
187}
188
189#[derive(Debug, Clone, Serialize, Deserialize)]
191pub struct KernelState {
192 #[serde(default)]
193 pub memories: Vec<Mem>,
194 #[serde(default, rename = "reflectedIds")]
195 pub reflected_ids: Vec<String>,
196 #[serde(skip_serializing_if = "Option::is_none", rename = "lastReflectionAt")]
197 pub last_reflection_at: Option<String>,
198 #[serde(skip_serializing_if = "Option::is_none", rename = "lastCuratedAt")]
199 pub last_curated_at: Option<String>,
200 #[serde(default)]
201 pub warnings: Vec<Warning>,
202 #[serde(default)]
203 pub sessions: Vec<SessionInfo>,
204 #[serde(default, rename = "pendingFacts")]
205 pub pending_facts: Vec<serde_json::Value>,
206 #[serde(skip_serializing_if = "Option::is_none")]
207 pub skills: Option<Vec<serde_json::Value>>,
208 #[serde(skip_serializing_if = "Option::is_none")]
209 pub goals: Option<Vec<serde_json::Value>>,
210}
211
212impl KernelState {
213 pub fn load(path: &str) -> Result<Self, String> {
215 let content = std::fs::read_to_string(path)
216 .map_err(|e| format!("Failed to read {}: {}", path, e))?;
217 serde_json::from_str(&content)
218 .map_err(|e| format!("Failed to parse {}: {}", path, e))
219 }
220
221 pub fn save(&self, path: &str) -> Result<(), String> {
223 let json = serde_json::to_string_pretty(self)
224 .map_err(|e| format!("Failed to serialize: {}", e))?;
225 std::fs::write(path, json)
226 .map_err(|e| format!("Failed to write {}: {}", path, e))
227 }
228
229 pub fn list_active(&self) -> Vec<&Mem> {
231 self.memories.iter().filter(|m| !m.is_superseded()).collect()
232 }
233
234 pub fn find_mem(&self, id: &str) -> Option<&Mem> {
236 self.memories.iter().find(|m| m.id == id || m.id.ends_with(id))
237 }
238
239 pub fn find_mem_mut(&mut self, id: &str) -> Option<&mut Mem> {
241 self.memories.iter_mut().find(|m| m.id == id || m.id.ends_with(id))
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248
249 #[test]
250 fn test_mem_level_roundtrip() {
251 assert_eq!(MemLevel::from_str("activity"), Some(MemLevel::Activity));
252 assert_eq!(MemLevel::from_str("fact"), Some(MemLevel::Fact));
253 assert_eq!(MemLevel::from_str("cornerstone"), Some(MemLevel::Cornerstone));
254 assert_eq!(MemLevel::from_str("invalid"), None);
255 assert_eq!(MemLevel::Activity.as_str(), "activity");
256 }
257
258 #[test]
259 fn test_memory_zone_roundtrip() {
260 assert_eq!(MemoryZone::from_str("core"), Some(MemoryZone::Core));
261 assert_eq!(MemoryZone::from_str("general"), Some(MemoryZone::General));
262 assert_eq!(MemoryZone::from_str("unknown"), None);
263 }
264
265 #[test]
266 fn test_short_id() {
267 let mem = Mem {
268 id: "1779000000001-abcdefgh".into(),
269 level: MemLevel::Fact,
270 zone: MemoryZone::General,
271 content: "test".into(),
272 weight: 0.5,
273 tags: vec![],
274 source: None,
275 metadata: None,
276 last_matched: None,
277 match_count: 0,
278 loaded_count: 0,
279 referenced_count: 0,
280 last_loaded: None,
281 last_referenced: None,
282 supersedes: None,
283 superseded_at: None,
284 superseded_by: None,
285 trust: None,
286 created_at: "2026-01-01T00:00:00Z".into(),
287 updated_at: "2026-01-01T00:00:00Z".into(),
288 };
289 assert_eq!(mem.short_id(), "abcdefgh");
290 }
291
292 #[test]
293 fn test_list_active_filters_superseded() {
294 let mut state = KernelState {
295 memories: vec![],
296 reflected_ids: vec![],
297 last_reflection_at: None,
298 last_curated_at: None,
299 warnings: vec![],
300 sessions: vec![],
301 pending_facts: vec![],
302 skills: None,
303 goals: None,
304 };
305
306 let m1 = Mem {
307 id: "m1".into(),
308 level: MemLevel::Fact,
309 zone: MemoryZone::General,
310 content: "active".into(),
311 weight: 0.5,
312 tags: vec![],
313 source: None, metadata: None,
314 last_matched: None, match_count: 0, loaded_count: 0, referenced_count: 0,
315 last_loaded: None, last_referenced: None,
316 supersedes: None, superseded_at: None, superseded_by: None,
317 trust: None,
318 created_at: "2026-01-01T00:00:00Z".into(),
319 updated_at: "2026-01-01T00:00:00Z".into(),
320 };
321 let m2 = Mem {
322 id: "m2".into(),
323 level: MemLevel::Fact,
324 zone: MemoryZone::General,
325 content: "superseded".into(),
326 weight: 0.5,
327 tags: vec![],
328 source: None, metadata: None,
329 last_matched: None, match_count: 0, loaded_count: 0, referenced_count: 0,
330 last_loaded: None, last_referenced: None,
331 supersedes: None, superseded_at: None,
332 superseded_by: Some("m3".into()),
333 trust: None,
334 created_at: "2026-01-01T00:00:00Z".into(),
335 updated_at: "2026-01-01T00:00:00Z".into(),
336 };
337 state.memories = vec![m1, m2];
338 let active = state.list_active();
339 assert_eq!(active.len(), 1);
340 assert_eq!(active[0].id, "m1");
341 }
342}