daimon_core/
episodic_memory.rs1use std::future::Future;
9use std::pin::Pin;
10use std::sync::Arc;
11
12use serde_json::Value;
13
14use crate::error::Result;
15
16#[derive(Debug, Clone)]
18pub struct EpisodicEvent {
19 pub id: String,
21 pub event_type: String,
23 pub payload: Value,
25 pub timestamp_ms: i64,
27}
28
29#[derive(Debug, Clone, Default)]
32pub struct EpisodicQuery {
33 pub event_type: Option<String>,
35 pub since_ms: Option<i64>,
37 pub until_ms: Option<i64>,
39 pub limit: Option<usize>,
41}
42
43impl EpisodicQuery {
44 pub fn all() -> Self {
46 Self::default()
47 }
48
49 pub fn of_type(mut self, event_type: impl Into<String>) -> Self {
51 self.event_type = Some(event_type.into());
52 self
53 }
54
55 pub fn between(mut self, since_ms: i64, until_ms: i64) -> Self {
57 self.since_ms = Some(since_ms);
58 self.until_ms = Some(until_ms);
59 self
60 }
61
62 pub fn limit(mut self, limit: usize) -> Self {
64 self.limit = Some(limit);
65 self
66 }
67}
68
69pub trait EpisodicMemory: Send + Sync {
71 fn record(
74 &self,
75 event_type: &str,
76 payload: Value,
77 ) -> impl Future<Output = Result<String>> + Send;
78
79 fn query(
81 &self,
82 query: EpisodicQuery,
83 ) -> impl Future<Output = Result<Vec<EpisodicEvent>>> + Send;
84
85 fn clear(&self) -> impl Future<Output = Result<()>> + Send;
87}
88
89pub trait ErasedEpisodicMemory: Send + Sync {
92 fn record_erased<'a>(
93 &'a self,
94 event_type: &'a str,
95 payload: Value,
96 ) -> Pin<Box<dyn Future<Output = Result<String>> + Send + 'a>>;
97
98 fn query_erased(
99 &self,
100 query: EpisodicQuery,
101 ) -> Pin<Box<dyn Future<Output = Result<Vec<EpisodicEvent>>> + Send + '_>>;
102
103 fn clear_erased(&self) -> Pin<Box<dyn Future<Output = Result<()>> + Send + '_>>;
104}
105
106impl<T: EpisodicMemory> ErasedEpisodicMemory for T {
107 fn record_erased<'a>(
108 &'a self,
109 event_type: &'a str,
110 payload: Value,
111 ) -> Pin<Box<dyn Future<Output = Result<String>> + Send + 'a>> {
112 Box::pin(self.record(event_type, payload))
113 }
114
115 fn query_erased(
116 &self,
117 query: EpisodicQuery,
118 ) -> Pin<Box<dyn Future<Output = Result<Vec<EpisodicEvent>>> + Send + '_>> {
119 Box::pin(self.query(query))
120 }
121
122 fn clear_erased(&self) -> Pin<Box<dyn Future<Output = Result<()>> + Send + '_>> {
123 Box::pin(self.clear())
124 }
125}
126
127pub type SharedEpisodicMemory = Arc<dyn ErasedEpisodicMemory>;
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133 use std::sync::Mutex;
134
135 struct VecEpisodicMemory(Mutex<Vec<EpisodicEvent>>);
136
137 impl EpisodicMemory for VecEpisodicMemory {
138 async fn record(&self, event_type: &str, payload: Value) -> Result<String> {
139 let mut events = self.0.lock().unwrap();
140 let seq = events.len() as i64;
141 let id = format!("evt-{seq}");
142 events.push(EpisodicEvent {
143 id: id.clone(),
144 event_type: event_type.to_string(),
145 payload,
146 timestamp_ms: seq,
147 });
148 Ok(id)
149 }
150
151 async fn query(&self, query: EpisodicQuery) -> Result<Vec<EpisodicEvent>> {
152 let events = self.0.lock().unwrap();
153 let mut matched: Vec<EpisodicEvent> = events
154 .iter()
155 .filter(|e| {
156 query.event_type.as_ref().is_none_or(|t| &e.event_type == t)
157 && query.since_ms.is_none_or(|s| e.timestamp_ms >= s)
158 && query.until_ms.is_none_or(|u| e.timestamp_ms <= u)
159 })
160 .cloned()
161 .collect();
162 matched.sort_by_key(|e| std::cmp::Reverse(e.timestamp_ms));
163 if let Some(limit) = query.limit {
164 matched.truncate(limit);
165 }
166 Ok(matched)
167 }
168
169 async fn clear(&self) -> Result<()> {
170 self.0.lock().unwrap().clear();
171 Ok(())
172 }
173 }
174
175 #[tokio::test]
176 async fn episodic_memory_is_implementable_from_core_alone() {
177 let mem = VecEpisodicMemory(Mutex::new(Vec::new()));
178 mem.record("login", serde_json::json!({"user": "a"}))
179 .await
180 .unwrap();
181 mem.record("logout", serde_json::json!({"user": "a"}))
182 .await
183 .unwrap();
184 mem.record("login", serde_json::json!({"user": "b"}))
185 .await
186 .unwrap();
187
188 let logins = mem
189 .query(EpisodicQuery::all().of_type("login"))
190 .await
191 .unwrap();
192 assert_eq!(logins.len(), 2);
193 assert_eq!(logins[0].payload["user"], "b");
195
196 let ranged = mem.query(EpisodicQuery::all().between(0, 0)).await.unwrap();
197 assert_eq!(ranged.len(), 1);
198
199 let limited = mem.query(EpisodicQuery::all().limit(1)).await.unwrap();
200 assert_eq!(limited.len(), 1);
201
202 mem.clear().await.unwrap();
203 assert!(mem.query(EpisodicQuery::all()).await.unwrap().is_empty());
204
205 let shared: SharedEpisodicMemory = Arc::new(VecEpisodicMemory(Mutex::new(Vec::new())));
206 shared
207 .record_erased("test", serde_json::json!(null))
208 .await
209 .unwrap();
210 assert_eq!(
211 shared
212 .query_erased(EpisodicQuery::all())
213 .await
214 .unwrap()
215 .len(),
216 1
217 );
218 }
219}