Skip to main content

brainos_backends/
memory.rs

1//! Memory backend for the action dispatcher.
2
3use std::sync::Arc;
4
5/// The capabilities this backend declares for the one manifest. Semantic-memory
6/// store/delete are always wired, so the config is not consulted.
7pub fn capabilities(_config: &brain::BrainConfig) -> Vec<intent::ToolDescriptor> {
8    use crate::capabilities::{backend, destructive, native, usage};
9    use intent::ToolAnnotations;
10    let mem = || backend("memory");
11    vec![
12        native(
13            "memory",
14            "store",
15            mem(),
16            ToolAnnotations::default(),
17            usage(
18                "The user states a durable fact about themselves, their world, projects, or preferences that should survive the session.",
19                "Transient chit-chat, or content already captured as an episodic turn.",
20                &["A subject-predicate-object triple can be extracted from the statement."],
21                "free / local SQLite + embedding",
22                "\"Remember that my deploy script lives in ops/deploy.sh\"",
23            ),
24        ),
25        native(
26            "memory",
27            "delete",
28            mem(),
29            destructive(),
30            usage(
31                "The user asks to forget or correct a previously stored fact.",
32                "When unsure which facts match — deletion is irreversible.",
33                &["A matching subject/predicate is known."],
34                "free / local",
35                "\"Forget what I said about the old API key\"",
36            ),
37        ),
38    ]
39}
40
41#[derive(Clone)]
42pub struct DefaultMemoryBackend {
43    pub semantic: Option<hippocampus::SemanticStore>,
44    pub embedder: Arc<tokio::sync::Mutex<Option<hippocampus::Embedder>>>,
45    pub embedding_dim: usize,
46}
47
48#[async_trait::async_trait]
49impl cortex::actions::MemoryBackend for DefaultMemoryBackend {
50    async fn store_fact(
51        &self,
52        namespace: &str,
53        _category: &str,
54        subject: &str,
55        predicate: &str,
56        object: &str,
57    ) -> Result<String, cortex::actions::ActionError> {
58        let Some(semantic) = &self.semantic else {
59            return Err(cortex::actions::ActionError::ExecutionFailed(
60                "Semantic store unavailable".to_string(),
61            ));
62        };
63
64        let content = format!("{subject} {predicate} {object}");
65        let vector = {
66            let mut guard = self.embedder.lock().await;
67            if let Some(embedder) = guard.as_mut() {
68                match embedder.embed(&content).await {
69                    Ok(v) => {
70                        hippocampus::embedding::sanitize_embedding(v, self.embedding_dim, &content)
71                    }
72                    Err(e) => {
73                        tracing::warn!("ActionDispatcher embedding failed: {e}");
74                        hippocampus::embedding::deterministic_fallback_embedding(
75                            &content,
76                            self.embedding_dim,
77                        )
78                    }
79                }
80            } else {
81                hippocampus::embedding::deterministic_fallback_embedding(
82                    &content,
83                    self.embedding_dim,
84                )
85            }
86        };
87
88        semantic
89            .store_fact(
90                namespace, _category, subject, predicate, object, 1.0, None, vector, None,
91            )
92            .await
93            .map_err(|e| cortex::actions::ActionError::ExecutionFailed(e.to_string()))
94    }
95
96    async fn recall(
97        &self,
98        query: &str,
99        top_k: usize,
100        namespace: Option<&str>,
101    ) -> Result<Vec<cortex::actions::MemoryFact>, cortex::actions::ActionError> {
102        let Some(semantic) = &self.semantic else {
103            return Err(cortex::actions::ActionError::ExecutionFailed(
104                "Semantic store unavailable".to_string(),
105            ));
106        };
107
108        let vector = {
109            let mut guard = self.embedder.lock().await;
110            if let Some(embedder) = guard.as_mut() {
111                match embedder.embed(query).await {
112                    Ok(v) => {
113                        hippocampus::embedding::sanitize_embedding(v, self.embedding_dim, query)
114                    }
115                    Err(e) => {
116                        tracing::warn!("ActionDispatcher embedding failed: {e}");
117                        hippocampus::embedding::deterministic_fallback_embedding(
118                            query,
119                            self.embedding_dim,
120                        )
121                    }
122                }
123            } else {
124                hippocampus::embedding::deterministic_fallback_embedding(query, self.embedding_dim)
125            }
126        };
127
128        let results = semantic
129            .search_similar(vector, top_k.max(1), namespace, None)
130            .await
131            .map_err(|e| cortex::actions::ActionError::ExecutionFailed(e.to_string()))?;
132
133        Ok(results
134            .into_iter()
135            .map(|r| cortex::actions::MemoryFact {
136                namespace: r.fact.namespace,
137                subject: r.fact.subject,
138                predicate: r.fact.predicate,
139                object: r.fact.object,
140                confidence: r.fact.confidence,
141            })
142            .collect())
143    }
144}