Persist memory scores, use them for eviction in trim_entries

Scores are saved to memory-scores.json alongside the conversation log
after each scoring run, and loaded on startup — no more re-scoring
on restart.

trim_entries now evicts lowest-scored memories first (instead of
oldest-first) when memories exceed 50% of context. The 50% threshold
stays as a heuristic for memory-vs-conversation balance until we have
a scoring signal for conversation entries too. Unscored memories get
0.0, so they're evicted before scored ones.

save_memory_scores rebuilds from current entries, so evicted memories
are automatically expired from the scores file.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
Kent Overstreet 2026-04-07 19:35:46 -04:00
parent bef1bfbb33
commit df62b7ceaa
2 changed files with 68 additions and 5 deletions

View file

@ -101,11 +101,23 @@ pub fn trim_entries(
}
}
// Phase 2b: if memories > 50% of entries, evict oldest memories
// Phase 2b: if memories > 50% of context, evict lowest-scored first
if cur_mem > conv_tokens && trimmed > max_tokens {
for i in 0..deduped.len() {
if drop[i] { continue; }
if !deduped[i].is_memory() { continue; }
let mut mem_indices: Vec<usize> = (0..deduped.len())
.filter(|&i| !drop[i] && deduped[i].is_memory())
.collect();
mem_indices.sort_by(|&a, &b| {
let sa = match &deduped[a] {
ConversationEntry::Memory { score, .. } => score.unwrap_or(0.0),
_ => 0.0,
};
let sb = match &deduped[b] {
ConversationEntry::Memory { score, .. } => score.unwrap_or(0.0),
_ => 0.0,
};
sa.partial_cmp(&sb).unwrap_or(std::cmp::Ordering::Equal)
});
for i in mem_indices {
if cur_mem <= conv_tokens { break; }
if trimmed <= max_tokens { break; }
drop[i] = true;
@ -114,7 +126,7 @@ pub fn trim_entries(
}
}
// Phase 2b: drop oldest entries until under budget
// Phase 2c: if still over, drop oldest conversation entries
for i in 0..deduped.len() {
if trimmed <= max_tokens { break; }
if drop[i] { continue; }