1use serde::{Deserialize, Serialize};
2use uuid::Uuid;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
7#[serde(rename_all = "snake_case")]
8pub enum MemoryKind {
9 #[default]
11 Episodic,
12 Semantic,
14 Working,
16 Reflection,
18 ToolCall,
20 Procedural,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
27#[serde(rename_all = "snake_case")]
28pub enum Visibility {
29 #[default]
31 Shared,
32 Private,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct MemoryRecord {
38 pub id: Uuid,
39 pub kind: MemoryKind,
40 pub text: String,
41 pub created_at: i64,
43 pub importance: f32,
45 #[serde(default, skip_serializing_if = "Option::is_none")]
48 pub expires_at: Option<i64>,
49 #[serde(default, skip_serializing_if = "Vec::is_empty")]
52 pub sources: Vec<Uuid>,
53 #[serde(default, skip_serializing_if = "Vec::is_empty")]
56 pub entities: Vec<String>,
57 #[serde(default, skip_serializing_if = "Vec::is_empty")]
58 pub tags: Vec<String>,
59 #[serde(default, skip_serializing_if = "Option::is_none")]
60 pub session_id: Option<String>,
61 #[serde(default, skip_serializing_if = "Option::is_none")]
62 pub agent_id: Option<String>,
63 #[serde(default)]
66 pub visibility: Visibility,
67 #[serde(default, skip_serializing_if = "Option::is_none")]
68 pub metadata: Option<serde_json::Value>,
69 #[serde(default, skip_serializing_if = "Option::is_none")]
72 pub embedding: Option<Vec<f32>>,
73}
74
75#[derive(Debug, Clone, Default, Deserialize)]
76pub struct RememberRequest {
77 pub text: String,
78 #[serde(default)]
79 pub kind: MemoryKind,
80 #[serde(default)]
81 pub tags: Vec<String>,
82 #[serde(default)]
83 pub session_id: Option<String>,
84 #[serde(default)]
85 pub agent_id: Option<String>,
86 #[serde(default)]
87 pub metadata: Option<serde_json::Value>,
88 #[serde(default)]
90 pub importance: Option<f32>,
91 #[serde(default)]
94 pub ttl_seconds: Option<u64>,
95 #[serde(default)]
97 pub visibility: Option<Visibility>,
98 #[serde(default)]
100 pub embedding: Option<Vec<f32>>,
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
105#[serde(rename_all = "snake_case")]
106pub enum RecallStrategy {
107 #[default]
109 Balanced,
110 Semantic,
112 Lexical,
114 Recent,
116 Relational,
119}
120
121impl RecallStrategy {
122 pub fn weights(self) -> (f32, f32, f32, f32) {
124 match self {
125 RecallStrategy::Balanced => (1.0, 1.0, 0.7, 0.3),
126 RecallStrategy::Semantic => (1.0, 0.3, 0.5, 0.1),
127 RecallStrategy::Lexical => (0.3, 1.0, 0.5, 0.1),
128 RecallStrategy::Recent => (0.5, 0.5, 0.3, 1.2),
129 RecallStrategy::Relational => (0.4, 0.4, 1.2, 0.2),
130 }
131 }
132}
133
134#[derive(Debug, Clone, Default, Deserialize)]
135pub struct MemoryFilter {
136 #[serde(default)]
137 pub kinds: Vec<MemoryKind>,
138 #[serde(default)]
139 pub tags: Vec<String>,
140 #[serde(default)]
141 pub session_id: Option<String>,
142 #[serde(default)]
143 pub agent_id: Option<String>,
144 #[serde(default)]
146 pub since: Option<i64>,
147 #[serde(default)]
148 pub until: Option<i64>,
149}
150
151impl MemoryFilter {
152 pub fn is_empty(&self) -> bool {
155 self.kinds.is_empty()
156 && self.tags.is_empty()
157 && self.session_id.is_none()
158 && self.agent_id.is_none()
159 && self.since.is_none()
160 && self.until.is_none()
161 }
162
163 pub fn matches(&self, rec: &MemoryRecord) -> bool {
164 if !self.kinds.is_empty() && !self.kinds.contains(&rec.kind) {
165 return false;
166 }
167 if let Some(s) = &self.session_id {
168 if rec.session_id.as_deref() != Some(s.as_str()) {
169 return false;
170 }
171 }
172 if let Some(a) = &self.agent_id {
173 if rec.agent_id.as_deref() != Some(a.as_str()) {
174 return false;
175 }
176 }
177 if !self.tags.is_empty() && !self.tags.iter().all(|t| rec.tags.contains(t)) {
178 return false;
179 }
180 if let Some(since) = self.since {
181 if rec.created_at < since {
182 return false;
183 }
184 }
185 if let Some(until) = self.until {
186 if rec.created_at > until {
187 return false;
188 }
189 }
190 true
191 }
192}
193
194#[derive(Debug, Clone, Default, Deserialize)]
195pub struct RecallRequest {
196 pub query: String,
197 #[serde(default)]
198 pub top_k: Option<usize>,
199 #[serde(default)]
200 pub strategy: RecallStrategy,
201 #[serde(default)]
202 pub filter: MemoryFilter,
203 #[serde(default)]
206 pub query_embedding: Option<Vec<f32>>,
207 #[serde(default)]
209 pub rerank: Option<bool>,
210 #[serde(default)]
216 pub as_agent: Option<String>,
217 #[serde(default)]
220 pub ef_search: Option<usize>,
221}
222
223#[derive(Debug, Clone, Copy, Serialize)]
226pub struct RecallSignals {
227 pub vector: f32,
229 pub lexical: f32,
231 pub graph: f32,
234 pub recency: f32,
236 pub importance: f32,
238 pub rerank: f32,
241}
242
243#[derive(Debug, Clone, Serialize)]
244pub struct RecallHit {
245 pub record: MemoryRecord,
246 pub score: f32,
247 pub signals: RecallSignals,
248}
249
250#[derive(Debug, Clone, Serialize)]
251pub struct EngineStats {
252 pub total_memories: usize,
253 pub vector_indexed: usize,
254 pub lexical_indexed: usize,
255 pub embedding_dim: usize,
258 pub vector_dim: Option<usize>,
261 pub entities: usize,
263 pub quantized: bool,
265 pub wal_tail_ops: usize,
267}
268
269#[derive(Debug, Clone)]
271pub struct LifecycleConfig {
272 pub working_ttl_secs: u64,
274 pub consolidate_after_secs: u64,
276 pub min_batch: usize,
278 pub max_batch: usize,
280}
281
282impl Default for LifecycleConfig {
283 fn default() -> Self {
284 Self {
285 working_ttl_secs: 24 * 3600,
286 consolidate_after_secs: 7 * 24 * 3600,
287 min_batch: 4,
288 max_batch: 12,
289 }
290 }
291}
292
293#[derive(Debug, Clone, Default, Serialize)]
294pub struct LifecycleReport {
295 pub expired_swept: usize,
297 pub batches_consolidated: usize,
299 pub summaries: Vec<Uuid>,
301 pub checkpointed: bool,
303}
304
305#[derive(Debug, Clone, Serialize, Deserialize)]
307pub struct Snapshot {
308 pub created_at: i64,
309 #[serde(default, skip_serializing_if = "Option::is_none")]
310 pub session_id: Option<String>,
311 pub records: Vec<MemoryRecord>,
312}
313
314pub fn now_ms() -> i64 {
315 std::time::SystemTime::now()
316 .duration_since(std::time::UNIX_EPOCH)
317 .map(|d| d.as_millis() as i64)
318 .unwrap_or(0)
319}