Skip to main content

khive_pack_memory/
lib.rs

1pub mod config;
2pub mod handlers;
3pub mod tunable;
4
5use std::sync::Mutex;
6
7use async_trait::async_trait;
8use serde_json::Value;
9
10use khive_runtime::pack::PackRuntime;
11use khive_runtime::{KhiveRuntime, NamespaceToken, RuntimeError, VerbRegistry};
12use khive_types::{HandlerDef, Pack, VerbCategory, Visibility};
13
14use crate::config::RecallConfig;
15
16pub struct MemoryPack {
17    runtime: KhiveRuntime,
18    /// Active recall config.
19    config: Mutex<RecallConfig>,
20}
21
22impl MemoryPack {
23    /// Return a clone of the current active `RecallConfig`.
24    ///
25    /// Handlers call this to pick up the latest tuned parameters.
26    pub(crate) fn active_config(&self) -> RecallConfig {
27        self.config.lock().unwrap().clone()
28    }
29}
30
31impl Pack for MemoryPack {
32    const NAME: &'static str = "memory";
33    const NOTE_KINDS: &'static [&'static str] = &["memory"];
34    const ENTITY_KINDS: &'static [&'static str] = &[];
35    const HANDLERS: &'static [HandlerDef] = &MEMORY_HANDLERS;
36    const REQUIRES: &'static [&'static str] = &["kg"];
37}
38
39// ADR-025: Illocutionary classification (Searle 1976)
40//   Commissive — commits caller to a persistent change
41//   Assertive  — retrieves/presents state of affairs
42static MEMORY_HANDLERS: [HandlerDef; 7] = [
43    // Commissive: commits a memory to the namespace
44    HandlerDef {
45        name: "remember",
46        description: "Create a memory note with salience and decay",
47        visibility: Visibility::Verb,
48        category: VerbCategory::Commissive,
49    },
50    // Assertive: retrieves memory notes via decay-aware ranking
51    HandlerDef {
52        name: "recall",
53        description: "Recall memory notes with decay-aware hybrid ranking",
54        visibility: Visibility::Verb,
55        category: VerbCategory::Assertive,
56    },
57    HandlerDef {
58        name: "recall.embed",
59        description: "Return the embedding vector used by memory recall",
60        visibility: Visibility::Subhandler,
61        category: VerbCategory::Assertive,
62    },
63    HandlerDef {
64        name: "recall.candidates",
65        description: "Return raw memory recall candidates by retrieval source",
66        visibility: Visibility::Subhandler,
67        category: VerbCategory::Assertive,
68    },
69    HandlerDef {
70        name: "recall.fuse",
71        description: "Return fused memory recall candidates before final scoring",
72        visibility: Visibility::Subhandler,
73        category: VerbCategory::Assertive,
74    },
75    // ADR-033 §2, F222: rerank stage between fuse and score
76    HandlerDef {
77        name: "recall.rerank",
78        description: "Apply configured rerankers to fused candidates (ADR-033 §2)",
79        visibility: Visibility::Subhandler,
80        category: VerbCategory::Assertive,
81    },
82    HandlerDef {
83        name: "recall.score",
84        description: "Score a memory recall candidate and return score breakdown",
85        visibility: Visibility::Subhandler,
86        category: VerbCategory::Assertive,
87    },
88];
89
90impl MemoryPack {
91    pub fn new(runtime: KhiveRuntime) -> Self {
92        Self {
93            runtime,
94            config: Mutex::new(RecallConfig::default()),
95        }
96    }
97}
98
99// ── ADR-027: inventory self-registration ─────────────────────────────────────
100
101struct MemoryPackFactory;
102
103impl khive_runtime::PackFactory for MemoryPackFactory {
104    fn name(&self) -> &'static str {
105        "memory"
106    }
107
108    fn requires(&self) -> &'static [&'static str] {
109        &["kg"]
110    }
111
112    fn create(&self, runtime: KhiveRuntime) -> Box<dyn khive_runtime::PackRuntime> {
113        Box::new(MemoryPack::new(runtime))
114    }
115}
116
117inventory::submit! { khive_runtime::PackRegistration(&MemoryPackFactory) }
118
119#[async_trait]
120impl PackRuntime for MemoryPack {
121    fn name(&self) -> &str {
122        <MemoryPack as Pack>::NAME
123    }
124
125    fn note_kinds(&self) -> &'static [&'static str] {
126        <MemoryPack as Pack>::NOTE_KINDS
127    }
128
129    fn entity_kinds(&self) -> &'static [&'static str] {
130        <MemoryPack as Pack>::ENTITY_KINDS
131    }
132
133    fn handlers(&self) -> &'static [HandlerDef] {
134        &MEMORY_HANDLERS
135    }
136
137    fn requires(&self) -> &'static [&'static str] {
138        <MemoryPack as Pack>::REQUIRES
139    }
140
141    async fn dispatch(
142        &self,
143        verb: &str,
144        params: Value,
145        registry: &VerbRegistry,
146        token: &NamespaceToken,
147    ) -> Result<Value, RuntimeError> {
148        match verb {
149            "remember" => self.handle_remember(token, params).await,
150            "recall" => self.handle_recall(token, params, registry).await,
151            "recall.embed" => self.handle_recall_embed(params).await,
152            "recall.candidates" => self.handle_recall_candidates(token, params).await,
153            "recall.fuse" => self.handle_recall_fuse(token, params, registry).await,
154            "recall.rerank" => self.handle_recall_rerank(params).await,
155            "recall.score" => self.handle_recall_score(params).await,
156            _ => Err(RuntimeError::InvalidInput(format!(
157                "memory pack does not handle verb {verb:?}"
158            ))),
159        }
160    }
161}