deepstrike_core/mm/
value.rs1#[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 pub confidence_ppm: u32,
29 pub stale_discount_ppm: u32,
31}
32
33pub fn deterministic_retention_score(features: RetentionFeatures) -> i64 {
35 if features.pinned {
36 return i64::MAX;
37 }
38
39 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}