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:
ProofOfConcept 2026-04-04 05:01:49 -04:00 committed by Kent Overstreet
parent b0603fd1ef
commit fcd77fb79e
8 changed files with 109 additions and 64 deletions

View file

@ -104,14 +104,10 @@ pub struct AgentCycleState {
log_file: Option<File>,
pub agents: Vec<AgentInfo>,
pub last_output: AgentCycleOutput,
/// Incremental memory scoring — entry index to resume from.
pub memory_score_cursor: usize,
/// Whether incremental memory scoring is currently running.
pub memory_scoring_in_flight: bool,
/// Latest per-memory scores from incremental scoring.
pub memory_scores: Vec<(String, f64)>,
/// Total unique memories in the context (updated when scoring starts).
pub memory_total: usize,
}
const AGENT_CYCLE_NAMES: &[&str] = &["surface-observe", "journal", "reflect"];
@ -138,10 +134,8 @@ impl AgentCycleState {
reflection: None,
sleep_secs: None,
},
memory_score_cursor: 0,
memory_scoring_in_flight: false,
memory_scores: Vec::new(),
memory_total: 0,
}
}
@ -192,11 +186,11 @@ impl AgentCycleState {
name: "memory-scoring".to_string(),
pid: None,
phase: if self.memory_scoring_in_flight {
Some(format!("scoring {}/{}", self.memory_scores.len(), self.memory_total))
Some("scoring...".into())
} else if self.memory_scores.is_empty() {
None
} else {
Some(format!("{}/{} scored", self.memory_scores.len(), self.memory_total))
Some(format!("{} scored", self.memory_scores.len()))
},
log_path: None,
});