training: per-node scoring with graph weight updates
Memory scoring now uses the graph as source of truth: - last_scored timestamp on each node (new capnp field @22) - Nodes scored when older than scoring_interval_secs (default 1hr) - Oldest-scored-first ordering - Window: scoring_response_window assistant responses (default 100) - First-quarter memories scored even without full window - Per-response normalization (raw divergence / response count) - Asymmetric weight update: alpha=0.5 up, alpha=0.1 down (responds fast to importance, decays slowly — memories stay surfaced even if only useful 1/4 of the time) Graph writes disabled pending normalization calibration. Also: configurable scoring_interval_secs and scoring_response_window. Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
parent
b0603fd1ef
commit
fcd77fb79e
8 changed files with 109 additions and 64 deletions
|
|
@ -296,6 +296,22 @@ impl Store {
|
|||
Ok((old, weight))
|
||||
}
|
||||
|
||||
/// Update a node's weight with a new score and record the scoring
|
||||
/// timestamp. Uses asymmetric smoothing: responds quickly to high
|
||||
/// scores (alpha=0.5) but decays slowly on low scores (alpha=0.1).
|
||||
/// This keeps memories surfaced even if they're only useful 1 in 4 times.
|
||||
/// Returns (old_weight, new_weight).
|
||||
pub fn score_weight(&mut self, key: &str, score: f64) -> Result<(f32, f32), String> {
|
||||
let node = self.nodes.get_mut(key)
|
||||
.ok_or_else(|| format!("node not found: {}", key))?;
|
||||
let old = node.weight;
|
||||
let alpha = if score > old as f64 { 0.5 } else { 0.1 };
|
||||
let new = (alpha * score + (1.0 - alpha) * old as f64) as f32;
|
||||
node.weight = new.clamp(0.01, 1.0);
|
||||
node.last_scored = chrono::Utc::now().timestamp();
|
||||
Ok((old, node.weight))
|
||||
}
|
||||
|
||||
/// Set the strength of a link between two nodes. Deduplicates if
|
||||
/// multiple links exist. Returns the old strength, or error if no link.
|
||||
pub fn set_link_strength(&mut self, source: &str, target: &str, strength: f32) -> Result<f32, String> {
|
||||
|
|
|
|||
|
|
@ -214,6 +214,10 @@ pub struct Node {
|
|||
#[serde(default)]
|
||||
pub created_at: i64,
|
||||
|
||||
// Memory importance scoring — unix epoch seconds, 0 = never scored.
|
||||
#[serde(default)]
|
||||
pub last_scored: i64,
|
||||
|
||||
// Derived fields (not in capnp, computed from graph)
|
||||
#[serde(default)]
|
||||
pub community_id: Option<u32>,
|
||||
|
|
@ -342,7 +346,7 @@ capnp_message!(Node,
|
|||
uuid: [uuid],
|
||||
prim: [version, timestamp, weight, emotion, deleted,
|
||||
retrievals, uses, wrongs, last_replayed,
|
||||
spaced_repetition_interval, position, created_at],
|
||||
spaced_repetition_interval, position, created_at, last_scored],
|
||||
enm: [node_type: NodeType],
|
||||
skip: [community_id, clustering_coefficient, degree],
|
||||
);
|
||||
|
|
@ -531,6 +535,7 @@ pub fn new_node(key: &str, content: &str) -> Node {
|
|||
spaced_repetition_interval: 1,
|
||||
position: 0,
|
||||
created_at: now_epoch(),
|
||||
last_scored: 0,
|
||||
community_id: None,
|
||||
clustering_coefficient: None,
|
||||
degree: None,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue