ruv_neural_memory/
session.rs1use std::collections::HashMap;
4
5use serde::{Deserialize, Serialize};
6
7use ruv_neural_core::embedding::NeuralEmbedding;
8use ruv_neural_core::error::{Result, RuvNeuralError};
9use ruv_neural_core::topology::CognitiveState;
10
11use crate::store::NeuralMemoryStore;
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct SessionMetadata {
16 pub session_id: String,
18 pub subject_id: String,
20 pub start_time: f64,
22 pub end_time: Option<f64>,
24 pub num_embeddings: usize,
26 pub cognitive_states_observed: Vec<CognitiveState>,
28}
29
30pub struct SessionMemory {
32 store: NeuralMemoryStore,
34 current_session: Option<String>,
36 session_metadata: HashMap<String, SessionMetadata>,
38 session_indices: HashMap<String, Vec<usize>>,
40 session_counter: u64,
42}
43
44impl SessionMemory {
45 pub fn new(capacity: usize) -> Self {
47 Self {
48 store: NeuralMemoryStore::new(capacity),
49 current_session: None,
50 session_metadata: HashMap::new(),
51 session_indices: HashMap::new(),
52 session_counter: 0,
53 }
54 }
55
56 pub fn start_session(&mut self, subject_id: &str) -> String {
60 if self.current_session.is_some() {
61 self.end_session();
62 }
63
64 self.session_counter += 1;
65 let session_id = format!("session-{:04}", self.session_counter);
66
67 let metadata = SessionMetadata {
68 session_id: session_id.clone(),
69 subject_id: subject_id.to_string(),
70 start_time: 0.0, end_time: None,
72 num_embeddings: 0,
73 cognitive_states_observed: Vec::new(),
74 };
75
76 self.session_metadata
77 .insert(session_id.clone(), metadata);
78 self.session_indices
79 .insert(session_id.clone(), Vec::new());
80 self.current_session = Some(session_id.clone());
81
82 session_id
83 }
84
85 pub fn end_session(&mut self) {
87 if let Some(ref session_id) = self.current_session.clone() {
88 if let Some(meta) = self.session_metadata.get_mut(session_id) {
89 if let Some(indices) = self.session_indices.get(session_id) {
91 if let Some(&last_idx) = indices.last() {
92 if let Some(emb) = self.store.get(last_idx) {
93 meta.end_time = Some(emb.timestamp);
94 }
95 }
96 }
97 }
98 }
99 self.current_session = None;
100 }
101
102 pub fn store(&mut self, embedding: NeuralEmbedding) -> Result<usize> {
106 let session_id = self
107 .current_session
108 .clone()
109 .ok_or_else(|| RuvNeuralError::Memory("No active session".into()))?;
110
111 let timestamp = embedding.timestamp;
112 let state = embedding.metadata.cognitive_state;
113 let idx = self.store.store(embedding)?;
114
115 if let Some(meta) = self.session_metadata.get_mut(&session_id) {
117 if meta.num_embeddings == 0 {
118 meta.start_time = timestamp;
119 }
120 meta.num_embeddings += 1;
121
122 if let Some(s) = state {
123 if !meta.cognitive_states_observed.contains(&s) {
124 meta.cognitive_states_observed.push(s);
125 }
126 }
127 }
128
129 if let Some(indices) = self.session_indices.get_mut(&session_id) {
130 indices.push(idx);
131 }
132
133 Ok(idx)
134 }
135
136 pub fn get_session_history(&self, session_id: &str) -> Vec<&NeuralEmbedding> {
138 match self.session_indices.get(session_id) {
139 Some(indices) => indices
140 .iter()
141 .filter_map(|&i| self.store.get(i))
142 .collect(),
143 None => Vec::new(),
144 }
145 }
146
147 pub fn get_subject_history(&self, subject_id: &str) -> Vec<&NeuralEmbedding> {
149 self.store.query_by_subject(subject_id)
150 }
151
152 pub fn get_session_metadata(&self, session_id: &str) -> Option<&SessionMetadata> {
154 self.session_metadata.get(session_id)
155 }
156
157 pub fn current_session_id(&self) -> Option<&str> {
159 self.current_session.as_deref()
160 }
161
162 pub fn store_ref(&self) -> &NeuralMemoryStore {
164 &self.store
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171 use ruv_neural_core::brain::Atlas;
172 use ruv_neural_core::embedding::EmbeddingMetadata;
173
174 fn make_embedding(vector: Vec<f64>, subject: &str, timestamp: f64) -> NeuralEmbedding {
175 NeuralEmbedding::new(
176 vector,
177 timestamp,
178 EmbeddingMetadata {
179 subject_id: Some(subject.to_string()),
180 session_id: None,
181 cognitive_state: Some(CognitiveState::Rest),
182 source_atlas: Atlas::Schaefer100,
183 embedding_method: "test".to_string(),
184 },
185 )
186 .unwrap()
187 }
188
189 #[test]
190 fn session_lifecycle() {
191 let mut mem = SessionMemory::new(100);
192
193 assert!(mem.current_session_id().is_none());
195
196 let sid = mem.start_session("subj1");
198 assert_eq!(mem.current_session_id(), Some(sid.as_str()));
199
200 mem.store(make_embedding(vec![1.0, 0.0], "subj1", 1.0))
202 .unwrap();
203 mem.store(make_embedding(vec![0.0, 1.0], "subj1", 2.0))
204 .unwrap();
205
206 let history = mem.get_session_history(&sid);
208 assert_eq!(history.len(), 2);
209
210 let meta = mem.get_session_metadata(&sid).unwrap();
212 assert_eq!(meta.num_embeddings, 2);
213 assert_eq!(meta.subject_id, "subj1");
214
215 mem.end_session();
217 assert!(mem.current_session_id().is_none());
218
219 let meta = mem.get_session_metadata(&sid).unwrap();
220 assert_eq!(meta.end_time, Some(2.0));
221 }
222
223 #[test]
224 fn store_without_session_fails() {
225 let mut mem = SessionMemory::new(100);
226 let result = mem.store(make_embedding(vec![1.0], "subj1", 0.0));
227 assert!(result.is_err());
228 }
229
230 #[test]
231 fn multiple_sessions() {
232 let mut mem = SessionMemory::new(100);
233
234 let s1 = mem.start_session("subj1");
235 mem.store(make_embedding(vec![1.0], "subj1", 1.0))
236 .unwrap();
237 mem.end_session();
238
239 let s2 = mem.start_session("subj1");
240 mem.store(make_embedding(vec![2.0], "subj1", 2.0))
241 .unwrap();
242 mem.store(make_embedding(vec![3.0], "subj1", 3.0))
243 .unwrap();
244 mem.end_session();
245
246 assert_eq!(mem.get_session_history(&s1).len(), 1);
247 assert_eq!(mem.get_session_history(&s2).len(), 2);
248
249 let subject_history = mem.get_subject_history("subj1");
251 assert_eq!(subject_history.len(), 3);
252 }
253
254 #[test]
255 fn starting_new_session_ends_previous() {
256 let mut mem = SessionMemory::new(100);
257
258 let s1 = mem.start_session("subj1");
259 mem.store(make_embedding(vec![1.0], "subj1", 1.0))
260 .unwrap();
261
262 let _s2 = mem.start_session("subj2");
264
265 let meta = mem.get_session_metadata(&s1).unwrap();
266 assert!(meta.end_time.is_some());
267 }
268}