brainos_backends/
memory.rs1use std::sync::Arc;
4
5pub 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}