Skip to main content

VectorStore

Trait VectorStore 

Source
pub trait VectorStore:
    Send
    + Sync
    + 'static {
    // Required methods
    fn dimension(&self) -> usize;
    fn add<'life0, 'life1, 'life2, 'async_trait>(
        &'life0 self,
        ctx: &'life1 ExecutionContext,
        ns: &'life2 Namespace,
        document: Document,
        vector: Vec<f32>,
    ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait,
             'life1: 'async_trait,
             'life2: 'async_trait;
    fn search<'life0, 'life1, 'life2, 'life3, 'async_trait>(
        &'life0 self,
        ctx: &'life1 ExecutionContext,
        ns: &'life2 Namespace,
        query_vector: &'life3 [f32],
        top_k: usize,
    ) -> Pin<Box<dyn Future<Output = Result<Vec<Document>>> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait,
             'life1: 'async_trait,
             'life2: 'async_trait,
             'life3: 'async_trait;

    // Provided methods
    fn delete<'life0, 'life1, 'life2, 'life3, 'async_trait>(
        &'life0 self,
        _ctx: &'life1 ExecutionContext,
        _ns: &'life2 Namespace,
        _doc_id: &'life3 str,
    ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait,
             'life1: 'async_trait,
             'life2: 'async_trait,
             'life3: 'async_trait { ... }
    fn update<'life0, 'life1, 'life2, 'life3, 'async_trait>(
        &'life0 self,
        ctx: &'life1 ExecutionContext,
        ns: &'life2 Namespace,
        doc_id: &'life3 str,
        document: Document,
        vector: Vec<f32>,
    ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait,
             'life1: 'async_trait,
             'life2: 'async_trait,
             'life3: 'async_trait { ... }
    fn add_batch<'life0, 'life1, 'life2, 'async_trait>(
        &'life0 self,
        ctx: &'life1 ExecutionContext,
        ns: &'life2 Namespace,
        items: Vec<(Document, Vec<f32>)>,
    ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait,
             'life1: 'async_trait,
             'life2: 'async_trait { ... }
    fn search_filtered<'life0, 'life1, 'life2, 'life3, 'life4, 'async_trait>(
        &'life0 self,
        _ctx: &'life1 ExecutionContext,
        _ns: &'life2 Namespace,
        _query_vector: &'life3 [f32],
        _top_k: usize,
        _filter: &'life4 VectorFilter,
    ) -> Pin<Box<dyn Future<Output = Result<Vec<Document>>> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait,
             'life1: 'async_trait,
             'life2: 'async_trait,
             'life3: 'async_trait,
             'life4: 'async_trait { ... }
    fn count<'life0, 'life1, 'life2, 'life3, 'async_trait>(
        &'life0 self,
        _ctx: &'life1 ExecutionContext,
        _ns: &'life2 Namespace,
        _filter: Option<&'life3 VectorFilter>,
    ) -> Pin<Box<dyn Future<Output = Result<usize>> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait,
             'life1: 'async_trait,
             'life2: 'async_trait,
             'life3: 'async_trait { ... }
    fn list<'life0, 'life1, 'life2, 'life3, 'async_trait>(
        &'life0 self,
        _ctx: &'life1 ExecutionContext,
        _ns: &'life2 Namespace,
        _filter: Option<&'life3 VectorFilter>,
        _limit: usize,
        _offset: usize,
    ) -> Pin<Box<dyn Future<Output = Result<Vec<Document>>> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait,
             'life1: 'async_trait,
             'life2: 'async_trait,
             'life3: 'async_trait { ... }
}
Expand description

Vector index keyed by Namespace. Backed by qdrant, lancedb, pgvector, etc. in companion crates.

Layering — this is tier 1 (primitive) of the semantic-memory three-tier architecture. Operators implement VectorStore once per backend; the bundle crate::SemanticMemory<E, V> (tier 2) and the consumer trait crate::SemanticMemoryBackend (tier 3) compose it into the agent-facing surface automatically. Take Namespace as a per-call parameter so a single store instance serves many tenants.

Every async method accepts an ExecutionContext so backends can honour caller-side cancellation and deadlines (CLAUDE.md §“Cancellation”). The delete / update / add_batch / search_filtered methods have default impls so simple backends only need add and search — production backends override every method for efficiency and correctness.

Atomicity: the default update impl is non-atomic (delete-then-add): concurrent search calls observe a momentary gap. Backends that support transactional updates must override.

Required Methods§

Source

fn dimension(&self) -> usize

Vector dimension this index expects.

Source

fn add<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, ctx: &'life1 ExecutionContext, ns: &'life2 Namespace, document: Document, vector: Vec<f32>, ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait,

Add a document with its pre-computed vector to the index. Implementations validate vector.len() == self.dimension().

Source

fn search<'life0, 'life1, 'life2, 'life3, 'async_trait>( &'life0 self, ctx: &'life1 ExecutionContext, ns: &'life2 Namespace, query_vector: &'life3 [f32], top_k: usize, ) -> Pin<Box<dyn Future<Output = Result<Vec<Document>>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait,

Search for the top top_k nearest documents to query_vector.

Provided Methods§

Source

fn delete<'life0, 'life1, 'life2, 'life3, 'async_trait>( &'life0 self, _ctx: &'life1 ExecutionContext, _ns: &'life2 Namespace, _doc_id: &'life3 str, ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait,

Delete a document by its backend-assigned id. Default impl returns Error::Config — backends without a stable id space must override or document the lifecycle.

Source

fn update<'life0, 'life1, 'life2, 'life3, 'async_trait>( &'life0 self, ctx: &'life1 ExecutionContext, ns: &'life2 Namespace, doc_id: &'life3 str, document: Document, vector: Vec<f32>, ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait,

Replace an existing document’s vector and metadata. Default impl chains delete + add (non-atomic — concurrent searches observe a gap); backends with atomic-update support must override.

Source

fn add_batch<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, ctx: &'life1 ExecutionContext, ns: &'life2 Namespace, items: Vec<(Document, Vec<f32>)>, ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait,

Insert many documents at once. Default impl loops over add, polling ExecutionContext::is_cancelled between iterations so a cancelled caller releases the index lock within one add round-trip instead of completing the full batch. Backends that support a native batch endpoint should override — sequential calls amplify network latency by N.

Source

fn search_filtered<'life0, 'life1, 'life2, 'life3, 'life4, 'async_trait>( &'life0 self, _ctx: &'life1 ExecutionContext, _ns: &'life2 Namespace, _query_vector: &'life3 [f32], _top_k: usize, _filter: &'life4 VectorFilter, ) -> Pin<Box<dyn Future<Output = Result<Vec<Document>>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait, 'life4: 'async_trait,

Top-K nearest matches with a metadata filter pushed down to the index. Default impl returns entelix_core::Error::Config — silently dropping the filter would return wrong results, so the trait makes the backend’s lack of filter support explicit. Backends with filter support must override.

Source

fn count<'life0, 'life1, 'life2, 'life3, 'async_trait>( &'life0 self, _ctx: &'life1 ExecutionContext, _ns: &'life2 Namespace, _filter: Option<&'life3 VectorFilter>, ) -> Pin<Box<dyn Future<Output = Result<usize>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait,

Count documents in the namespace, optionally narrowed by a filter. Used by dashboards reporting per-tenant index sizes and by memory-budget enforcement (skip indexing when the namespace is at its cap).

Default impl returns entelix_core::Error::Config — counting requires either a backend-native COUNT or a full scan, both of which are operator-visible cost decisions that should not be silently approximated.

Source

fn list<'life0, 'life1, 'life2, 'life3, 'async_trait>( &'life0 self, _ctx: &'life1 ExecutionContext, _ns: &'life2 Namespace, _filter: Option<&'life3 VectorFilter>, _limit: usize, _offset: usize, ) -> Pin<Box<dyn Future<Output = Result<Vec<Document>>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait,

Enumerate documents in the namespace, optionally narrowed by a filter. limit caps the page size; offset is the page start (cursor-style pagination semantics depend on the backend). Returned documents may omit their vectors — the method is for inspection / pagination, not for retrieval.

Default impl returns entelix_core::Error::Config — listing requires a stable iteration order that not every vector backend exposes (e.g. ANN indices give no useful ordering across calls).

Implementors§