Skip to main content

MemoryStore

Struct MemoryStore 

Source
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

Source

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 string
  • force - If true, bypass conflict detection and add regardless
§Returns
  • Ok(AddResult::Added { id }) if no conflicts or force=true
  • Ok(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
Source

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 string
  • policy - Conflict handling policy (ConflictAware or Force)
§Returns
  • Ok(AddResult::Added { id }) if memory was stored successfully
  • Ok(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!(),
};
Source

pub fn get(&self, id: &str) -> Result<Option<Memory>, Error>

Get a specific memory by ID.

Returns None if the memory doesn’t exist.

Source

pub fn list(&self, project_id: &str, limit: usize) -> Result<Vec<Memory>, Error>

List all memories for a project.

Returns memories ordered by creation time (newest first).

§Arguments
  • project_id - Project identifier
  • limit - Maximum number of results to return
§Errors

Returns error if:

  • Limit is 0
  • Limit exceeds MAX_SEARCH_LIMIT
Source

pub fn update(&mut self, id: &str, content: &str) -> Result<(), Error>

Update a memory’s content.

Generates a new embedding for the updated content and persists it. The memory ID, project ID, and creation timestamp remain unchanged.

§Arguments
  • id - Memory ID to update
  • content - New content for the memory
§Errors

Returns error if the memory doesn’t exist.

Source

pub fn delete(&self, id: &str) -> Result<bool, Error>

Delete a memory.

Returns:

  • Ok(true) if memory was deleted
  • Ok(false) if memory didn’t exist
Source

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 identifier
  • since_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)?;
Source

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 ID
Source

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 ingest
  • policy - 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 to items[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

Source

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 within
  • query - Search query text (1 to 100,000 characters)
  • limit - Maximum number of results to return
  • recency_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
Source

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 within
  • query - Search query text (1 to 100,000 characters)
  • limit - Maximum number of results to return
  • recency_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

Source

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§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more