1pub mod consolidate_api;
10pub mod dump_api;
11pub mod explain_api;
12pub mod feedback_api;
13pub mod inspect_api;
14pub mod list_api;
15pub mod retrieve_api;
16pub mod runtime;
17mod signals;
18pub mod traverse_api;
19pub mod write_api;
20
21use hippmem_core::config::{AlgoParams, EmbedderConfig};
22use hippmem_core::ids::MemoryId;
23use hippmem_core::model::links::AssociationLink;
24use hippmem_core::model::understanding::MemoryUnderstanding;
25use hippmem_core::model::unit::{MemoryStage, WriteContext};
26use hippmem_model::registry::{build_embedder, BackendSelection};
27use hippmem_model::traits::Embedder;
28use hippmem_store::fulltext::FulltextIndex;
29use hippmem_store::semantic::binary::BinaryCodeIndex;
30use hippmem_store::semantic::hnsw::FlatVectorIndex;
31use hippmem_store::store::{RedbStore, Store};
32use parking_lot::RwLock;
33use serde::Serialize;
34use std::path::PathBuf;
35use std::sync::Arc;
36
37#[derive(Debug, thiserror::Error)]
43pub enum EngineError {
44 #[error("store: {0}")]
46 Store(String),
47
48 #[error("not found: {0:?}")]
50 NotFound(MemoryId),
51
52 #[error("invalid input: {0}")]
54 InvalidInput(String),
55
56 #[error("schema too new: {0}")]
58 SchemaTooNew(u16),
59
60 #[error("model: {0}")]
62 Model(String),
63
64 #[error("backend unavailable: {0}")]
66 BackendUnavailable(String),
67
68 #[error("internal: {0}")]
70 Internal(String),
71}
72
73pub type EngineResult<T> = Result<T, EngineError>;
75
76pub struct WriteMemoryInput {
80 pub content: String,
81 pub content_type: Option<hippmem_core::model::enums::ContentType>,
82 pub context: WriteContext,
83 pub importance_hint: Option<f32>,
84 pub source_refs: Vec<hippmem_core::model::unit::SourceRef>,
85}
86
87pub struct WriteMemoryOutput {
89 pub memory_id: MemoryId,
90 pub stage_reached: MemoryStage,
91 pub created_links: Vec<AssociationLink>,
92 pub understanding: MemoryUnderstanding,
93 pub warnings: Vec<WriteWarning>,
94}
95
96use hippmem_core::model::links::{RecallChannel, RetrievalResult};
99
100#[derive(Debug, Clone, Default)]
102pub struct RetrieveContext {
103 pub conversation_id: Option<u64>,
104 pub session_id: Option<u64>,
105 pub project_id: Option<u64>,
106 pub task_id: Option<u64>,
107 pub user_id: Option<u64>,
108 pub recent_memory_ids: Vec<MemoryId>,
109}
110
111pub struct RetrieveInput {
113 pub query: String,
114 pub context: RetrieveContext,
115 pub top_k: usize,
116 pub max_hops: Option<usize>,
117 pub retrieval_mode: hippmem_core::model::links::RetrievalMode,
118}
119
120pub struct RetrieveOutput {
122 pub retrieval_id: u64,
124 pub results: Vec<RetrievalResult>,
125 pub trace: RetrievalTrace,
126 pub diagnostics: RetrievalDiagnostics,
127}
128
129pub struct RetrievalTrace {
131 pub seeds: Vec<SeedRecord>,
132 pub steps: Vec<hippmem_core::model::links::ActivationStep>,
133 pub hops_used: u8,
134 pub merged_count: usize,
135}
136
137pub struct SeedRecord {
139 pub id: MemoryId,
140 pub channel: RecallChannel,
141 pub initial_energy: f32,
142 pub rank_in_channel: Option<usize>,
144}
145
146pub struct RetrievalDiagnostics {
148 pub channel_contributions: Vec<(RecallChannel, u32)>,
149 pub reranked: bool,
150 pub pruned_branches: u32,
151 pub backend_used: BackendUsage,
152 pub latency_ms: u32,
153}
154
155pub struct BackendUsage {
157 pub embedder: String,
158 pub reranker: Option<String>,
159}
160
161pub struct FeedbackInput {
165 pub retrieval_id: u64,
166 pub used_memory_ids: Vec<MemoryId>,
167 pub signal: UsageSignal,
168}
169
170#[derive(Debug, Clone)]
174pub enum ConsolidationScope {
175 Full,
176 Incremental,
177 ByMemoryType(hippmem_core::model::enums::ContentType),
178 ByTimeRange {
179 from: hippmem_core::time::Timestamp,
180 to: hippmem_core::time::Timestamp,
181 },
182 Reindex,
183 EdgesOnly,
184}
185
186use hippmem_core::model::links::LinkType;
189use hippmem_core::model::unit::{MemoryLifecycle, MemoryUnit};
190
191pub struct Explanation {
193 pub memory_id: MemoryId,
194 pub content_summary: String,
195 pub current_importance: f32,
196 pub linked: Vec<LinkSummary>,
197 pub corrections: Vec<MemoryId>,
198 pub contradictions: Vec<MemoryId>,
199 pub recent_activations: u32,
200}
201
202pub struct LinkSummary {
203 pub target: MemoryId,
204 pub link_type: LinkType,
205 pub strength: f32,
206}
207
208pub enum InspectQuery {
209 Memory(MemoryId),
210 Edges(MemoryId),
211 Channel(RecallChannel),
212 StoreStats,
213 QueueStatus,
214 StrongestEdges { limit: usize },
215 Contradictions { limit: usize },
216}
217
218pub enum InspectReport {
219 Memory(Box<MemoryInspect>),
220 StoreStats(StoreStats),
221 QueueStatus(QueueStatus),
222}
223
224pub struct MemoryInspect {
225 pub unit: MemoryUnit,
226 pub out_edges: Vec<EdgeView>,
227 pub in_edges: Vec<EdgeView>,
228 pub stage: MemoryStage,
229 pub lifecycle: MemoryLifecycle,
230}
231
232#[derive(Debug, Clone)]
233pub struct EdgeView {
234 pub from: MemoryId,
235 pub to: MemoryId,
236 pub link_type: LinkType,
237 pub strength: f32,
238 pub confidence: f32,
239 pub activation_count: u32,
240 pub evidence: String,
241}
242
243pub struct StoreStats {
244 pub memory_count: u64,
245 pub edge_count: u64,
246 pub observing_edge_count: u64,
247 pub per_index_size: Vec<(RecallChannel, u64)>,
248 pub queue_backlog: u64,
249 pub store_bytes: u64,
250}
251
252pub struct QueueStatus {
253 pub pending_enrich: u64,
254 pub pending_consolidate: u64,
255 pub in_flight: u64,
256 pub oldest_pending_age_ms: u64,
257}
258
259pub struct ConsolidationReport {
261 pub memories_processed: u64,
262 pub edges_decayed: u64,
263 pub edges_archived: u64,
264 pub edges_merged: u64,
265 pub observation_promoted: u64,
266 pub summaries_created: u64,
267 pub contradictions_found: u64,
268 pub reindexed: bool,
269 pub elapsed_ms: u64,
270}
271
272#[derive(Debug, Clone, Copy, PartialEq, Eq)]
274pub enum UsageSignal {
275 Referenced,
276 UserConfirmedCorrect,
277 TaskSucceeded,
278 UserRejected,
279}
280
281#[derive(Debug, Clone, PartialEq)]
283pub enum WriteWarning {
284 ExtractorDegraded,
286 EmbeddingDeferred,
288 StrongDimsDeferred,
290 ModelError { detail: String },
292}
293
294#[derive(Debug, Clone)]
298pub struct ListInput {
299 pub limit: usize,
301 pub cursor: Option<u128>,
303 pub content_type: Option<hippmem_core::model::enums::ContentType>,
305}
306
307impl Default for ListInput {
308 fn default() -> Self {
309 Self {
310 limit: 20,
311 cursor: None,
312 content_type: None,
313 }
314 }
315}
316
317#[derive(Debug, Clone, Serialize)]
319pub struct ListOutput {
320 pub items: Vec<ListItem>,
321 pub next_cursor: Option<u128>,
323 pub total: u64,
325}
326
327#[derive(Debug, Clone, Serialize)]
329pub struct ListItem {
330 pub id: hippmem_core::ids::MemoryId,
331 pub content_preview: String,
333 pub content_type: hippmem_core::model::enums::ContentType,
334 pub created_at: hippmem_core::time::Timestamp,
335 pub importance: f32,
336 pub stage: hippmem_core::model::unit::MemoryStage,
337 pub lifecycle: hippmem_core::model::unit::MemoryLifecycle,
338 pub edge_count: usize,
340}
341
342#[derive(Debug, Clone, Default)]
346pub struct DumpInput {
347 pub output_path: Option<std::path::PathBuf>,
349}
350
351#[derive(Debug, Clone, Serialize)]
353pub struct DumpOutput {
354 pub count: u64,
355 pub written_to: Option<std::path::PathBuf>,
357 pub json: Option<String>,
359}
360
361#[derive(Debug, Clone)]
365pub struct TraverseInput {
366 pub start_id: hippmem_core::ids::MemoryId,
368 pub max_depth: u8,
370 pub direction: TraverseDirection,
372 pub link_types: Option<Vec<hippmem_core::model::links::LinkType>>,
374}
375
376impl TraverseInput {
377 pub fn new(start_id: hippmem_core::ids::MemoryId) -> Self {
379 Self {
380 start_id,
381 max_depth: 2,
382 direction: TraverseDirection::Outgoing,
383 link_types: None,
384 }
385 }
386}
387
388impl Default for TraverseInput {
389 fn default() -> Self {
390 Self {
391 start_id: hippmem_core::ids::MemoryId(0),
392 max_depth: 2,
393 direction: TraverseDirection::Outgoing,
394 link_types: None,
395 }
396 }
397}
398
399#[derive(Debug, Clone, Copy, PartialEq, Eq)]
401pub enum TraverseDirection {
402 Outgoing,
404 Incoming,
406 Both,
408}
409
410#[derive(Debug, Clone)]
412pub struct TraverseOutput {
413 pub nodes: Vec<TraverseNode>,
415 pub edges: Vec<EdgeView>,
417}
418
419#[derive(Debug, Clone)]
421pub struct TraverseNode {
422 pub id: hippmem_core::ids::MemoryId,
423 pub depth: u8,
425 pub content_preview: String,
426 pub content_type: hippmem_core::model::enums::ContentType,
427 pub importance: f32,
428}
429
430impl From<hippmem_store::store::StoreError> for EngineError {
433 fn from(e: hippmem_store::store::StoreError) -> Self {
434 EngineError::Store(e.to_string())
435 }
436}
437
438#[derive(Debug, Clone)]
442pub struct BackgroundConfig {
443 pub enrich_workers: usize,
445 pub consolidate_workers: usize,
447 pub queue_capacity: usize,
449 pub consolidate_interval_ms: u64,
451 pub enrich_enabled: bool,
453}
454
455impl Default for BackgroundConfig {
456 fn default() -> Self {
457 Self {
458 enrich_workers: 2,
459 consolidate_workers: 1,
460 queue_capacity: 4096,
461 consolidate_interval_ms: 3_600_000,
462 enrich_enabled: true,
463 }
464 }
465}
466
467#[derive(Debug, Clone)]
472pub struct EngineConfig {
473 pub store_dir: PathBuf,
475 pub algo: AlgoParams,
477 pub embedder: EmbedderConfig,
479 pub backend: BackendSelection,
481 pub background: BackgroundConfig,
483}
484
485impl Default for EngineConfig {
486 fn default() -> Self {
487 Self {
488 store_dir: PathBuf::from("./hippmem_data"),
489 algo: AlgoParams::default(),
490 embedder: EmbedderConfig::default(),
491 backend: BackendSelection::default(),
492 background: BackgroundConfig::default(),
493 }
494 }
495}
496
497pub struct Engine {
506 store: Arc<RedbStore>,
508 #[allow(dead_code)]
510 params: Arc<RwLock<AlgoParams>>,
511 embedder: Arc<dyn Embedder>,
513 #[allow(dead_code)]
515 backend: BackendSelection,
516 fulltext_index: parking_lot::Mutex<FulltextIndex>,
518 fulltext_dir: PathBuf,
520 binary_code_index: parking_lot::Mutex<BinaryCodeIndex>,
522 dense_vector_index: parking_lot::Mutex<FlatVectorIndex>,
524}
525
526impl Engine {
527 pub fn open(config: EngineConfig) -> EngineResult<Self> {
534 if let Some(parent) = config.store_dir.parent() {
536 std::fs::create_dir_all(parent).map_err(|e| {
537 EngineError::Store(format!("cannot create storage directory: {}", e))
538 })?;
539 }
540
541 let embedder =
543 build_embedder(&config.embedder).map_err(|e| EngineError::Model(e.to_string()))?;
544
545 let store = RedbStore::open(&config.store_dir)?;
547
548 let fulltext_dir = config
550 .store_dir
551 .parent()
552 .map(|p| p.join("fulltext"))
553 .unwrap_or_else(|| PathBuf::from("hippmem_data").join("fulltext"));
554 let fulltext_index = FulltextIndex::open(&fulltext_dir)
555 .or_else(|_| FulltextIndex::create(&fulltext_dir))
556 .map_err(|e| {
557 EngineError::Store(format!("Tantivy index initialization failed: {}", e))
558 })?;
559
560 Ok(Self {
561 store: Arc::new(store),
562 params: Arc::new(RwLock::new(config.algo)),
563 embedder,
564 backend: config.backend,
565 fulltext_index: parking_lot::Mutex::new(fulltext_index),
566 fulltext_dir,
567 binary_code_index: parking_lot::Mutex::new(BinaryCodeIndex::new()),
568 dense_vector_index: parking_lot::Mutex::new(FlatVectorIndex::new()),
569 })
570 }
571
572 pub fn close(self) -> EngineResult<()> {
577 if let Err(e) = self.fulltext_index.lock().flush() {
579 eprintln!("Tantivy flush failed: {}", e);
581 }
582 drop(self.store);
584 Ok(())
585 }
586
587 pub fn set_fulltext_commit_every(&self, n: usize) {
590 self.fulltext_index.lock().set_commit_every(n);
591 }
592
593 pub fn flush_fulltext(&self) {
595 let _ = self.fulltext_index.lock().flush();
596 }
597}