1use crate::{Database, Point};
8use serde_json::json;
9use std::sync::Arc;
10
11use super::error::AgentMemoryError;
12use super::memory_helpers;
13use super::temporal_index::TemporalIndex;
14use super::ttl::{MemoryKind, MemoryTtl};
15
16pub struct EpisodicMemory {
21 collection_name: String,
22 db: Arc<Database>,
23 dimension: usize,
24 ttl: Arc<MemoryTtl>,
25 temporal_index: Arc<TemporalIndex>,
26 next_edge_id: std::sync::atomic::AtomicU64,
28}
29
30impl EpisodicMemory {
31 const COLLECTION_NAME: &'static str = "_episodic_memory";
32
33 #[must_use]
35 pub fn dimension(&self) -> usize {
36 self.dimension
37 }
38
39 pub fn new_from_db(db: Arc<Database>, dimension: usize) -> Result<Self, AgentMemoryError> {
45 Self::new(
46 db,
47 dimension,
48 Arc::new(MemoryTtl::new()),
49 Arc::new(TemporalIndex::new()),
50 )
51 }
52 pub(crate) fn new(
53 db: Arc<Database>,
54 dimension: usize,
55 ttl: Arc<MemoryTtl>,
56 temporal_index: Arc<TemporalIndex>,
57 ) -> Result<Self, AgentMemoryError> {
58 let collection_name = Self::COLLECTION_NAME.to_string();
59 let actual_dimension =
60 memory_helpers::open_or_create_collection(&db, &collection_name, dimension)?;
61
62 if temporal_index.is_empty() {
63 if let Some(collection) = db.get_vector_collection(&collection_name) {
64 Self::rebuild_temporal_index(&collection.inner, &temporal_index);
65 }
66 }
67 memory_helpers::rebuild_ttl_from_payloads(
68 &db,
69 &collection_name,
70 &ttl,
71 MemoryKind::Episodic,
72 )?;
73
74 let next_edge_id = memory_helpers::seed_edge_counter(&memory_helpers::get_collection(
75 &db,
76 &collection_name,
77 )?);
78
79 Ok(Self {
80 collection_name,
81 db,
82 dimension: actual_dimension,
83 ttl,
84 temporal_index,
85 next_edge_id,
86 })
87 }
88 fn rebuild_temporal_index(
89 collection: &crate::collection::Collection,
90 temporal_index: &TemporalIndex,
91 ) {
92 let all_ids = collection.all_ids();
93 let points = collection.get(&all_ids);
94 for point in points.into_iter().flatten() {
95 if let Some(payload) = &point.payload {
96 if let Some(ts) = payload.get("timestamp").and_then(serde_json::Value::as_i64) {
97 temporal_index.insert(point.id, ts);
98 }
99 }
100 }
101 }
102
103 #[must_use]
105 pub fn collection_name(&self) -> &str {
106 &self.collection_name
107 }
108
109 pub fn record(
116 &self,
117 event_id: u64,
118 description: &str,
119 timestamp: i64,
120 embedding: Option<&[f32]>,
121 ) -> Result<(), AgentMemoryError> {
122 self.record_internal(event_id, description, timestamp, embedding, None)
123 }
124
125 fn record_internal(
128 &self,
129 event_id: u64,
130 description: &str,
131 timestamp: i64,
132 embedding: Option<&[f32]>,
133 expires_at: Option<u64>,
134 ) -> Result<(), AgentMemoryError> {
135 let vector = memory_helpers::resolve_embedding(self.dimension, embedding)?;
136 let collection = memory_helpers::get_collection(&self.db, &self.collection_name)?;
137
138 let mut payload = json!({
139 "description": description,
140 "timestamp": timestamp
141 });
142 memory_helpers::attach_expiry(&mut payload, expires_at);
143 let point = Point::new(event_id, vector, Some(payload));
144
145 memory_helpers::upsert_points(&collection, vec![point])?;
146 self.temporal_index.insert(event_id, timestamp);
147
148 Ok(())
149 }
150
151 pub fn record_with_ttl(
168 &self,
169 event_id: u64,
170 description: &str,
171 timestamp: i64,
172 embedding: Option<&[f32]>,
173 ttl_seconds: u64,
174 ) -> Result<(), AgentMemoryError> {
175 if ttl_seconds == 0 {
176 if let Some(emb) = embedding {
177 memory_helpers::validate_dimension(self.dimension, emb.len())?;
178 }
179 return self.delete(event_id);
180 }
181 let expires_at = MemoryTtl::now().saturating_add(ttl_seconds);
182 self.record_internal(
183 event_id,
184 description,
185 timestamp,
186 embedding,
187 Some(expires_at),
188 )?;
189 self.ttl
190 .set_expiry(MemoryKind::Episodic, event_id, expires_at);
191 Ok(())
192 }
193
194 pub fn set_ttl_durable(&self, event_id: u64, ttl_seconds: u64) -> Result<(), AgentMemoryError> {
206 memory_helpers::set_ttl_durable(
207 &self.db,
208 &self.collection_name,
209 &self.ttl,
210 MemoryKind::Episodic,
211 event_id,
212 ttl_seconds,
213 )
214 }
215
216 pub fn relate(
224 &self,
225 from_id: u64,
226 to_id: u64,
227 rel_type: &str,
228 properties: Option<&serde_json::Map<String, serde_json::Value>>,
229 ) -> Result<u64, AgentMemoryError> {
230 memory_helpers::relate_memory_points(
231 &memory_helpers::MemorySubsystem {
232 db: &self.db,
233 collection_name: &self.collection_name,
234 ttl: &self.ttl,
235 kind: MemoryKind::Episodic,
236 next_edge_id: &self.next_edge_id,
237 },
238 from_id,
239 to_id,
240 rel_type,
241 properties,
242 )
243 }
244
245 pub fn relations(
251 &self,
252 id: u64,
253 ) -> Result<Vec<crate::collection::graph::GraphEdge>, AgentMemoryError> {
254 memory_helpers::relations_of(
255 &self.db,
256 &self.collection_name,
257 id,
258 &self.ttl,
259 MemoryKind::Episodic,
260 )
261 }
262
263 pub fn unrelate(&self, edge_id: u64) -> Result<bool, AgentMemoryError> {
269 memory_helpers::unrelate_edge(&self.db, &self.collection_name, edge_id)
270 }
271
272 pub fn recent(
278 &self,
279 limit: usize,
280 since_timestamp: Option<i64>,
281 ) -> Result<Vec<(u64, String, i64)>, AgentMemoryError> {
282 let collection = memory_helpers::get_collection(&self.db, &self.collection_name)?;
283
284 Ok(self.fetch_temporal_events(
285 limit,
286 |fetch_limit| {
287 let entries = self.temporal_index.recent(fetch_limit, since_timestamp);
288 entries.iter().map(|e| e.id).collect()
289 },
290 &collection,
291 ))
292 }
293
294 pub fn older_than(
300 &self,
301 timestamp: i64,
302 limit: usize,
303 ) -> Result<Vec<(u64, String, i64)>, AgentMemoryError> {
304 let collection = memory_helpers::get_collection(&self.db, &self.collection_name)?;
305
306 Ok(self.fetch_temporal_events(
307 limit,
308 |fetch_limit| {
309 let entries = self.temporal_index.older_than(timestamp, fetch_limit);
310 entries.iter().map(|e| e.id).collect()
311 },
312 &collection,
313 ))
314 }
315
316 pub fn recall_similar(
323 &self,
324 query_embedding: &[f32],
325 k: usize,
326 ) -> Result<Vec<(u64, String, i64, f32)>, AgentMemoryError> {
327 let results = memory_helpers::search_filtered(
328 &self.db,
329 &self.collection_name,
330 self.dimension,
331 query_embedding,
332 k,
333 &self.ttl,
334 MemoryKind::Episodic,
335 )?;
336
337 Ok(results
338 .into_iter()
339 .filter_map(|r| {
340 let (desc, ts) = extract_event_fields(&r.point)?;
341 Some((r.point.id, desc, ts, r.score))
342 })
343 .collect())
344 }
345
346 pub fn get_with_embedding(
352 &self,
353 id: u64,
354 ) -> Result<Option<(String, i64, Vec<f32>)>, AgentMemoryError> {
355 let collection = memory_helpers::get_collection(&self.db, &self.collection_name)?;
356
357 let points = collection.get(&[id]);
358 let Some(point) = points.into_iter().flatten().next() else {
359 return Ok(None);
360 };
361
362 if self.ttl.is_expired(MemoryKind::Episodic, point.id) {
363 return Ok(None);
364 }
365
366 let Some(payload) = point.payload.as_ref() else {
367 return Ok(None);
368 };
369
370 let desc = payload
371 .get("description")
372 .and_then(serde_json::Value::as_str)
373 .unwrap_or("")
374 .to_string();
375 let ts = payload
376 .get("timestamp")
377 .and_then(serde_json::Value::as_i64)
378 .unwrap_or(0);
379
380 Ok(Some((desc, ts, point.vector.clone())))
381 }
382
383 pub fn delete(&self, id: u64) -> Result<(), AgentMemoryError> {
389 let collection = memory_helpers::get_collection(&self.db, &self.collection_name)?;
390 memory_helpers::delete_from_collection(&collection, &[id])?;
391
392 self.temporal_index.remove(id);
393 self.ttl.remove(MemoryKind::Episodic, id);
394 Ok(())
395 }
396
397 pub fn serialize(&self) -> Result<Vec<u8>, AgentMemoryError> {
403 let collection = memory_helpers::get_collection(&self.db, &self.collection_name)?;
404 let all_ids = self.temporal_index.all_ids();
405 memory_helpers::serialize_points(&collection, &all_ids)
406 }
407
408 pub fn deserialize(&self, data: &[u8]) -> Result<(), AgentMemoryError> {
415 let collection = memory_helpers::get_collection(&self.db, &self.collection_name)?;
416 if let Some(points) = memory_helpers::deserialize_into_collection(data, &collection)? {
417 self.rebuild_temporal_from_points(&points);
418 }
419 Ok(())
420 }
421
422 fn fetch_temporal_events(
424 &self,
425 limit: usize,
426 id_fetcher: impl Fn(usize) -> Vec<u64>,
427 collection: &crate::collection::Collection,
428 ) -> Vec<(u64, String, i64)> {
429 let indexed = self.temporal_index.len();
433 let mut events = Vec::with_capacity(limit.min(indexed));
434 let mut fetch_limit = limit.saturating_mul(2);
435 let max_fetch = indexed.max(limit).saturating_mul(2);
439
440 while events.len() < limit && fetch_limit <= max_fetch {
441 let ids = id_fetcher(fetch_limit);
442 if ids.is_empty() {
443 break;
444 }
445 let id_count = ids.len();
446
447 events = Self::filter_live_events(&self.ttl, collection, &ids, limit);
448
449 if events.len() >= limit || id_count < fetch_limit {
450 break;
451 }
452 fetch_limit = fetch_limit.saturating_mul(2);
453 }
454
455 events
456 }
457
458 fn filter_live_events(
460 ttl: &MemoryTtl,
461 collection: &crate::collection::Collection,
462 ids: &[u64],
463 limit: usize,
464 ) -> Vec<(u64, String, i64)> {
465 collection
466 .get(ids)
467 .into_iter()
468 .flatten()
469 .filter(|p| !ttl.is_expired(MemoryKind::Episodic, p.id))
470 .filter_map(|p| {
471 let (desc, ts) = extract_event_fields(&p)?;
472 Some((p.id, desc, ts))
473 })
474 .take(limit)
475 .collect()
476 }
477
478 fn rebuild_temporal_from_points(&self, points: &[Point]) {
480 self.temporal_index.clear();
481 for point in points {
482 if let Some(payload) = &point.payload {
483 if let Some(ts) = payload.get("timestamp").and_then(serde_json::Value::as_i64) {
484 self.temporal_index.insert(point.id, ts);
485 }
486 }
487 }
488 }
489}
490
491fn extract_event_fields(point: &Point) -> Option<(String, i64)> {
493 let payload = point.payload.as_ref()?;
494 let desc = payload.get("description")?.as_str()?.to_string();
495 let ts = payload.get("timestamp")?.as_i64()?;
496 Some((desc, ts))
497}