Skip to main content

oxios_kernel/kernel_handle/
memory_api.rs

1//! Memory API — memory subsystem facade (Phase C).
2//!
3//! Extracted from `AgentApi` to provide a focused interface for
4//! memory operations. Replaces the previous `AgentApi.memory_manager()`
5//! leaky accessor pattern.
6//!
7//! Consumers should use [`KernelHandle::memory()`] to access this API.
8
9use crate::memory::{HnswMemoryIndex, MemoryEntry, MemoryManager, MemoryType, SemanticHit};
10use std::sync::Arc;
11
12/// Memory subsystem facade.
13///
14/// Provides high-level memory operations without exposing the
15/// internal `MemoryManager` directly. This is the 14th typed API
16/// in `KernelHandle` (alongside `A2aApi`, `AgentApi`, etc.).
17pub struct MemoryApi {
18    /// Underlying memory manager.
19    pub(crate) memory_manager: Arc<MemoryManager>,
20    /// Optional HNSW index for fast semantic search.
21    pub(crate) hnsw_index: Option<Arc<HnswMemoryIndex>>,
22}
23
24impl MemoryApi {
25    /// Create a new MemoryApi.
26    ///
27    /// The optional HNSW index, when present, is used by `search_semantic`
28    /// and `rebuild_hnsw_index`. Callers that have an attached index (e.g.
29    /// `KernelHandle::memory`) should pass it here so this facade agrees with
30    /// `AgentApi`'s memory operations.
31    pub fn new(
32        memory_manager: Arc<MemoryManager>,
33        hnsw_index: Option<Arc<HnswMemoryIndex>>,
34    ) -> Self {
35        Self {
36            memory_manager,
37            hnsw_index,
38        }
39    }
40
41    /// Attach an HNSW index for fast semantic search.
42    pub fn set_hnsw_index(&mut self, index: Arc<HnswMemoryIndex>) {
43        self.hnsw_index = Some(index);
44    }
45
46    /// Store a memory entry. Returns the entry's ID.
47    pub async fn remember(&self, entry: MemoryEntry) -> anyhow::Result<String> {
48        self.memory_manager.remember(entry).await
49    }
50
51    /// Search memory by text query.
52    pub async fn search(
53        &self,
54        query: &str,
55        memory_type: Option<MemoryType>,
56        limit: usize,
57    ) -> anyhow::Result<Vec<MemoryEntry>> {
58        self.memory_manager.search(query, memory_type, limit).await
59    }
60
61    /// Recall memory by query (semantic if HNSW available, else keyword).
62    pub async fn recall(&self, query: &str) -> anyhow::Result<Vec<MemoryEntry>> {
63        self.memory_manager.recall(query).await
64    }
65
66    /// Get a specific memory entry by ID and type.
67    pub async fn get(
68        &self,
69        id: &str,
70        memory_type: MemoryType,
71    ) -> anyhow::Result<Option<MemoryEntry>> {
72        self.memory_manager.get(id, memory_type).await
73    }
74
75    /// Forget (delete) a memory entry.
76    pub async fn forget(&self, id: &str, memory_type: MemoryType) -> anyhow::Result<bool> {
77        self.memory_manager.forget(id, memory_type).await
78    }
79
80    /// List memories of a given type.
81    pub async fn list(
82        &self,
83        memory_type: MemoryType,
84        limit: usize,
85    ) -> anyhow::Result<Vec<MemoryEntry>> {
86        self.memory_manager.list(memory_type, limit).await
87    }
88
89    /// Search memory using semantic similarity (returns `SemanticHit`s ranked
90    /// by cosine similarity).
91    ///
92    /// Uses the attached HNSW index when available; otherwise falls back to
93    /// keyword search. In the fallback case `similarity` is `0.0` (unknown),
94    /// never a fabricated `1.0`.
95    pub async fn search_semantic(
96        &self,
97        query: &str,
98        limit: usize,
99    ) -> anyhow::Result<Vec<SemanticHit>> {
100        if let Some(hnsw) = &self.hnsw_index {
101            self.memory_manager
102                .semantic_search(query, None, limit, hnsw)
103                .await
104        } else {
105            // Fallback: keyword search. similarity/distance are unknown.
106            let entries = self.memory_manager.search(query, None, limit).await?;
107            Ok(entries
108                .into_iter()
109                .map(|e| SemanticHit {
110                    entry: e,
111                    distance: 0.0,
112                    similarity: 0.0,
113                })
114                .collect())
115        }
116    }
117
118    /// Get memory statistics: (total_entries, vector_index_size).
119    pub async fn stats(&self) -> (usize, usize) {
120        (
121            self.memory_manager.total_entries().await,
122            self.memory_manager.vector_index_size(),
123        )
124    }
125
126    /// Rebuild the HNSW index from current memory state.
127    ///
128    /// Returns the number of vectors indexed. Errors if no HNSW index is
129    /// attached (matching `AgentApi::rebuild_hnsw_index`).
130    pub async fn rebuild_hnsw_index(&self) -> anyhow::Result<usize> {
131        if let Some(hnsw) = &self.hnsw_index {
132            self.memory_manager.rebuild_hnsw_index(hnsw).await
133        } else {
134            Err(anyhow::anyhow!("HNSW index not initialized"))
135        }
136    }
137
138    /// Access the underlying memory manager. For advanced operations
139    /// not yet exposed via this facade.
140    pub fn manager(&self) -> &Arc<MemoryManager> {
141        &self.memory_manager
142    }
143}