Skip to main content

lean_ctx/core/context_ledger/
reinjection.rs

1use super::helpers::GWT_IGNITION_Z;
2use super::types::{ContextLedger, LedgerEntry};
3
4#[derive(Debug, Clone)]
5pub struct ReinjectionAction {
6    pub path: String,
7    pub current_mode: String,
8    pub new_mode: String,
9    pub tokens_freed: usize,
10}
11
12#[derive(Debug, Clone)]
13pub struct ReinjectionPlan {
14    pub actions: Vec<ReinjectionAction>,
15    pub total_tokens_freed: usize,
16    pub new_utilization: f64,
17}
18
19impl ContextLedger {
20    pub fn reinjection_plan(
21        &self,
22        intent: &super::super::intent_engine::StructuredIntent,
23        target_utilization: f64,
24    ) -> ReinjectionPlan {
25        let current_util = self.total_tokens_sent as f64 / self.window_size as f64;
26        if current_util <= target_utilization {
27            return ReinjectionPlan {
28                actions: Vec::new(),
29                total_tokens_freed: 0,
30                new_utilization: current_util,
31            };
32        }
33
34        let tokens_to_free =
35            self.total_tokens_sent - (self.window_size as f64 * target_utilization) as usize;
36
37        let target_set: std::collections::HashSet<&str> = intent
38            .targets
39            .iter()
40            .map(std::string::String::as_str)
41            .collect();
42
43        let mut candidates: Vec<(usize, &LedgerEntry)> = self
44            .entries
45            .iter()
46            .enumerate()
47            .filter(|(_, e)| !target_set.iter().any(|t| e.path.contains(t)))
48            .collect();
49
50        candidates.sort_by(|a, b| {
51            let a_phi = a.1.phi.unwrap_or(0.0);
52            let b_phi = b.1.phi.unwrap_or(0.0);
53            a_phi
54                .partial_cmp(&b_phi)
55                .unwrap_or_else(|| a.1.timestamp.cmp(&b.1.timestamp))
56        });
57
58        let mut actions = Vec::new();
59        let mut freed = 0usize;
60
61        for (_, entry) in &candidates {
62            if freed >= tokens_to_free {
63                break;
64            }
65            if let Some((new_mode, new_tokens)) = downgrade_mode(&entry.mode, entry.sent_tokens) {
66                let saving = entry.sent_tokens.saturating_sub(new_tokens);
67                if saving > 0 {
68                    actions.push(ReinjectionAction {
69                        path: entry.path.clone(),
70                        current_mode: entry.mode.clone(),
71                        new_mode,
72                        tokens_freed: saving,
73                    });
74                    freed += saving;
75                }
76            }
77        }
78
79        let new_sent = self.total_tokens_sent.saturating_sub(freed);
80        let new_utilization = new_sent as f64 / self.window_size as f64;
81
82        ReinjectionPlan {
83            actions,
84            total_tokens_freed: freed,
85            new_utilization,
86        }
87    }
88}
89
90pub(super) fn downgrade_mode(current_mode: &str, current_tokens: usize) -> Option<(String, usize)> {
91    match current_mode {
92        "full" => Some(("signatures".to_string(), current_tokens / 5)),
93        "aggressive" => Some(("signatures".to_string(), current_tokens / 3)),
94        "signatures" => Some(("map".to_string(), current_tokens / 2)),
95        "map" => Some(("reference".to_string(), current_tokens / 4)),
96        _ => None,
97    }
98}
99
100/// Resolve the Global-Workspace ignition z-score threshold (#6): the
101/// `LEAN_CTX_GWT_IGNITION_Z` env override (must be > 0) wins, else the default
102/// [`GWT_IGNITION_Z`]. Deterministic for a given environment.
103pub(super) fn ignition_z_threshold() -> f64 {
104    std::env::var("LEAN_CTX_GWT_IGNITION_Z")
105        .ok()
106        .and_then(|v| v.trim().parse::<f64>().ok())
107        .filter(|v| *v > 0.0)
108        .unwrap_or(GWT_IGNITION_Z)
109}
110
111impl Default for ContextLedger {
112    fn default() -> Self {
113        Self::new()
114    }
115}