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§
Sourcefn 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 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().
Sourcefn 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,
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§
Sourcefn 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 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.
Sourcefn 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 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.
Sourcefn 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 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.
Sourcefn 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 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.
Sourcefn 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 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.
Sourcefn 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,
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).