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}