khive_runtime/note_search_ann.rs
1//! Pack-owned ANN candidate source for the note-substrate search vector leg.
2//!
3//! The runtime owns this neutral seam so its note search does not depend on a
4//! higher-layer pack. The memory pack installs the provider for its backend.
5
6use std::sync::atomic::{AtomicU64, Ordering};
7
8use async_trait::async_trait;
9use khive_storage::types::VectorSearchHit;
10
11use crate::{KhiveRuntime, NamespaceToken, RuntimeResult};
12
13static ANN_ROUTE_TOTAL: AtomicU64 = AtomicU64::new(0);
14static FALLBACK_ROUTE_TOTAL: AtomicU64 = AtomicU64::new(0);
15
16/// An installed graph candidate source for one database backend.
17///
18/// `None` means this model has no graph this consumer may serve and licenses the
19/// existing exact sqlite-vec fallback. Errors propagate rather than silently
20/// changing route. Implementations merge the registered consumer's fresh tail
21/// before returning candidates.
22#[async_trait]
23pub trait NoteSearchAnnProvider: Send + Sync {
24 /// Backend identity whose note vectors the graph indexes.
25 fn backend_id(&self) -> &str;
26
27 /// Match the actual opened backend, not only its logical name. Two
28 /// independently opened databases may both have the default `main` ID.
29 fn serves_backend(&self, runtime: &KhiveRuntime) -> bool;
30
31 /// Return ANN plus fresh-tail candidates in the same score and ordering
32 /// contract as `VectorStore::search`.
33 async fn search(
34 &self,
35 token: &NamespaceToken,
36 model: &str,
37 query_embedding: &[f32],
38 top_k: u32,
39 ) -> RuntimeResult<Option<Vec<VectorSearchHit>>>;
40}
41
42fn increment(counter: &AtomicU64) {
43 let _ = counter.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |value| {
44 Some(value.saturating_add(1))
45 });
46}
47
48pub(crate) fn record_ann_route() {
49 increment(&ANN_ROUTE_TOTAL);
50}
51
52pub(crate) fn record_fallback_route() {
53 increment(&FALLBACK_ROUTE_TOTAL);
54}
55
56/// Process-lifetime note-search vector route counts. The pair does not reset
57/// when a database pool is reopened or diagnostics is collected.
58pub fn route_totals() -> (u64, u64) {
59 (
60 ANN_ROUTE_TOTAL.load(Ordering::Relaxed),
61 FALLBACK_ROUTE_TOTAL.load(Ordering::Relaxed),
62 )
63}