Restore full N×M memory scoring matrix (/score command)

The full matrix scorer was deleted during the AST conversion. Restore
it: /score runs score_memories() which computes divergence for every
memory × response pair, stores the MemoryScore on MindState, and
displays per-memory weights with bar charts on the F2 screen.

Both scoring paths now use ActivityGuard::update() for live progress
in the status bar instead of creating a new activity per iteration.

Also bumps score API timeout from 120s to 300s and adds progress
logging throughout.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
ProofOfConcept 2026-04-09 22:19:02 -04:00 committed by Kent Overstreet
parent f6a6c37435
commit 58cec97e57
6 changed files with 187 additions and 98 deletions

View file

@ -93,7 +93,14 @@ impl<'de> Deserialize<'de> for NodeLeaf {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AstNode {
Leaf(NodeLeaf),
Branch { role: Role, children: Vec<AstNode> },
Branch {
role: Role,
children: Vec<AstNode>,
/// Per-response memory attribution from full scoring matrix.
/// Maps memory key → divergence score for this response.
#[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
memory_scores: std::collections::BTreeMap<String, f64>,
},
}
/// The context window: four sections as Vec<AstNode>.
@ -277,13 +284,14 @@ impl AstNode {
// -- Branch constructors --------------------------------------------------
pub fn branch(role: Role, children: Vec<AstNode>) -> Self {
Self::Branch { role, children }
Self::Branch { role, children, memory_scores: Default::default() }
}
pub fn system_msg(text: impl Into<String>) -> Self {
Self::Branch {
role: Role::System,
children: vec![Self::content(text)],
memory_scores: Default::default(),
}
}
@ -291,6 +299,7 @@ impl AstNode {
Self::Branch {
role: Role::User,
children: vec![Self::content(text)],
memory_scores: Default::default(),
}
}
@ -306,9 +315,10 @@ impl AstNode {
};
Self::Leaf(NodeLeaf { token_ids, ..leaf })
}
Self::Branch { role, children } => Self::Branch {
Self::Branch { role, children, memory_scores, .. } => Self::Branch {
role,
children: children.into_iter().map(|c| c.retokenize()).collect(),
memory_scores,
},
}
}
@ -339,7 +349,7 @@ impl AstNode {
pub fn label(&self) -> String {
let cfg = crate::config::get();
match self {
Self::Branch { role, children } => {
Self::Branch { role, children, .. } => {
let preview = children.first()
.and_then(|c| c.leaf())
.map(|l| truncate_preview(l.body.text(), 60))
@ -370,7 +380,7 @@ impl AstNode {
fn render_into(&self, out: &mut String) {
match self {
Self::Leaf(leaf) => leaf.body.render_into(out),
Self::Branch { role, children } => {
Self::Branch { role, children, .. } => {
out.push_str(&format!("<|im_start|>{}\n", role.as_str()));
for child in children {
child.render_into(out);
@ -383,7 +393,7 @@ impl AstNode {
fn token_ids_into(&self, out: &mut Vec<u32>) {
match self {
Self::Leaf(leaf) => out.extend_from_slice(&leaf.token_ids),
Self::Branch { role, children } => {
Self::Branch { role, children, .. } => {
out.push(tokenizer::IM_START);
out.extend(tokenizer::encode(&format!("{}\n", role.as_str())));
for child in children {
@ -412,7 +422,7 @@ impl Ast for AstNode {
fn tokens(&self) -> usize {
match self {
Self::Leaf(leaf) => leaf.tokens(),
Self::Branch { role, children } => {
Self::Branch { role, children, .. } => {
1 + tokenizer::encode(&format!("{}\n", role.as_str())).len()
+ children.iter().map(|c| c.tokens()).sum::<usize>()
+ 1 + tokenizer::encode("\n").len()
@ -752,6 +762,7 @@ impl ContextState {
pub fn identity(&self) -> &[AstNode] { &self.identity }
pub fn journal(&self) -> &[AstNode] { &self.journal }
pub fn conversation(&self) -> &[AstNode] { &self.conversation }
pub fn conversation_mut(&mut self) -> &mut Vec<AstNode> { &mut self.conversation }
fn sections(&self) -> [&Vec<AstNode>; 4] {
[&self.system, &self.identity, &self.journal, &self.conversation]