use crate::brain::brain_sections;
use crate::brain::section_rank::Ranked;
pub(crate) const RECALL_MAX_SECTIONS: usize = 2;
pub(crate) const RECALL_MAX_CHARS: usize = 1200;
pub(crate) const RECALL_MIN_SCORE: f64 = 0.35;
pub fn recall_from(memory: &str, user_message: &str) -> Option<String> {
if !worth_reading_for(user_message) {
return None;
}
render(Ranked::build(memory).find_relevant(
user_message,
RECALL_MAX_SECTIONS,
RECALL_MAX_CHARS,
RECALL_MIN_SCORE,
))
}
fn worth_reading_for(user_message: &str) -> bool {
if user_message.starts_with("[System:") {
return false;
}
!brain_sections::query_terms(user_message).is_empty()
}
struct Cached {
stamp: (std::time::SystemTime, u64),
indexed: Ranked,
}
static CACHE: std::sync::RwLock<Option<Cached>> = std::sync::RwLock::new(None);
pub async fn recall_for(user_message: &str) -> Option<String> {
if !worth_reading_for(user_message) {
return None;
}
let path = crate::config::opencrabs_home().join("MEMORY.md");
let meta = tokio::fs::metadata(&path).await.ok()?;
let stamp = (meta.modified().ok()?, meta.len());
{
let cache = CACHE.read().ok()?;
if let Some(c) = cache.as_ref()
&& c.stamp == stamp
{
return render(c.indexed.find_relevant(
user_message,
RECALL_MAX_SECTIONS,
RECALL_MAX_CHARS,
RECALL_MIN_SCORE,
));
}
}
let memory = tokio::fs::read_to_string(&path).await.ok()?;
let indexed = Ranked::build(&memory);
let matches = indexed.find_relevant(
user_message,
RECALL_MAX_SECTIONS,
RECALL_MAX_CHARS,
RECALL_MIN_SCORE,
);
if let Ok(mut cache) = CACHE.write() {
tracing::debug!(
"MEMORY.md re-parsed for recall: {} sections, {} bytes",
indexed.len(),
memory.len()
);
*cache = Some(Cached { stamp, indexed });
}
render(matches)
}
fn render(matches: brain_sections::Matches) -> Option<String> {
if matches.sections.is_empty() {
return None;
}
let body = matches
.sections
.iter()
.map(brain_sections::Section::render)
.collect::<Vec<_>>()
.join("\n\n");
Some(format!(
"─── from your MEMORY.md, possibly relevant ───\n{body}\n\
[Recalled automatically. Load MEMORY.md with a query for more.]"
))
}