Skip to main content

locus_sdk/domain/
memory.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use locus_core_rs::domain::models::{AvecState, PsiRange, SttpNode};
4
5#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
6#[serde(rename_all = "snake_case")]
7pub enum FallbackPolicy {
8    Never,
9    OnEmpty,
10    Always,
11}
12
13impl Default for FallbackPolicy {
14    fn default() -> Self {
15        Self::OnEmpty
16    }
17}
18
19#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
20#[serde(rename_all = "snake_case")]
21pub enum StrictnessMode {
22    Precision,
23    Balanced,
24    Recall,
25}
26
27impl Default for StrictnessMode {
28    fn default() -> Self {
29        Self::Balanced
30    }
31}
32
33#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
34#[serde(rename_all = "snake_case")]
35pub enum MemorySortField {
36    Timestamp,
37    UpdatedAt,
38    Psi,
39    Rho,
40    Kappa,
41}
42
43impl Default for MemorySortField {
44    fn default() -> Self {
45        Self::Timestamp
46    }
47}
48
49#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
50#[serde(rename_all = "snake_case")]
51pub enum SortDirection {
52    Asc,
53    Desc,
54}
55
56impl Default for SortDirection {
57    fn default() -> Self {
58        Self::Desc
59    }
60}
61
62#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
63#[serde(rename_all = "snake_case")]
64pub enum RetrievalPath {
65    ResonanceOnly,
66    SemanticOnly,
67    Hybrid,
68    LexicalFallback,
69}
70
71#[derive(Debug, Clone, Default, Serialize, Deserialize)]
72#[serde(rename_all = "camelCase")]
73pub struct MemoryScope {
74    pub tenant_id: Option<String>,
75    pub session_ids: Option<Vec<String>>,
76    pub tiers: Option<Vec<String>>,
77    pub from_utc: Option<DateTime<Utc>>,
78    pub to_utc: Option<DateTime<Utc>>,
79}
80
81#[derive(Debug, Clone, Default, Serialize, Deserialize)]
82#[serde(rename_all = "camelCase")]
83pub struct MetricRange {
84    pub min: Option<f32>,
85    pub max: Option<f32>,
86}
87
88impl MetricRange {
89    pub fn contains(&self, value: f32) -> bool {
90        if let Some(min) = self.min {
91            if value < min {
92                return false;
93            }
94        }
95        if let Some(max) = self.max {
96            if value > max {
97                return false;
98            }
99        }
100        true
101    }
102}
103
104#[derive(Debug, Clone, Default, Serialize, Deserialize)]
105#[serde(rename_all = "camelCase")]
106pub struct MemoryFilter {
107    pub has_embedding: Option<bool>,
108    pub embedding_model: Option<String>,
109    pub psi: Option<MetricRange>,
110    pub rho: Option<MetricRange>,
111    pub kappa: Option<MetricRange>,
112    pub text_contains: Option<String>,
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize)]
116#[serde(rename_all = "camelCase")]
117pub struct MemoryPage {
118    pub limit: usize,
119    pub cursor: Option<String>,
120}
121
122impl Default for MemoryPage {
123    fn default() -> Self {
124        Self {
125            limit: 50,
126            cursor: None,
127        }
128    }
129}
130
131#[derive(Debug, Clone, Default, Serialize, Deserialize)]
132#[serde(rename_all = "camelCase")]
133pub struct MemorySort {
134    pub field: MemorySortField,
135    pub direction: SortDirection,
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
139#[serde(rename_all = "camelCase")]
140pub struct MemoryScoring {
141    pub resonance_weight: f32,
142    pub semantic_weight: f32,
143    pub lexical_weight: f32,
144    pub alpha: f32,
145    pub beta: f32,
146    pub fallback_policy: FallbackPolicy,
147    pub strictness: StrictnessMode,
148}
149
150impl Default for MemoryScoring {
151    fn default() -> Self {
152        Self {
153            resonance_weight: 1.0,
154            semantic_weight: 0.0,
155            lexical_weight: 0.0,
156            alpha: 0.7,
157            beta: 0.3,
158            fallback_policy: FallbackPolicy::OnEmpty,
159            strictness: StrictnessMode::Balanced,
160        }
161    }
162}
163
164#[derive(Debug, Clone, Default, Serialize, Deserialize)]
165#[serde(rename_all = "camelCase")]
166pub struct MemoryFindRequest {
167    pub scope: MemoryScope,
168    pub filter: MemoryFilter,
169    pub page: MemoryPage,
170    pub sort: MemorySort,
171}
172
173#[derive(Debug, Clone)]
174pub struct MemoryFindResult {
175    pub nodes: Vec<SttpNode>,
176    pub retrieved: usize,
177    pub has_more: bool,
178    pub next_cursor: Option<String>,
179}
180
181#[derive(Debug, Clone, Default)]
182pub struct MemoryRecallRequest {
183    pub scope: MemoryScope,
184    pub filter: MemoryFilter,
185    pub page: MemoryPage,
186    pub scoring: MemoryScoring,
187    pub current_avec: Option<AvecState>,
188    pub query_text: Option<String>,
189    pub query_embedding: Option<Vec<f32>>,
190}
191
192#[derive(Debug, Clone)]
193pub struct MemoryRecallResult {
194    pub nodes: Vec<SttpNode>,
195    pub retrieved: usize,
196    pub psi_range: PsiRange,
197    pub retrieval_path: RetrievalPath,
198    pub has_more: bool,
199    pub next_cursor: Option<String>,
200}
201
202#[derive(Debug, Clone)]
203pub struct MemoryExplainRequest {
204    pub recall: MemoryRecallRequest,
205}
206
207#[derive(Debug, Clone)]
208pub struct MemoryExplainStage {
209    pub stage: String,
210    pub count: usize,
211}
212
213#[derive(Debug, Clone)]
214pub struct MemoryExplainResult {
215    pub retrieval_path: RetrievalPath,
216    pub fallback_triggered: bool,
217    pub fallback_reason: Option<String>,
218    pub stages: Vec<MemoryExplainStage>,
219    pub scoring: MemoryScoring,
220}
221
222#[derive(Debug, Clone, Default)]
223pub struct MemorySchemaResult {
224    pub schema_version: String,
225    pub sort_fields: Vec<String>,
226    pub filter_fields: Vec<String>,
227    pub group_by_fields: Vec<String>,
228    pub fallback_policies: Vec<String>,
229    pub strictness_modes: Vec<String>,
230    pub transform_operations: Vec<String>,
231}
232
233#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
234#[serde(rename_all = "snake_case")]
235pub enum MemoryGroupBy {
236    SessionId,
237    Tier,
238    EmbeddingModel,
239    DateDay,
240}
241
242impl Default for MemoryGroupBy {
243    fn default() -> Self {
244        Self::SessionId
245    }
246}
247
248#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
249#[serde(rename_all = "camelCase")]
250pub struct NumericStats {
251    pub min: f32,
252    pub max: f32,
253    pub average: f32,
254}
255
256#[derive(Debug, Clone, Default, Serialize, Deserialize)]
257#[serde(rename_all = "camelCase")]
258pub struct MemoryAggregateRequest {
259    pub scope: MemoryScope,
260    pub filter: MemoryFilter,
261    pub group_by: MemoryGroupBy,
262    pub max_groups: usize,
263    pub max_nodes: usize,
264}
265
266#[derive(Debug, Clone)]
267pub struct MemoryAggregateGroup {
268    pub key: String,
269    pub node_count: usize,
270    pub embedding_coverage: f32,
271    pub avg_user_avec: AvecState,
272    pub avg_model_avec: AvecState,
273    pub avg_compression_avec: Option<AvecState>,
274    pub psi_stats: NumericStats,
275    pub rho_stats: NumericStats,
276    pub kappa_stats: NumericStats,
277}
278
279#[derive(Debug, Clone, Default)]
280pub struct MemoryAggregateResult {
281    pub groups: Vec<MemoryAggregateGroup>,
282    pub total_groups: usize,
283    pub scanned_nodes: usize,
284}
285
286#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
287#[serde(rename_all = "snake_case")]
288pub enum MemoryTransformOperation {
289    EmbedBackfill,
290    ReindexEmbeddings,
291}
292
293impl Default for MemoryTransformOperation {
294    fn default() -> Self {
295        Self::EmbedBackfill
296    }
297}
298
299#[derive(Debug, Clone, Default, Serialize, Deserialize)]
300#[serde(rename_all = "camelCase")]
301pub struct MemoryTransformRequest {
302    pub scope: MemoryScope,
303    pub filter: MemoryFilter,
304    pub operation: MemoryTransformOperation,
305    pub dry_run: bool,
306    pub batch_size: usize,
307    pub max_nodes: usize,
308    pub provider_id: Option<String>,
309    pub model: Option<String>,
310}
311
312#[derive(Debug, Clone, Default)]
313pub struct MemoryTransformResult {
314    pub scanned: usize,
315    pub selected: usize,
316    pub updated: usize,
317    pub skipped: usize,
318    pub failed: usize,
319    pub duplicate: usize,
320    pub started_at: DateTime<Utc>,
321    pub completed_at: DateTime<Utc>,
322    pub failures: Vec<String>,
323}
324
325pub fn clamp_limit(limit: usize) -> usize {
326    limit.clamp(1, 200)
327}
328
329pub fn clamp_groups(limit: usize) -> usize {
330    limit.clamp(1, 5000)
331}
332
333pub fn clamp_nodes(limit: usize) -> usize {
334    limit.clamp(1, 50000)
335}
336
337pub fn clamp_batch_size(limit: usize) -> usize {
338    limit.clamp(1, 500)
339}