Skip to main content

lean_ctx/core/
memory_salience.rs

1//! Content salience for memory: a cheap, deterministic keyword score over a
2//! fact/finding's raw text.
3//!
4//! It answers "how much signal does this string carry?" without any model call,
5//! and feeds two consumers:
6//! - the cognition loop's auto-promotion gate (only promote findings above a
7//!   floor), and
8//! - the write-time admission floor (#970) that keeps low-signal facts out of a
9//!   capped store.
10//!
11//! Pure and total so it never perturbs output determinism (#498): the same text
12//! always scores the same value, independent of process state.
13
14/// Boost table: substrings that mark a finding as high-signal (errors, security,
15/// failures). A base score of [`BASE`] is always granted so a plain, valid fact
16/// is never zero — the floor (when enabled) is what decides admission.
17const BASE: u32 = 20;
18const BOOSTS: &[(&str, u32)] = &[
19    ("error", 25),
20    ("failed", 25),
21    ("panic", 30),
22    ("assert", 20),
23    ("forbidden", 25),
24    ("timeout", 20),
25    ("deadlock", 25),
26    ("security", 25),
27    ("vuln", 25),
28    ("e0", 15), // Rust error codes often start with E0xxx.
29];
30
31/// Score the salience of a piece of memory text. Always `>= BASE`.
32#[must_use]
33pub fn text_salience(text: &str) -> u32 {
34    let s = text.to_lowercase();
35    let mut score = BASE;
36    for (pat, b) in BOOSTS {
37        if s.contains(pat) {
38            score = score.saturating_add(*b);
39        }
40    }
41    score
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47
48    #[test]
49    fn plain_text_gets_base_only() {
50        assert_eq!(text_salience("the database is postgres"), BASE);
51    }
52
53    #[test]
54    fn high_signal_terms_boost_above_base() {
55        assert!(text_salience("auth failed with a security error") > BASE);
56        // Each distinct boost term stacks.
57        assert!(
58            text_salience("panic deadlock timeout") > text_salience("timeout"),
59            "multiple boosts must accumulate"
60        );
61    }
62
63    #[test]
64    fn is_deterministic_and_case_insensitive() {
65        assert_eq!(
66            text_salience("SECURITY VULN"),
67            text_salience("security vuln")
68        );
69        assert_eq!(text_salience("x"), text_salience("x"));
70    }
71}