mind: trigger incremental scoring on startup + log persist path

Two changes to make scoring debuggable and self-starting:

1. init() kicks off start_memory_scoring() after restore_from_log +
   load_memory_scores. No user message needed to exercise the
   incremental path.

2. Diagnostic logging around the on_score persist path:
   - [scoring] persisted K → N.NNN (Section[i]) read_back=Some(...)
     when find_memory_by_key succeeds and set_score stores the score
     (with a read-back check on the leaf).
   - [scoring] DROP K: find_memory_by_key None (id=N, cv=M)
     when the scored key isn't findable in the live context — with
     section sizes to diagnose whether content shrank.
   - [scoring] snapshot size=N contains(K)=true/false
     after collect_memory_scores, to catch the case where set_score
     claims to have written but collect doesn't see it.
   - [scoring] about to save N entries
   - save_memory_scores now also logs serialize/write errors so a
     silent write failure isn't invisible.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
ProofOfConcept 2026-04-16 20:47:16 -04:00
parent b8485ed6c1
commit 0d1044c2e8

View file

@ -103,9 +103,13 @@ fn collect_memory_scores(ctx: &ContextState) -> std::collections::BTreeMap<Strin
/// Save memory scores to disk.
fn save_memory_scores(scores: &std::collections::BTreeMap<String, f64>, path: &std::path::Path) {
if let Ok(json) = serde_json::to_string_pretty(scores) {
let _ = std::fs::write(path, json);
dbglog!("[scoring] saved {} scores to {}", scores.len(), path.display());
match serde_json::to_string_pretty(scores) {
Ok(json) => match std::fs::write(path, &json) {
Ok(()) => dbglog!("[scoring] saved {} scores to {} ({} bytes)",
scores.len(), path.display(), json.len()),
Err(e) => dbglog!("[scoring] save FAILED ({}): {}", path.display(), e),
},
Err(e) => dbglog!("[scoring] serialize FAILED: {}", e),
}
}
@ -506,6 +510,17 @@ impl Mind {
// Load persistent subconscious state
let state_path = self.config.session_dir.join("subconscious-state.json");
self.subconscious.lock().await.set_state_path(state_path);
// Kick off an incremental scoring pass on startup so memories due
// for re-scoring get evaluated without requiring a user message.
{
let mut s = self.shared.lock().unwrap();
if !s.scoring_in_flight {
s.scoring_in_flight = true;
drop(s);
self.start_memory_scoring();
}
}
}
pub fn turn_watch(&self) -> tokio::sync::watch::Receiver<bool> {
@ -619,14 +634,40 @@ impl Mind {
let mut ctx = agent.context.lock().await;
// Find memory by key in identity or conversation
let found = find_memory_by_key(&ctx, &key);
if let Some((section, i)) = found {
ctx.set_score(section, i, Some(score));
match found {
Some((section, i)) => {
ctx.set_score(section, i, Some(score));
let nodes: &[crate::agent::context::AstNode] = match section {
Section::Identity => ctx.identity(),
Section::Conversation => ctx.conversation(),
_ => &[],
};
let read_back = match nodes.get(i) {
Some(crate::agent::context::AstNode::Leaf(l)) => match l.body() {
crate::agent::context::NodeBody::Memory { score, .. } => format!("{:?}", score),
_ => "not-memory".to_string(),
},
_ => "out-of-bounds".to_string(),
};
dbglog!("[scoring] persisted {} → {:.3} ({:?}[{}]) read_back={}",
key, score, section, i, read_back);
}
None => {
dbglog!(
"[scoring] DROP {}: find_memory_by_key None (id={}, cv={})",
key, ctx.identity().len(), ctx.conversation().len()
);
}
}
let snapshot = collect_memory_scores(&ctx);
let in_snapshot = snapshot.contains_key(&key);
dbglog!("[scoring] snapshot size={} contains({})={}",
snapshot.len(), key, in_snapshot);
drop(ctx);
agent.state.lock().await.changed.notify_one();
snapshot
};
dbglog!("[scoring] about to save {} entries", scores_snapshot.len());
save_memory_scores(&scores_snapshot, &path);
}
},