Skip to main content

deepstrike_core/context/
snapshot.rs

1use super::sections::{ContextSectionRegistry, SectionCachePolicy};
2use crate::types::capability::CapabilityManifest;
3use crate::types::message::Message;
4use crate::context::task_state::TaskState;
5use serde::{Deserialize, Serialize};
6
7/// A single page of context memory.
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct ContextPage {
10    pub id: String,
11    pub content: String,
12    pub token_count: u32,
13}
14
15/// Frozen snapshot of all active context partitions at a given turn.
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct ContextSnapshot {
18    pub turn: u32,
19    pub system_messages: Vec<Message>,
20    pub knowledge_messages: Vec<Message>,
21    pub history_messages: Vec<Message>,
22    pub task_state: TaskState,
23}
24
25/// Reference to an archived context segment in external storage.
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct ContextArchiveRef {
28    pub seq: u64,
29    pub archive_ref: String,
30    pub summary: String,
31    pub token_count: u32,
32}
33
34/// Garbage collection and pressure policy ratios.
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct ContextGcPolicy {
37    pub target_tokens_ratio: f64,
38    pub auto_compact_ratio: f64,
39}
40
41/// Errors/Faults that can occur during Context VM execution or replay.
42#[derive(Debug, Clone, thiserror::Error, Serialize, Deserialize)]
43pub enum ContextFault {
44    #[error("Prompt exceeds maximum token limit: budget={budget}, actual={actual}")]
45    PromptTooLong { budget: u32, actual: u32 },
46    #[error("Missing archive chunk {seq} for session {session_id}")]
47    MissingArchive { session_id: String, seq: u64 },
48    #[error("Invalid replay at turn {turn}: {reason}")]
49    InvalidReplay { turn: u32, reason: String },
50}
51
52
53/// Provider-cache hint derived from pure kernel metadata.
54///
55/// SDKs can use these stable fingerprints to decide whether provider prompt
56/// cache boundaries are still reusable. The kernel intentionally emits only
57/// hashes and section ids; concrete cache reads/writes remain in the SDK layer.
58#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
59pub struct ContextSnapshotHint {
60    pub static_prefix_hash: u64,
61    pub section_plan_hash: u64,
62    pub capability_manifest_hash: u64,
63    pub static_section_ids: Vec<String>,
64}
65
66impl ContextSnapshotHint {
67    pub fn from_parts(
68        sections: &ContextSectionRegistry,
69        capabilities: &CapabilityManifest,
70    ) -> Self {
71        let plan = sections.plan();
72        let mut section_plan_material = String::new();
73        for id in &plan.ids {
74            section_plan_material.push_str(id.as_str());
75            section_plan_material.push('\n');
76        }
77
78        let mut static_section_ids = Vec::new();
79        let mut static_prefix_material = String::new();
80        for section in sections.sections() {
81            if !section.enabled || section.cache_policy != SectionCachePolicy::Static {
82                continue;
83            }
84            static_section_ids.push(section.id.to_string());
85            static_prefix_material.push_str(section.id.as_str());
86            static_prefix_material.push('|');
87            static_prefix_material.push_str(&section.priority.to_string());
88            static_prefix_material.push('|');
89            static_prefix_material.push_str(&format!("{:?}", section.partition));
90            static_prefix_material.push('\n');
91        }
92
93        Self {
94            static_prefix_hash: stable_hash(static_prefix_material.as_bytes()),
95            section_plan_hash: stable_hash(section_plan_material.as_bytes()),
96            capability_manifest_hash: stable_hash(capabilities.format_inventory().as_bytes()),
97            static_section_ids,
98        }
99    }
100}
101
102/// FNV-1a 64-bit. The kernel's one stable, dependency-free content hash — shared
103/// by snapshot hints and the render-layer [`super::renderer::PrefixFingerprint`] so
104/// both speak the same fingerprint dialect.
105pub(crate) fn stable_hash(bytes: &[u8]) -> u64 {
106    let mut hash = 0xcbf29ce484222325u64;
107    for byte in bytes {
108        hash ^= u64::from(*byte);
109        hash = hash.wrapping_mul(0x100000001b3);
110    }
111    hash
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117    use crate::context::sections::{
118        ContextSection, ContextSectionPartition, ContextSectionRegistry, SectionCachePolicy,
119    };
120    use crate::types::capability::{CapabilityKind, CapabilityManifest};
121
122    #[test]
123    fn dynamic_sections_do_not_change_static_prefix_hash() {
124        let mut base = ContextSectionRegistry::new();
125        base.upsert(
126            ContextSection::new("system.base", ContextSectionPartition::System, 100)
127                .with_cache_policy(SectionCachePolicy::Static),
128        );
129
130        let mut changed = base.clone();
131        changed.upsert(ContextSection::new(
132            "history.rolling",
133            ContextSectionPartition::History,
134            10,
135        ));
136
137        let manifest = CapabilityManifest::new();
138        let base_hint = ContextSnapshotHint::from_parts(&base, &manifest);
139        let changed_hint = ContextSnapshotHint::from_parts(&changed, &manifest);
140
141        assert_eq!(
142            base_hint.static_prefix_hash,
143            changed_hint.static_prefix_hash
144        );
145        assert_ne!(base_hint.section_plan_hash, changed_hint.section_plan_hash);
146    }
147
148    #[test]
149    fn capability_changes_alter_manifest_hash() {
150        let sections = ContextSectionRegistry::default_agent_sections();
151        let mut before = CapabilityManifest::new();
152        let mut after = CapabilityManifest::new();
153        after.add_marker(CapabilityKind::Agent, "verify", "verification agent");
154
155        assert_ne!(
156            ContextSnapshotHint::from_parts(&sections, &before).capability_manifest_hash,
157            ContextSnapshotHint::from_parts(&sections, &after).capability_manifest_hash
158        );
159
160        before.add_marker(CapabilityKind::Agent, "verify", "verification agent");
161        assert_eq!(
162            ContextSnapshotHint::from_parts(&sections, &before).capability_manifest_hash,
163            ContextSnapshotHint::from_parts(&sections, &after).capability_manifest_hash
164        );
165    }
166}