Skip to main content

sim_lib_agent/memory/core/
advanced.rs

1use 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}