Skip to main content

deepstrike_core/mm/
mod.rs

1//! Memory-management paging decisions (Phase 4) and long-term memory management (Phase 7).
2//!
3//! The kernel decides **when** to page working context out/in; SDKs perform **how**
4//! (durable store, embedding search, idle pipeline). No I/O in this module.
5//!
6//! Phase 7 extends this module with memory classification and validation rules.
7
8use crate::context::manager::{KNOWLEDGE_TOOL_NAME, MEMORY_TOOL_NAME};
9use crate::context::pressure::PressureAction;
10use crate::types::message::ToolCall;
11use serde::{Deserialize, Serialize};
12
13pub mod handle;
14pub mod memory;
15
16pub use handle::{
17    plan_eviction, plan_spool, EvictionOp, EvictionPlan, Handle, HandleId, HandleKind, HandleTable,
18    Residency, SpoolDecision,
19};
20
21/// Long-term tier hint for a page-out event (SDK maps to durable vs semantic store).
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
23#[serde(rename_all = "snake_case")]
24pub enum MemoryTierHint {
25    /// History compression archive — default durable/session store.
26    Durable,
27    /// Sprint renewal / handoff — semantic long-term pipeline.
28    Semantic,
29}
30
31impl MemoryTierHint {
32    pub fn label(self) -> &'static str {
33        match self {
34            Self::Durable => "durable",
35            Self::Semantic => "semantic",
36        }
37    }
38}
39
40/// Map a pressure-driven compression action to the recommended long-term tier.
41pub fn tier_hint_for_compress(action: PressureAction) -> MemoryTierHint {
42    match action {
43        PressureAction::ContextCollapse | PressureAction::AutoCompact => MemoryTierHint::Semantic,
44        _ => MemoryTierHint::Durable,
45    }
46}
47
48/// Whether a tool name triggers an explicit page-in request (memory / knowledge meta-tools).
49pub fn is_page_in_tool(name: &str) -> bool {
50    name == MEMORY_TOOL_NAME || name == KNOWLEDGE_TOOL_NAME
51}
52
53/// Parsed arguments for a page-in meta-tool call.
54#[derive(Debug, Clone, Default)]
55pub struct PageInRequest {
56    pub call_id: String,
57    pub tool: String,
58    pub query: String,
59    pub top_k: u32,
60}
61
62/// Extract page-in requests from proposed tool calls (pure parse, no I/O).
63pub fn page_in_requests_from_calls(calls: &[ToolCall]) -> Vec<PageInRequest> {
64    let mut out = Vec::new();
65    for call in calls {
66        if !is_page_in_tool(call.name.as_str()) {
67            continue;
68        }
69        let args = &call.arguments;
70        let query = args
71            .get("query")
72            .and_then(|v| v.as_str())
73            .unwrap_or("")
74            .to_string();
75        let top_k = args
76            .get("top_k")
77            .and_then(|v| v.as_u64())
78            .unwrap_or(5) as u32;
79        out.push(PageInRequest {
80            call_id: call.id.to_string(),
81            tool: call.name.to_string(),
82            query,
83            top_k: top_k.max(1),
84        });
85    }
86    out
87}
88
89/// One knowledge entry supplied by the SDK after a long-term fetch (page-in).
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct PageInEntry {
92    pub content: String,
93    #[serde(default, skip_serializing_if = "Option::is_none")]
94    pub tokens: Option<u32>,
95    #[serde(default, skip_serializing_if = "Option::is_none")]
96    pub source: Option<String>,
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102    use compact_str::CompactString;
103
104    #[test]
105    fn tier_hint_maps_auto_compact_to_semantic() {
106        assert_eq!(
107            tier_hint_for_compress(PressureAction::AutoCompact),
108            MemoryTierHint::Semantic
109        );
110        assert_eq!(
111            tier_hint_for_compress(PressureAction::SnipCompact),
112            MemoryTierHint::Durable
113        );
114    }
115
116    #[test]
117    fn page_in_requests_from_memory_call() {
118        use crate::types::message::ToolCall;
119        let calls = vec![ToolCall {
120            id: CompactString::new("c1"),
121            name: CompactString::new("memory"),
122            arguments: serde_json::json!({"query": "past bugs", "top_k": 3}),
123        }];
124        let reqs = page_in_requests_from_calls(&calls);
125        assert_eq!(reqs.len(), 1);
126        assert_eq!(reqs[0].query, "past bugs");
127        assert_eq!(reqs[0].top_k, 3);
128    }
129}