Skip to main content

khive_pack_knowledge/
pack.rs

1//! `KnowledgePack` struct, factory, and `PackRuntime` impl.
2
3use std::sync::Mutex;
4
5use async_trait::async_trait;
6use serde_json::Value;
7
8use khive_brain_core::SectionPosteriorState;
9use khive_runtime::pack::PackRuntime;
10use khive_runtime::{KhiveRuntime, NamespaceToken, RuntimeError, VerbRegistry};
11use khive_types::{HandlerDef, Pack};
12
13use crate::knowledge::vamana;
14use crate::knowledge::KnowledgeHandlers;
15use crate::vocab::KNOWLEDGE_HANDLERS;
16
17/// Knowledge corpus pack — atoms, domains, TF-IDF search, fold, import, and KG concept verbs.
18pub struct KnowledgePack {
19    pub(crate) runtime: KhiveRuntime,
20    pub(crate) ann: vamana::SharedAnn,
21    pub(crate) section_posteriors: Mutex<SectionPosteriorState>,
22    /// Explicit brain profile ID from config (ADR-035 §Brain profile configuration).
23    ///
24    /// Tier-1 of the 3-tier feedback resolution: when set, `knowledge.feedback` directs
25    /// feedback to this profile via `brain.feedback`. When absent, tier-2
26    /// (namespace-bound profile) and tier-3 (global prior) are tried in order.
27    pub(crate) brain_profile: Option<String>,
28}
29
30impl Pack for KnowledgePack {
31    const NAME: &'static str = "knowledge";
32    const NOTE_KINDS: &'static [&'static str] = &[];
33    const ENTITY_KINDS: &'static [&'static str] = &[];
34    const HANDLERS: &'static [HandlerDef] = &KNOWLEDGE_HANDLERS;
35    const REQUIRES: &'static [&'static str] = &["kg"];
36}
37
38impl KnowledgePack {
39    /// Create a new pack bound to the given runtime, initializing a shared ANN index.
40    pub fn new(runtime: KhiveRuntime) -> Self {
41        let brain_profile = runtime.config().brain_profile.clone();
42        Self {
43            runtime,
44            ann: vamana::new_shared(),
45            section_posteriors: Mutex::new(SectionPosteriorState::new()),
46            brain_profile,
47        }
48    }
49}
50
51struct KnowledgePackFactory;
52
53impl khive_runtime::PackFactory for KnowledgePackFactory {
54    fn name(&self) -> &'static str {
55        "knowledge"
56    }
57
58    fn requires(&self) -> &'static [&'static str] {
59        &["kg"]
60    }
61
62    fn create(&self, runtime: KhiveRuntime) -> Box<dyn khive_runtime::PackRuntime> {
63        Box::new(KnowledgePack::new(runtime))
64    }
65}
66
67inventory::submit! { khive_runtime::PackRegistration(&KnowledgePackFactory) }
68
69#[async_trait]
70impl PackRuntime for KnowledgePack {
71    fn name(&self) -> &str {
72        <KnowledgePack as Pack>::NAME
73    }
74
75    fn note_kinds(&self) -> &'static [&'static str] {
76        <KnowledgePack as Pack>::NOTE_KINDS
77    }
78
79    fn entity_kinds(&self) -> &'static [&'static str] {
80        <KnowledgePack as Pack>::ENTITY_KINDS
81    }
82
83    fn handlers(&self) -> &'static [HandlerDef] {
84        &KNOWLEDGE_HANDLERS
85    }
86
87    fn requires(&self) -> &'static [&'static str] {
88        <KnowledgePack as Pack>::REQUIRES
89    }
90
91    async fn warm(&self) {
92        crate::knowledge::vamana::warm_known_snapshots(&self.runtime, &self.ann).await;
93        if !self.runtime.default_embedder_name().is_empty() {
94            let runtime = self.runtime.clone();
95            tokio::spawn(async move {
96                let _ = runtime.embed("__khive_knowledge_warm__").await;
97            });
98        }
99    }
100
101    async fn dispatch(
102        &self,
103        verb: &str,
104        params: Value,
105        registry: &VerbRegistry,
106        token: &NamespaceToken,
107    ) -> Result<Value, RuntimeError> {
108        match verb {
109            "knowledge.upsert_atoms" => {
110                KnowledgeHandlers::upsert_atoms(&self.runtime, token, params).await
111            }
112            "knowledge.upsert_domains" => {
113                KnowledgeHandlers::upsert_domains(&self.runtime, token, params).await
114            }
115            "knowledge.get" => KnowledgeHandlers::get(&self.runtime, token, params).await,
116            "knowledge.list" => KnowledgeHandlers::list(&self.runtime, token, params).await,
117            "knowledge.delete_atoms" => {
118                KnowledgeHandlers::delete_atoms(&self.runtime, token, params).await
119            }
120            "knowledge.stats" => KnowledgeHandlers::stats(&self.runtime, token, params).await,
121            "knowledge.index" => {
122                KnowledgeHandlers::index(&self.runtime, token, params, &self.ann, None).await
123            }
124            "knowledge.fold" => KnowledgeHandlers::fold(&self.runtime, token, params).await,
125            "knowledge.search" => {
126                KnowledgeHandlers::search(&self.runtime, token, params, &self.ann).await
127            }
128            "knowledge.suggest" => {
129                KnowledgeHandlers::suggest(&self.runtime, token, params, &self.ann).await
130            }
131            "knowledge.compose" => {
132                KnowledgeHandlers::compose(&self.runtime, token, params, &self.ann).await
133            }
134            "knowledge.edit" => KnowledgeHandlers::edit(&self.runtime, token, params).await,
135            "knowledge.import" => KnowledgeHandlers::import(&self.runtime, token, params).await,
136            "knowledge.challenge" => {
137                KnowledgeHandlers::challenge(&self.runtime, token, params).await
138            }
139            "knowledge.adjudicate" => {
140                KnowledgeHandlers::adjudicate(&self.runtime, token, params).await
141            }
142            "knowledge.learn" => self.handle_learn(token, params).await,
143            "knowledge.cite" => self.handle_cite(token, params).await,
144            "knowledge.topic" => self.handle_topic(token, params).await,
145            "knowledge.feedback" => self.handle_feedback(token, params, registry).await,
146            _ => Err(RuntimeError::InvalidInput(format!(
147                "knowledge pack does not handle verb {verb:?}"
148            ))),
149        }
150    }
151}