pub struct MemoryStore { /* private fields */ }Expand description
Core memory store combining embedding generation and persistence.
Wraps a SQLite database and ONNX embedding engine to provide semantic search capabilities for stored text memories.
§Mutability Requirements
Methods that generate embeddings (add, search, update) require
&mut self because EmbeddingEngine::embed internally mutates state
for ONNX tensor allocations.
Implementations§
Source§impl MemoryStore
impl MemoryStore
Sourcepub fn add_with_conflict(
&mut self,
project_id: &str,
content: &str,
metadata: Option<&str>,
force: bool,
) -> Result<AddResult, Error>
pub fn add_with_conflict( &mut self, project_id: &str, content: &str, metadata: Option<&str>, force: bool, ) -> Result<AddResult, Error>
Add a memory with conflict detection.
Checks for similar existing memories before adding. If conflicts are found (similarity >= threshold), returns conflicts details without storing.
§Arguments
project_id- Project identifier (e.g., git repo URL or user-defined)content- Text content to store (1 to 100,000 characters)metadata- Optional JSON metadata stringforce- If true, bypass conflict detection and add regardless
§Returns
Ok(AddResult::Added { id })if no conflicts or force=trueOk(AddResult::Conflicts { proposed, conflicts })if conflicts found
§Errors
Returns error if:
- Input is empty
- Input exceeds 100,000 characters
- Embedding generation fails
- Database operations fail
Sourcepub fn ingest(
&mut self,
project_id: &str,
content: &str,
metadata: Option<&str>,
policy: IngestPolicy,
) -> Result<AddResult, Error>
pub fn ingest( &mut self, project_id: &str, content: &str, metadata: Option<&str>, policy: IngestPolicy, ) -> Result<AddResult, Error>
Ingest a memory with explicit policy.
Ergonomic single-method API for adding memories with configurable conflict handling behavior.
§Arguments
project_id- Project identifier (e.g., git repo URL or user-defined)content- Text content to store (1 to 100,000 characters)metadata- Optional JSON metadata stringpolicy- Conflict handling policy (ConflictAware or Force)
§Returns
Ok(AddResult::Added { id })if memory was stored successfullyOk(AddResult::Conflicts { proposed, conflicts })if Conflicts policy and similar memories exist
§Errors
Returns error if:
- Input is empty
- Input exceeds 100,000 characters
- Embedding generation fails
- Database operations fail
§Examples
// Add with conflict detection (reject if similar exists)
match store.ingest("my-project", "Alice works at Microsoft", None, IngestPolicy::ConflictAware)? {
AddResult::Added { id } => println!("Added: {}", id),
AddResult::Conflicts { conflicts, .. } => println!("Found {} conflicts", conflicts.len()),
}
// Force add regardless of conflicts
let id = match store.ingest("my-project", "Duplicate content", None, IngestPolicy::Force)? {
AddResult::Added { id } => id,
AddResult::Conflicts { .. } => unreachable!(),
};Sourcepub fn get(&self, id: &str) -> Result<Option<Memory>, Error>
pub fn get(&self, id: &str) -> Result<Option<Memory>, Error>
Get a specific memory by ID.
Returns None if the memory doesn’t exist.
Sourcepub fn delete(&self, id: &str) -> Result<bool, Error>
pub fn delete(&self, id: &str) -> Result<bool, Error>
Delete a memory.
Returns:
Ok(true)if memory was deletedOk(false)if memory didn’t exist
Sourcepub fn list_since(
&self,
project_id: &str,
since_timestamp: &str,
limit: usize,
) -> Result<Vec<Memory>, Error>
pub fn list_since( &self, project_id: &str, since_timestamp: &str, limit: usize, ) -> Result<Vec<Memory>, Error>
List memories for a project created since a given timestamp.
Returns memories with created_at > since_timestamp, ordered by creation time (newest first).
The timestamp comparison is exclusive (does not include memories created exactly at the timestamp).
§Arguments
project_id- Project identifiersince_timestamp- RFC3339-formatted timestamp (exclusive lower bound)limit- Maximum number of results to return
§Errors
Returns error if:
- The timestamp is not valid RFC3339
- Limit is 0 or exceeds MAX_SEARCH_LIMIT
- Database query fails
§Examples
use chrono::Utc;
let one_hour_ago = (Utc::now() - chrono::Duration::hours(1)).to_rfc3339();
let recent = store.list_since("project", &one_hour_ago, 10)?;Sourcepub fn get_many(&self, ids: &[&str]) -> Result<Vec<Option<Memory>>, Error>
pub fn get_many(&self, ids: &[&str]) -> Result<Vec<Option<Memory>>, Error>
Get multiple memories by their IDs.
Returns results in the same order as the input IDs. Missing IDs are represented as None.
§Arguments
ids- Slice of memory IDs to retrieve
§Returns
Vector of Option<Memory> with the same length as ids. Each position corresponds
to the ID at the same index in the input. Some(memory) if found, None if not found.
§Examples
let results = store.get_many(&["id1", "id2", "missing-id"])?;
assert_eq!(results.len(), 3);
assert!(results[0].is_some()); // Found id1
assert!(results[1].is_some()); // Found id2
assert!(results[2].is_none()); // Missing IDSourcepub fn batch_ingest(
&mut self,
project_id: &str,
items: Vec<(&str, Option<&str>)>,
policy: IngestPolicy,
) -> Result<BatchIngestResult, Error>
pub fn batch_ingest( &mut self, project_id: &str, items: Vec<(&str, Option<&str>)>, policy: IngestPolicy, ) -> Result<BatchIngestResult, Error>
Batch ingest multiple memories with conflict-aware per-item outcomes.
This method is part of the public library API for external consumers, even though the CLI binary doesn’t use it directly.
Processes each item independently according to the specified policy.
Returns a BatchIngestResult with deterministic mapping from input indices
to per-item results (Added, Conflicts, or Error).
§Arguments
project_id- Project identifier (e.g., git repo URL or user-defined)items- Vector of (content, optional_metadata) tuples to ingestpolicy- Conflict handling policy (ConflictAware or Force)
§Returns
Ok(BatchIngestResult { results })where results[i] corresponds to items[i]
§Partial-Failure Semantics
- Added: Item succeeded (Force policy always succeeds unless validation fails)
- Conflicts: Similar memories found (only with ConflictAware policy)
- Error: Item failed validation (empty, too long, embedding error, database error)
All items are processed. No single item failure stops the batch. Result order matches input order for deterministic index-based mapping.
§Consistency Guarantees
- Independent Processing: Each item is processed independently
- No Early Termination: Failures in earlier items do NOT prevent processing of later items
- Deterministic Index Mapping:
results[i]ALWAYS corresponds toitems[i] - Partial Success Possible: No atomic or transactional semantics; some items may succeed while others fail
- Single-Threaded Safe: vipune is fully synchronous with no concurrent access patterns
§Examples
let items = vec![
("First memory", None),
("Second memory", Some(r#"{"tag": "important"}"#)),
];
let result = store.batch_ingest("my-project", items, IngestPolicy::ConflictAware)?;
for (idx, item_result) in result.results.iter().enumerate() {
match item_result {
BatchIngestItemResult::Added { id } => println!("Item {}: Added {}", idx, id),
BatchIngestItemResult::Conflicts { .. } => println!("Item {}: Conflict", idx),
BatchIngestItemResult::Error { message } => println!("Item {}: Error {}", idx, message),
}
}Source§impl MemoryStore
impl MemoryStore
Sourcepub fn search(
&mut self,
project_id: &str,
query: &str,
limit: usize,
recency_weight: f64,
) -> Result<Vec<Memory>, Error>
pub fn search( &mut self, project_id: &str, query: &str, limit: usize, recency_weight: f64, ) -> Result<Vec<Memory>, Error>
Search memories by semantic similarity.
Generates an embedding for the query and finds memories with highest cosine similarity scores. Optionally applies recency weighting to boost recent memories.
§Arguments
project_id- Project identifier to search withinquery- Search query text (1 to 100,000 characters)limit- Maximum number of results to returnrecency_weight- Weight for temporal decay (0.0 = pure semantic, 1.0 = max recency)
§Returns
Vector of memories sorted by similarity or recency-adjusted score (highest first).
Each memory includes a similarity score field (recency-adjusted if weight > 0).
§Errors
Returns error if:
- Query is empty
- Query exceeds 100,000 characters
- Recency weight is invalid
- Embedding generation fails
- Database operations fail
Sourcepub fn search_hybrid(
&mut self,
project_id: &str,
query: &str,
limit: usize,
recency_weight: f64,
) -> Result<Vec<Memory>, Error>
pub fn search_hybrid( &mut self, project_id: &str, query: &str, limit: usize, recency_weight: f64, ) -> Result<Vec<Memory>, Error>
Search memories using hybrid search (semantic + BM25 fused with RRF).
Combines semantic embedding search and BM25 full-text search using Reciprocal Rank Fusion (RRF), then optionally applies recency weighting.
§Arguments
project_id- Project identifier to search withinquery- Search query text (1 to 100,000 characters)limit- Maximum number of results to returnrecency_weight- Weight for temporal decay (0.0 = pure score, 1.0 = max recency)
§Returns
Vector of memories sorted by fused or recency-adjusted score (highest first).
The similarity field contains the final RRF score (or recency-adjusted if weight > 0).
§Errors
Returns error if:
- Query is empty
- Query exceeds 100,000 characters
- Recency weight is invalid
- Embedding generation fails
- Database operations fail
Source§impl MemoryStore
impl MemoryStore
Sourcepub fn new(
db_path: &Path,
model_id: &str,
config: Config,
) -> Result<Self, Error>
pub fn new( db_path: &Path, model_id: &str, config: Config, ) -> Result<Self, Error>
Initialize a new memory store with database path, model ID, and config.
§Arguments
db_path- Path to the SQLite database file (created if it doesn’t exist)model_id- HuggingFace model ID (e.g., “BAAI/bge-small-en-v1.5”)config- Configuration including similarity threshold for conflict detection
§Errors
Returns error if:
- Database path contains path traversal sequences (e.g., “../”)
- Parent directory cannot be canonicalized
- Database cannot be opened
Auto Trait Implementations§
impl !Freeze for MemoryStore
impl !RefUnwindSafe for MemoryStore
impl Send for MemoryStore
impl !Sync for MemoryStore
impl Unpin for MemoryStore
impl UnsafeUnpin for MemoryStore
impl !UnwindSafe for MemoryStore
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> 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