pub struct MemoryStorage { /* private fields */ }Expand description
Storage engine for long-term memory persistence
Uses a single RocksDB instance with 2 column families:
- default: main memory data (also shared by LearningHistoryStore, TemporalFactStore, etc. via key prefixes)
memory_index: secondary indices for tag/type/timestamp queries
Implementations§
Source§impl MemoryStorage
impl MemoryStorage
Sourcepub fn new(path: &Path, shared_cache: Option<&Cache>) -> Result<Self>
pub fn new(path: &Path, shared_cache: Option<&Cache>) -> Result<Self>
Create a new memory storage.
If shared_cache is provided, all block-cache reads are charged against
the shared LRU cache (recommended for multi-tenant server mode). When
None, a small per-instance cache is created (standalone / test use).
Sourcepub fn store(&self, memory: &Memory) -> Result<()>
pub fn store(&self, memory: &Memory) -> Result<()>
Store a memory with configurable write durability
ROBOTICS OPTIMIZATION: Write mode is configurable via SHODH_WRITE_MODE env var.
- Async (default): <1ms per write, data survives process crashes
- Sync: 2-10ms per write, data survives power loss
For robotics/edge: Use async mode + periodic flush() calls for best latency. For compliance/critical: Set SHODH_WRITE_MODE=sync for full durability.
Sourcepub fn get_by_content_hash(&self, content: &str) -> Option<MemoryId>
pub fn get_by_content_hash(&self, content: &str) -> Option<MemoryId>
Look up an existing memory by content hash (idempotency dedup, issue #109). Returns the MemoryId if identical content was already stored.
Sourcepub fn get(&self, id: &MemoryId) -> Result<Memory>
pub fn get(&self, id: &MemoryId) -> Result<Memory>
Retrieve a memory by ID
Performs lazy migration: if memory is in legacy format, re-writes it in current format for faster future reads.
Sourcepub fn find_by_external_id(&self, external_id: &str) -> Result<Option<Memory>>
pub fn find_by_external_id(&self, external_id: &str) -> Result<Option<Memory>>
Find a memory by its external_id (e.g., “linear:SHO-39”, “github:pr-123”)
Returns the memory if found, None if no memory with this external_id exists. Used for upsert operations when syncing from external sources.
Sourcepub fn update(&self, memory: &Memory) -> Result<()>
pub fn update(&self, memory: &Memory) -> Result<()>
Update an existing memory
ALGO-004 FIX: Re-indexes memory after update to handle importance drift. When Hebbian feedback changes importance, the old bucket index becomes stale. We remove old indices before storing to ensure index consistency.
Sourcepub fn search(&self, criteria: SearchCriteria) -> Result<Vec<Memory>>
pub fn search(&self, criteria: SearchCriteria) -> Result<Vec<Memory>>
Search memories by various criteria
Sourcepub fn get_children(&self, parent_id: &MemoryId) -> Result<Vec<Memory>>
pub fn get_children(&self, parent_id: &MemoryId) -> Result<Vec<Memory>>
Get children of a memory (public API)
Sourcepub fn get_ancestors(&self, memory_id: &MemoryId) -> Result<Vec<Memory>>
pub fn get_ancestors(&self, memory_id: &MemoryId) -> Result<Vec<Memory>>
Get the parent chain (ancestors) of a memory
Sourcepub fn get_hierarchy_context(
&self,
memory_id: &MemoryId,
) -> Result<(Vec<Memory>, Memory, Vec<Memory>)>
pub fn get_hierarchy_context( &self, memory_id: &MemoryId, ) -> Result<(Vec<Memory>, Memory, Vec<Memory>)>
Get the full hierarchy context for a memory Returns (ancestors, memory, children)
Sourcepub fn get_subtree(
&self,
root_id: &MemoryId,
max_depth: usize,
) -> Result<Vec<Memory>>
pub fn get_subtree( &self, root_id: &MemoryId, max_depth: usize, ) -> Result<Vec<Memory>>
Get all memories in a subtree rooted at the given memory
Sourcepub fn get_all_ids(&self) -> Result<Vec<MemoryId>>
pub fn get_all_ids(&self) -> Result<Vec<MemoryId>>
Get all memory IDs without loading full Memory objects.
Returns only 16-byte UUID keys (lightweight). Use with get() to load
individual memories in batches to avoid OOM on large datasets.
Sourcepub fn get_all(&self) -> Result<Vec<Memory>>
pub fn get_all(&self) -> Result<Vec<Memory>>
Get all memories from long-term storage
Only returns entries with valid 16-byte UUID keys (consistent with get_stats)
pub fn get_uncompressed_older_than( &self, cutoff: DateTime<Utc>, ) -> Result<Vec<Memory>>
Sourcepub fn mark_forgotten_by_age(
&self,
cutoff: DateTime<Utc>,
) -> Result<Vec<MemoryId>>
pub fn mark_forgotten_by_age( &self, cutoff: DateTime<Utc>, ) -> Result<Vec<MemoryId>>
Mark memories as forgotten (soft delete) with atomic batch write. Returns the IDs of memories that were flagged, so callers can clean up secondary indices (vector, BM25, graph).
Sourcepub fn mark_forgotten_by_importance(
&self,
threshold: f32,
) -> Result<Vec<MemoryId>>
pub fn mark_forgotten_by_importance( &self, threshold: f32, ) -> Result<Vec<MemoryId>>
Mark memories with low importance as forgotten with atomic batch write. Returns the IDs of memories that were flagged, so callers can clean up secondary indices (vector, BM25, graph).
Sourcepub fn remove_matching(&self, regex: &Regex) -> Result<usize>
pub fn remove_matching(&self, regex: &Regex) -> Result<usize>
Remove memories matching a pattern with durable writes
Sourcepub fn update_access(&self, id: &MemoryId) -> Result<()>
pub fn update_access(&self, id: &MemoryId) -> Result<()>
Update access count for a memory
Sourcepub fn get_stats(&self) -> Result<StorageStats>
pub fn get_stats(&self) -> Result<StorageStats>
Get statistics about stored memories
Sourcepub fn get_retrieval_count(&self) -> Result<usize>
pub fn get_retrieval_count(&self) -> Result<usize>
Get the persisted retrieval counter
Sourcepub fn increment_retrieval_count(&self) -> Result<usize>
pub fn increment_retrieval_count(&self) -> Result<usize>
Increment and persist the retrieval counter, returns new value
Sourcepub fn cleanup_corrupted(&self) -> Result<usize>
pub fn cleanup_corrupted(&self) -> Result<usize>
Remove corrupted memories that fail to deserialize even with legacy fallbacks Returns the number of entries deleted
This function safely cleans up:
- Entries with keys that are not valid 16-byte UUIDs (corrupted/misplaced)
- Entries with valid UUID keys but corrupted values that fail ALL format fallbacks
It preserves:
- Valid Memory entries (any format - current or legacy)
- Stats entries (keys starting with “stats:”)
Sourcepub fn migrate_legacy(&self) -> Result<(usize, usize, usize)>
pub fn migrate_legacy(&self) -> Result<(usize, usize, usize)>
Migrate legacy memories to current format for improved performance Returns (migrated_count, already_current_count, failed_count)
This function:
- Iterates all memories in storage
- Attempts to deserialize with format fallback
- Re-saves successfully deserialized legacy memories in current format
- Reports migration statistics
Source§impl MemoryStorage
impl MemoryStorage
Sourcepub fn store_with_vectors(
&self,
memory: &Memory,
vector_ids: Vec<u32>,
) -> Result<()>
pub fn store_with_vectors( &self, memory: &Memory, vector_ids: Vec<u32>, ) -> Result<()>
Store memory and its text vector mapping atomically
Uses WriteBatch to ensure both operations succeed or both fail. This is the ONLY way orphaned memories can be prevented.
For text-only memories (current implementation). Use store_with_multimodal_vectors for memories with image/audio/video content.
Sourcepub fn store_with_multimodal_vectors(
&self,
memory: &Memory,
modality: Modality,
vector_ids: Vec<u32>,
) -> Result<()>
pub fn store_with_multimodal_vectors( &self, memory: &Memory, modality: Modality, vector_ids: Vec<u32>, ) -> Result<()>
Store memory with vectors for a specific modality
MULTIMODALITY READY: Supports text, image, audio, video modalities. Each modality is stored separately, allowing cross-modal search.
Sourcepub fn get_vector_mapping(
&self,
memory_id: &MemoryId,
) -> Result<Option<VectorMappingEntry>>
pub fn get_vector_mapping( &self, memory_id: &MemoryId, ) -> Result<Option<VectorMappingEntry>>
Get vector mapping for a memory
Sourcepub fn get_all_vector_mappings(
&self,
) -> Result<Vec<(MemoryId, VectorMappingEntry)>>
pub fn get_all_vector_mappings( &self, ) -> Result<Vec<(MemoryId, VectorMappingEntry)>>
Get all vector mappings (for rebuilding Vamana index on startup)
Returns iterator-style results to avoid loading everything into memory at once. Sorted by memory_id for deterministic Vamana rebuilding.
Sourcepub fn delete_vector_mapping(&self, memory_id: &MemoryId) -> Result<()>
pub fn delete_vector_mapping(&self, memory_id: &MemoryId) -> Result<()>
Delete vector mapping for a memory (called when deleting memory)
Sourcepub fn update_vector_mapping(
&self,
memory_id: &MemoryId,
vector_ids: Vec<u32>,
) -> Result<()>
pub fn update_vector_mapping( &self, memory_id: &MemoryId, vector_ids: Vec<u32>, ) -> Result<()>
Update text vector mapping for a memory (for reindex operations)
Convenience method for text-only reindexing.
Sourcepub fn update_modality_vectors(
&self,
memory_id: &MemoryId,
modality: Modality,
vector_ids: Vec<u32>,
) -> Result<()>
pub fn update_modality_vectors( &self, memory_id: &MemoryId, modality: Modality, vector_ids: Vec<u32>, ) -> Result<()>
Update vector mapping for a specific modality
MULTIMODALITY READY: Preserves vectors for other modalities while updating one.
Sourcepub fn delete_with_vectors(&self, id: &MemoryId) -> Result<()>
pub fn delete_with_vectors(&self, id: &MemoryId) -> Result<()>
Delete memory and its vector mapping atomically
Sourcepub fn count_vector_mappings(&self) -> usize
pub fn count_vector_mappings(&self) -> usize
Count memories with vector mappings (for health checks)
Sourcepub fn find_memories_without_mappings(&self) -> Result<Vec<MemoryId>>
pub fn find_memories_without_mappings(&self) -> Result<Vec<MemoryId>>
Check integrity: find memories without vector mappings
Returns memories that have embeddings but no corresponding vector mapping. These need to be reindexed.
Sourcepub fn get_all_text_vector_ids(&self) -> Result<Vec<u32>>
pub fn get_all_text_vector_ids(&self) -> Result<Vec<u32>>
Get all text vector IDs from mappings (for Vamana statistics)
Sourcepub fn get_modality_stats(&self) -> Result<HashMap<Modality, usize>>
pub fn get_modality_stats(&self) -> Result<HashMap<Modality, usize>>
Get vector count per modality (for health monitoring)
Sourcepub fn save_interference_records(
&self,
memory_id: &str,
records: &[InterferenceRecord],
) -> Result<()>
pub fn save_interference_records( &self, memory_id: &str, records: &[InterferenceRecord], ) -> Result<()>
Persist interference records for a single memory
Key format: interference:{memory_id} → JSON Vec<InterferenceRecord>
Sourcepub fn load_all_interference_records(
&self,
) -> Result<(HashMap<String, Vec<InterferenceRecord>>, usize)>
pub fn load_all_interference_records( &self, ) -> Result<(HashMap<String, Vec<InterferenceRecord>>, usize)>
Load all interference records from storage on startup
Scans all interference: prefixed keys and deserializes records.
Returns (history_map, total_event_count) for bulk-loading into InterferenceDetector.
Sourcepub fn delete_interference_records(&self, memory_id: &str) -> Result<()>
pub fn delete_interference_records(&self, memory_id: &str) -> Result<()>
Delete interference records for a single memory (called on forget/delete)
Sourcepub fn save_interference_event_count(&self, count: usize) -> Result<()>
pub fn save_interference_event_count(&self, count: usize) -> Result<()>
Persist the total interference event count
Key: interference_meta:total → 8-byte little-endian u64
Sourcepub fn get_fact_watermark(&self, user_id: &str) -> Option<i64>
pub fn get_fact_watermark(&self, user_id: &str) -> Option<i64>
Load the fact extraction watermark for a user.
Key: _watermark:fact_extraction:{user_id} → 8-byte little-endian i64 (unix millis)
Returns None if no watermark has been persisted yet.
Sourcepub fn set_fact_watermark(&self, user_id: &str, timestamp_millis: i64)
pub fn set_fact_watermark(&self, user_id: &str, timestamp_millis: i64)
Persist the fact extraction watermark for a user.
Key: _watermark:fact_extraction:{user_id} → 8-byte little-endian i64 (unix millis)
Sourcepub fn clear_all_interference_records(&self) -> Result<usize>
pub fn clear_all_interference_records(&self) -> Result<usize>
Delete ALL interference records (GDPR forget_all)
Batch-deletes all interference: and interference_meta: keys.
Auto Trait Implementations§
impl Freeze for MemoryStorage
impl RefUnwindSafe for MemoryStorage
impl Send for MemoryStorage
impl Sync for MemoryStorage
impl Unpin for MemoryStorage
impl UnsafeUnpin for MemoryStorage
impl UnwindSafe for MemoryStorage
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>, which can then be
downcast into Box<dyn ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>, which can then be further
downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.Source§impl<T> DowncastSend for T
impl<T> DowncastSend for T
Source§impl<T> DowncastSync for T
impl<T> DowncastSync for T
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more