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 config: Mutex<RecallConfig>,
20}
21
22impl MemoryPack {
23 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
39static MEMORY_HANDLERS: [HandlerDef; 7] = [
43 HandlerDef {
45 name: "remember",
46 description: "Create a memory note with salience and decay",
47 visibility: Visibility::Verb,
48 category: VerbCategory::Commissive,
49 },
50 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 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
99struct 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}