sim_lib_agent/memory/core/
advanced.rs1use sim_kernel::{CapabilityName, Cx, Error, Expr, Result, Symbol, Value};
2use std::{
3 cmp::Ordering,
4 path::PathBuf,
5 sync::{Arc, Mutex},
6};
7
8use crate::{FILE_WRITE_CAPABILITY, PersonaMemory, VectorMemory, cosine, embed};
9
10use super::super::store::{
11 append_memory_log, flatten_expr_text, load_memory_log, rewrite_memory_log, snapshot_entries,
12 value_to_snapshot_expr,
13};
14use super::types::MemoryBackend;
15
16#[derive(Clone)]
17pub(super) struct VectorEntry {
18 pub(super) expr: Expr,
19 embedding: [f32; crate::embed::EMBED_DIM],
20}
21
22#[derive(Clone, Default)]
23pub(super) struct PersonaState {
24 profile: Vec<(Expr, Expr)>,
25 notes: Vec<Expr>,
26}
27
28fn lock_vector_entries<'a>(
29 entries: &'a Arc<Mutex<Vec<VectorEntry>>>,
30) -> Result<std::sync::MutexGuard<'a, Vec<VectorEntry>>> {
31 entries
32 .lock()
33 .map_err(|_| Error::PoisonedLock("vector memory entries"))
34}
35
36fn lock_persona_state<'a>(
37 state: &'a Arc<Mutex<PersonaState>>,
38) -> Result<std::sync::MutexGuard<'a, PersonaState>> {
39 state
40 .lock()
41 .map_err(|_| Error::PoisonedLock("persona memory state"))
42}
43
44impl VectorMemory {
45 pub(crate) fn open(path: Option<PathBuf>, codecs: Vec<Symbol>) -> Result<Self> {
46 let entries = match &path {
47 Some(path) => load_memory_log(path)?
48 .into_iter()
49 .map(VectorEntry::from_expr)
50 .collect(),
51 None => Vec::new(),
52 };
53 Ok(Self {
54 symbol: Symbol::qualified("memory", "vector"),
55 capabilities: path
56 .as_ref()
57 .map(|_| vec![CapabilityName::new(FILE_WRITE_CAPABILITY)])
58 .unwrap_or_default(),
59 address: sim_lib_server::ServerAddress::Local,
60 codecs,
61 path,
62 entries: Arc::new(Mutex::new(entries)),
63 })
64 }
65}
66
67impl PersonaMemory {
68 pub(crate) fn open(path: Option<PathBuf>, codecs: Vec<Symbol>) -> Result<Self> {
69 let state = match &path {
70 Some(path) => {
71 let entries = load_memory_log(path)?;
72 match entries.as_slice() {
73 [] => PersonaState::default(),
74 [snapshot] => persona_state_from_snapshot(snapshot.clone())?,
75 _ => {
76 return Err(Error::HostError(format!(
77 "persona memory {} expected at most one snapshot entry",
78 path.display()
79 )));
80 }
81 }
82 }
83 None => PersonaState::default(),
84 };
85 Ok(Self {
86 symbol: Symbol::qualified("memory", "persona"),
87 capabilities: path
88 .as_ref()
89 .map(|_| vec![CapabilityName::new(FILE_WRITE_CAPABILITY)])
90 .unwrap_or_default(),
91 address: sim_lib_server::ServerAddress::Local,
92 codecs,
93 path,
94 state: Arc::new(Mutex::new(state)),
95 })
96 }
97}
98
99impl VectorEntry {
100 fn from_expr(expr: Expr) -> Self {
101 let embedding = embed(&flatten_expr_text(&expr));
102 Self { expr, embedding }
103 }
104}
105
106impl PersonaState {
107 fn snapshot(&self) -> Expr {
108 Expr::List(vec![Expr::Map(vec![
109 (
110 Expr::Symbol(Symbol::new("state")),
111 Expr::Map(self.profile.clone()),
112 ),
113 (
114 Expr::Symbol(Symbol::new("notes")),
115 Expr::List(self.notes.clone()),
116 ),
117 ])])
118 }
119}
120
121fn persona_state_from_snapshot(snapshot: Expr) -> Result<PersonaState> {
122 let (Expr::List(items) | Expr::Vector(items)) = snapshot else {
123 return Err(Error::TypeMismatch {
124 expected: "persona snapshot list",
125 found: "non-list",
126 });
127 };
128 let Some(entry) = items.first() else {
129 return Ok(PersonaState::default());
130 };
131 let Expr::Map(entries) = entry else {
132 return Err(Error::TypeMismatch {
133 expected: "persona snapshot map",
134 found: "non-map",
135 });
136 };
137 let mut profile = Vec::new();
138 let mut notes = Vec::new();
139 for (key, value) in entries {
140 match key {
141 Expr::Symbol(symbol) if symbol.name.as_ref() == "state" => {
142 let Expr::Map(state_entries) = value else {
143 return Err(Error::TypeMismatch {
144 expected: "persona state map",
145 found: "non-map",
146 });
147 };
148 profile = state_entries.clone();
149 }
150 Expr::Symbol(symbol) if symbol.name.as_ref() == "notes" => {
151 notes = snapshot_entries(value.clone())?;
152 }
153 _ => {}
154 }
155 }
156 Ok(PersonaState { profile, notes })
157}
158
159impl MemoryBackend for VectorMemory {
160 fn append(&self, cx: &mut Cx, msg: Value) -> Result<()> {
161 if self.path.is_some() {
162 cx.require(&CapabilityName::new(FILE_WRITE_CAPABILITY))?;
163 }
164 let expr = value_to_snapshot_expr(cx, msg)?;
165 let entry = VectorEntry::from_expr(expr.clone());
166 lock_vector_entries(&self.entries)?.push(entry);
167 if let Some(path) = &self.path {
168 append_memory_log(path, &expr)?;
169 }
170 Ok(())
171 }
172
173 fn recent(&self, cx: &mut Cx, count: u32) -> Result<Vec<Value>> {
174 let entries = lock_vector_entries(&self.entries)?;
175 let count = usize::try_from(count).unwrap_or(usize::MAX);
176 entries[entries.len().saturating_sub(count)..]
177 .iter()
178 .map(|entry| crate::expr_to_value(cx, &entry.expr))
179 .collect()
180 }
181
182 fn search(&self, cx: &mut Cx, query: Expr, k: u32) -> Result<Vec<Value>> {
183 let query_embedding = embed(&flatten_expr_text(&query));
184 let mut scored = lock_vector_entries(&self.entries)?
185 .iter()
186 .enumerate()
187 .map(|(index, entry)| {
188 (
189 index,
190 cosine(&query_embedding, &entry.embedding),
191 entry.expr.clone(),
192 )
193 })
194 .collect::<Vec<_>>();
195 scored.sort_by(|left, right| {
196 right
197 .1
198 .partial_cmp(&left.1)
199 .unwrap_or(Ordering::Equal)
200 .then(left.0.cmp(&right.0))
201 });
202 scored
203 .into_iter()
204 .take(usize::try_from(k).unwrap_or(usize::MAX))
205 .map(|(_, _, expr)| crate::expr_to_value(cx, &expr))
206 .collect()
207 }
208
209 fn snapshot(&self, _cx: &mut Cx) -> Result<Expr> {
210 Ok(Expr::List(
211 lock_vector_entries(&self.entries)?
212 .iter()
213 .map(|entry| entry.expr.clone())
214 .collect(),
215 ))
216 }
217
218 fn restore(&self, cx: &mut Cx, snap: Expr) -> Result<()> {
219 if self.path.is_some() {
220 cx.require(&CapabilityName::new(FILE_WRITE_CAPABILITY))?;
221 }
222 let entries = snapshot_entries(snap)?
223 .into_iter()
224 .map(VectorEntry::from_expr)
225 .collect::<Vec<_>>();
226 if let Some(path) = &self.path {
227 let exprs = entries
228 .iter()
229 .map(|entry| entry.expr.clone())
230 .collect::<Vec<_>>();
231 rewrite_memory_log(path, &exprs)?;
232 }
233 *lock_vector_entries(&self.entries)? = entries;
234 Ok(())
235 }
236}
237
238impl MemoryBackend for PersonaMemory {
239 fn append(&self, cx: &mut Cx, msg: Value) -> Result<()> {
240 if self.path.is_some() {
241 cx.require(&CapabilityName::new(FILE_WRITE_CAPABILITY))?;
242 }
243 let note = value_to_snapshot_expr(cx, msg)?;
244 let mut state = lock_persona_state(&self.state)?;
245 state.notes.push(note);
246 if let Some(path) = &self.path {
247 rewrite_memory_log(path, &[state.snapshot()])?;
248 }
249 Ok(())
250 }
251
252 fn recent(&self, cx: &mut Cx, count: u32) -> Result<Vec<Value>> {
253 let state = lock_persona_state(&self.state)?;
254 let count = usize::try_from(count).unwrap_or(usize::MAX);
255 state.notes[state.notes.len().saturating_sub(count)..]
256 .iter()
257 .map(|entry| crate::expr_to_value(cx, entry))
258 .collect()
259 }
260
261 fn search(&self, cx: &mut Cx, query: Expr, k: u32) -> Result<Vec<Value>> {
262 let query_terms = flatten_expr_text(&query)
263 .split_whitespace()
264 .map(str::to_owned)
265 .collect::<Vec<_>>();
266 let state = lock_persona_state(&self.state)?;
267 let filtered = state
268 .notes
269 .iter()
270 .rev()
271 .filter(|expr| {
272 let haystack = flatten_expr_text(expr);
273 query_terms.iter().all(|term| haystack.contains(term))
274 })
275 .take(usize::try_from(k).unwrap_or(usize::MAX))
276 .cloned()
277 .collect::<Vec<_>>();
278 filtered
279 .into_iter()
280 .rev()
281 .map(|expr| crate::expr_to_value(cx, &expr))
282 .collect()
283 }
284
285 fn snapshot(&self, _cx: &mut Cx) -> Result<Expr> {
286 Ok(lock_persona_state(&self.state)?.snapshot())
287 }
288
289 fn restore(&self, cx: &mut Cx, snap: Expr) -> Result<()> {
290 if self.path.is_some() {
291 cx.require(&CapabilityName::new(FILE_WRITE_CAPABILITY))?;
292 }
293 let state = persona_state_from_snapshot(snap)?;
294 if let Some(path) = &self.path {
295 rewrite_memory_log(path, &[state.snapshot()])?;
296 }
297 *lock_persona_state(&self.state)? = state;
298 Ok(())
299 }
300}