Skip to main content

deepstrike_core/mm/
value.rs

1//! Shared, deterministic value vocabulary for context residency and durable-memory retention.
2//!
3//! The score deliberately uses fixed-point integer terms. Policy decisions therefore replay
4//! byte-for-byte without consulting a wall clock or relying on platform floating-point behavior.
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum RetentionKind {
8    User,
9    Feedback,
10    Project,
11    Reference,
12    Skill,
13    Artifact,
14    Other,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub struct RetentionFeatures {
19    pub pinned: bool,
20    pub use_count: u64,
21    pub last_used_step: Option<u64>,
22    pub current_step: u64,
23    pub lease_remaining_steps: Option<u64>,
24    pub kind: RetentionKind,
25    pub tokens: u32,
26    /// Optional confidence in parts-per-million. Knowledge entries use zero; durable memories use
27    /// their stored confidence when M3 evaluates retention with this same vocabulary.
28    pub confidence_ppm: u32,
29    /// TTL/staleness penalty in parts-per-million, supplied by the caller's lifecycle policy.
30    pub stale_discount_ppm: u32,
31}
32
33/// Higher scores are retained first. `i64::MAX` is reserved for explicit pins.
34pub fn deterministic_retention_score(features: RetentionFeatures) -> i64 {
35    if features.pinned {
36        return i64::MAX;
37    }
38
39    // Integer log2(1 + n) is a stable ln(1+n) proxy. One observed use intentionally outweighs
40    // pure insertion recency: useful old context must beat fresh but irrelevant context.
41    let usage_bucket = if features.use_count == 0 {
42        0
43    } else {
44        64 - features.use_count.saturating_add(1).leading_zeros() as i64
45    };
46    let usage = usage_bucket.saturating_mul(8_192);
47
48    let recency = features.last_used_step.map_or(0, |last| {
49        let age = features.current_step.saturating_sub(last);
50        (4_096_u64 / age.saturating_add(1).max(1)) as i64
51    });
52    let lease = features.lease_remaining_steps.unwrap_or(0).min(1_024) as i64 * 8;
53    let kind = match features.kind {
54        RetentionKind::User => 1_600,
55        RetentionKind::Feedback => 1_800,
56        RetentionKind::Project => 1_400,
57        RetentionKind::Reference => 1_200,
58        RetentionKind::Skill => 3_000,
59        RetentionKind::Artifact => 1_700,
60        RetentionKind::Other => 0,
61    };
62    let confidence = i64::from(features.confidence_ppm.min(1_000_000)) / 250;
63    let staleness = i64::from(features.stale_discount_ppm.min(1_000_000)) / 125;
64    let size = i64::from(features.tokens).saturating_mul(4);
65
66    usage
67        .saturating_add(recency)
68        .saturating_add(lease)
69        .saturating_add(kind)
70        .saturating_add(confidence)
71        .saturating_sub(staleness)
72        .saturating_sub(size)
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    fn features() -> RetentionFeatures {
80        RetentionFeatures {
81            pinned: false,
82            use_count: 0,
83            last_used_step: None,
84            current_step: 10,
85            lease_remaining_steps: None,
86            kind: RetentionKind::Reference,
87            tokens: 100,
88            confidence_ppm: 0,
89            stale_discount_ppm: 0,
90        }
91    }
92
93    #[test]
94    fn one_real_use_beats_unreferenced_recency() {
95        let mut used = features();
96        used.use_count = 1;
97        used.last_used_step = Some(1);
98        let mut fresh = features();
99        fresh.last_used_step = Some(10);
100        assert!(deterministic_retention_score(used) > deterministic_retention_score(fresh));
101    }
102
103    #[test]
104    fn pins_are_absolute_and_staleness_or_size_reduce_value() {
105        let base = deterministic_retention_score(features());
106        let mut worse = features();
107        worse.tokens = 500;
108        worse.stale_discount_ppm = 500_000;
109        assert!(deterministic_retention_score(worse) < base);
110        let mut pinned = worse;
111        pinned.pinned = true;
112        assert_eq!(deterministic_retention_score(pinned), i64::MAX);
113    }
114}