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