1pub mod branch;
2pub mod causality;
3pub mod checkpoint;
4pub mod conflict;
5pub mod consolidate;
6pub mod current_fact_resolver;
7pub mod event_builder;
8pub mod evidence;
9pub mod experience;
10pub mod forget;
11pub mod lifecycle;
12pub mod maturity;
13pub mod merge;
14pub mod orientation_cache;
15pub mod poisoning;
16pub mod recall;
17pub mod reflection;
18pub mod remember;
19pub mod replay;
20pub mod retained;
21pub mod retrieval;
22pub mod share;
23pub mod write_provenance;
24
25use std::sync::Arc;
26
27use crate::cache::MemoryCache;
28use crate::embedding::EmbeddingProvider;
29use crate::encryption::ContentEncryption;
30use crate::error::{Error, Result};
31use crate::index::VectorIndex;
32use crate::search::FullTextIndex;
33use crate::storage::StorageBackend;
34use crate::storage::cold::ColdStorage;
35
36const MAX_AGENT_ID_LEN: usize = 256;
37
38pub const MAX_BATCH_QUERY_LIMIT: usize = 10_000;
41
42pub fn validate_agent_id(agent_id: &str) -> Result<()> {
44 if agent_id.is_empty() {
45 return Err(Error::Validation("agent_id cannot be empty".into()));
46 }
47 if agent_id.len() > MAX_AGENT_ID_LEN {
48 return Err(Error::Validation(format!(
49 "agent_id exceeds max length of {MAX_AGENT_ID_LEN}"
50 )));
51 }
52 if !agent_id
53 .chars()
54 .all(|c| c.is_alphanumeric() || c == '-' || c == '_' || c == '.')
55 {
56 return Err(Error::Validation(
57 "agent_id must contain only alphanumeric characters, hyphens, underscores, or dots"
58 .into(),
59 ));
60 }
61 Ok(())
62}
63
64pub struct MnemoEngine {
65 pub storage: Arc<dyn StorageBackend>,
66 pub index: Arc<dyn VectorIndex>,
67 pub embedding: Arc<dyn EmbeddingProvider>,
68 pub full_text: Option<Arc<dyn FullTextIndex>>,
69 pub default_agent_id: String,
70 pub default_org_id: Option<String>,
71 pub encryption: Option<Arc<ContentEncryption>>,
72 pub cold_storage: Option<Arc<dyn ColdStorage>>,
73 pub cache: Option<Arc<MemoryCache>>,
74 pub embed_events: bool,
75 pub ttl_working_seconds: u64,
78 pub procedural_importance_floor: f32,
81 pub poisoning_policy: poisoning::PoisoningPolicy,
85 pub provenance_signer: Option<Arc<crate::provenance::ProvenanceSigner>>,
91 pub orientation_cache_store: Option<Arc<orientation_cache::OrientationCacheStore>>,
99 pub consolidation_policy: maturity::ConsolidationPolicy,
109 pub evidence_scorer: Option<Arc<dyn evidence::EvidenceScorer>>,
118 pub experience_memory_enabled: bool,
126 pub capability_issuer: Option<Arc<crate::model::capability::CapabilityIssuer>>,
133}
134
135pub const DEFAULT_TTL_WORKING_SECONDS: u64 = 3600;
137
138pub const DEFAULT_PROCEDURAL_IMPORTANCE_FLOOR: f32 = 0.8;
140
141impl MnemoEngine {
142 pub fn new(
143 storage: Arc<dyn StorageBackend>,
144 index: Arc<dyn VectorIndex>,
145 embedding: Arc<dyn EmbeddingProvider>,
146 default_agent_id: String,
147 default_org_id: Option<String>,
148 ) -> Self {
149 Self {
150 storage,
151 index,
152 embedding,
153 full_text: None,
154 default_agent_id,
155 default_org_id,
156 encryption: None,
157 cold_storage: None,
158 cache: None,
159 embed_events: false,
160 ttl_working_seconds: DEFAULT_TTL_WORKING_SECONDS,
161 procedural_importance_floor: DEFAULT_PROCEDURAL_IMPORTANCE_FLOOR,
162 poisoning_policy: poisoning::PoisoningPolicy::default(),
163 provenance_signer: None,
164 orientation_cache_store: None,
165 consolidation_policy: maturity::ConsolidationPolicy::default(),
166 evidence_scorer: None,
167 experience_memory_enabled: false,
168 capability_issuer: None,
169 }
170 }
171
172 pub fn with_capability_issuer(
176 mut self,
177 issuer: Arc<crate::model::capability::CapabilityIssuer>,
178 ) -> Self {
179 self.capability_issuer = Some(issuer);
180 self
181 }
182
183 pub fn with_provenance_signer(
187 mut self,
188 signer: Arc<crate::provenance::ProvenanceSigner>,
189 ) -> Self {
190 self.provenance_signer = Some(signer);
191 self
192 }
193
194 pub fn with_poisoning_policy(mut self, policy: poisoning::PoisoningPolicy) -> Self {
198 self.poisoning_policy = policy;
199 self
200 }
201
202 pub fn with_ttl_working_seconds(mut self, seconds: u64) -> Self {
205 self.ttl_working_seconds = seconds;
206 self
207 }
208
209 pub fn with_procedural_importance_floor(mut self, floor: f32) -> Self {
212 self.procedural_importance_floor = floor.clamp(0.0, 1.0);
213 self
214 }
215
216 pub fn with_full_text(mut self, ft: Arc<dyn FullTextIndex>) -> Self {
217 self.full_text = Some(ft);
218 self
219 }
220
221 pub fn with_encryption(mut self, enc: Arc<ContentEncryption>) -> Self {
222 self.encryption = Some(enc);
223 self
224 }
225
226 pub fn with_cold_storage(mut self, cs: Arc<dyn ColdStorage>) -> Self {
227 self.cold_storage = Some(cs);
228 self
229 }
230
231 pub fn with_cache(mut self, c: Arc<MemoryCache>) -> Self {
232 self.cache = Some(c);
233 self
234 }
235
236 pub fn with_event_embeddings(mut self) -> Self {
237 self.embed_events = true;
238 self
239 }
240
241 pub fn with_orientation_cache_store(
249 mut self,
250 store: Arc<orientation_cache::OrientationCacheStore>,
251 ) -> Self {
252 self.orientation_cache_store = Some(store);
253 self
254 }
255
256 pub fn with_consolidation_policy(mut self, policy: maturity::ConsolidationPolicy) -> Self {
262 self.consolidation_policy = policy;
263 self
264 }
265
266 pub fn with_evidence_scorer(mut self, scorer: Arc<dyn evidence::EvidenceScorer>) -> Self {
273 self.evidence_scorer = Some(scorer);
274 self
275 }
276
277 pub fn with_experience_memory(mut self) -> Self {
284 self.experience_memory_enabled = true;
285 self
286 }
287
288 pub async fn remember(
289 &self,
290 request: remember::RememberRequest,
291 ) -> Result<remember::RememberResponse> {
292 remember::execute(self, request).await
293 }
294
295 pub async fn remember_with_capability(
299 &self,
300 request: remember::RememberRequest,
301 capability: &crate::model::capability::Capability,
302 ) -> Result<remember::RememberResponse> {
303 remember::execute_with_capability(self, request, capability).await
304 }
305
306 pub async fn recall(&self, request: recall::RecallRequest) -> Result<recall::RecallResponse> {
307 recall::execute(self, request).await
308 }
309
310 pub async fn forget(&self, request: forget::ForgetRequest) -> Result<forget::ForgetResponse> {
311 forget::execute(self, request).await
312 }
313
314 pub async fn forget_subject(
317 &self,
318 request: forget::ForgetSubjectRequest,
319 ) -> Result<forget::ForgetSubjectResponse> {
320 forget::forget_subject(self, request).await
321 }
322
323 pub async fn run_ttl_sweep(&self) -> Result<lifecycle::TtlReport> {
326 lifecycle::run_ttl_sweep(self).await
327 }
328
329 pub async fn run_reflection_pass(
333 &self,
334 agent_id: Option<String>,
335 ) -> Result<reflection::ReflectionReport> {
336 let agent_id = agent_id.unwrap_or_else(|| self.default_agent_id.clone());
337 reflection::run_reflection_pass(self, &agent_id).await
338 }
339
340 pub async fn run_reflection_pass_with_mode(
343 &self,
344 agent_id: Option<String>,
345 mode: reflection::ReflectionMode,
346 force: bool,
347 ) -> Result<reflection::ReflectionReport> {
348 let agent_id = agent_id.unwrap_or_else(|| self.default_agent_id.clone());
349 reflection::run_reflection_pass_with_mode(self, &agent_id, mode, force).await
350 }
351
352 pub async fn replay_quarantine(
355 &self,
356 agent_id: Option<String>,
357 since: Option<&str>,
358 ) -> Result<Vec<poisoning::QuarantineReplayEntry>> {
359 let agent_id = agent_id.unwrap_or_else(|| self.default_agent_id.clone());
360 poisoning::replay_quarantine(self, &agent_id, since).await
361 }
362
363 pub async fn share(&self, request: share::ShareRequest) -> Result<share::ShareResponse> {
364 share::execute(self, request).await
365 }
366
367 pub async fn checkpoint(
368 &self,
369 request: checkpoint::CheckpointRequest,
370 ) -> Result<checkpoint::CheckpointResponse> {
371 checkpoint::execute(self, request).await
372 }
373
374 pub async fn branch(&self, request: branch::BranchRequest) -> Result<branch::BranchResponse> {
375 branch::execute(self, request).await
376 }
377
378 pub async fn merge(&self, request: merge::MergeRequest) -> Result<merge::MergeResponse> {
379 merge::execute(self, request).await
380 }
381
382 pub async fn replay(&self, request: replay::ReplayRequest) -> Result<replay::ReplayResponse> {
383 replay::execute(self, request).await
384 }
385
386 pub async fn consolidate(
390 &self,
391 request: consolidate::ConsolidateRequest,
392 ) -> Result<consolidate::ConsolidateResponse> {
393 consolidate::execute(self, request).await
394 }
395
396 pub async fn remember_plan(
402 &self,
403 request: experience::RememberPlanRequest,
404 ) -> Result<experience::RememberPlanResponse> {
405 experience::execute_remember_plan(self, request).await
406 }
407
408 pub async fn recall_plan(
413 &self,
414 request: experience::RecallPlanRequest,
415 ) -> Result<experience::RecallPlanResponse> {
416 experience::execute_recall_plan(self, request).await
417 }
418
419 pub async fn run_decay_pass(
420 &self,
421 agent_id: Option<String>,
422 archive_threshold: f32,
423 forget_threshold: f32,
424 ) -> Result<lifecycle::DecayPassResult> {
425 let agent_id = agent_id.unwrap_or_else(|| self.default_agent_id.clone());
426 lifecycle::run_decay_pass(self, &agent_id, archive_threshold, forget_threshold).await
427 }
428
429 pub async fn run_consolidation(
430 &self,
431 agent_id: Option<String>,
432 min_cluster_size: usize,
433 ) -> Result<lifecycle::ConsolidationResult> {
434 let agent_id = agent_id.unwrap_or_else(|| self.default_agent_id.clone());
435 lifecycle::run_consolidation(self, &agent_id, min_cluster_size).await
436 }
437
438 pub async fn verify_integrity(
439 &self,
440 agent_id: Option<String>,
441 thread_id: Option<&str>,
442 ) -> Result<crate::hash::ChainVerificationResult> {
443 let agent_id = agent_id.unwrap_or_else(|| self.default_agent_id.clone());
444 let records = self
445 .storage
446 .list_memories_by_agent_ordered(&agent_id, thread_id, 10000)
447 .await?;
448 Ok(crate::hash::verify_chain(&records))
449 }
450
451 pub async fn trace_causality(
452 &self,
453 event_id: uuid::Uuid,
454 max_depth: usize,
455 ) -> Result<causality::CausalChain> {
456 causality::trace_causality(
457 self,
458 event_id,
459 max_depth,
460 causality::TraceDirection::Down,
461 None,
462 )
463 .await
464 }
465
466 pub async fn trace_causality_with_options(
467 &self,
468 event_id: uuid::Uuid,
469 max_depth: usize,
470 direction: causality::TraceDirection,
471 event_type_filter: Option<crate::model::event::EventType>,
472 ) -> Result<causality::CausalChain> {
473 causality::trace_causality(self, event_id, max_depth, direction, event_type_filter).await
474 }
475
476 pub async fn verify_event_integrity(
477 &self,
478 agent_id: Option<String>,
479 thread_id: Option<&str>,
480 ) -> Result<crate::hash::ChainVerificationResult> {
481 let agent_id = agent_id.unwrap_or_else(|| self.default_agent_id.clone());
482 let events = if let Some(tid) = thread_id {
483 self.storage.get_events_by_thread(tid, 10000).await?
484 } else {
485 let mut evts = self.storage.list_events(&agent_id, 10000, 0).await?;
487 evts.reverse();
488 evts
489 };
490 Ok(crate::hash::verify_event_chain(&events))
491 }
492
493 pub async fn detect_conflicts(
494 &self,
495 agent_id: Option<String>,
496 threshold: f32,
497 ) -> Result<conflict::ConflictDetectionResult> {
498 let agent_id = agent_id.unwrap_or_else(|| self.default_agent_id.clone());
499 conflict::detect_conflicts(self, &agent_id, threshold).await
500 }
501
502 pub async fn resolve_conflict(
503 &self,
504 conflict_pair: &conflict::ConflictPair,
505 strategy: conflict::ResolutionStrategy,
506 ) -> Result<()> {
507 conflict::resolve_conflict(self, conflict_pair, strategy).await
508 }
509}