Skip to main content

pulsehive_core/
context.rs

1//! Context budget configuration for the Perceive phase.
2//!
3//! [`ContextBudget`] controls how much context an agent receives —
4//! limiting both token count and experience count to keep within
5//! LLM context window limits.
6
7use crate::lens::Lens;
8
9/// Budget constraints for context assembly.
10///
11/// Controls how many experiences and tokens are included in the agent's
12/// perceived context. The ContextOptimizer packs experiences greedily
13/// within these limits, prioritizing higher-scored items.
14#[derive(Debug, Clone)]
15pub struct ContextBudget {
16    /// Maximum estimated tokens for context (rough: chars/4).
17    pub max_tokens: u32,
18    /// Maximum number of experiences to include.
19    pub max_experiences: usize,
20    /// Maximum number of insights to include.
21    pub max_insights: usize,
22}
23
24impl ContextBudget {
25    /// Creates a budget from a Lens, using attention_budget for experience count.
26    pub fn from_lens(lens: &Lens) -> Self {
27        Self {
28            max_tokens: 4096,
29            max_experiences: lens.attention_budget,
30            max_insights: 10,
31        }
32    }
33}
34
35impl Default for ContextBudget {
36    fn default() -> Self {
37        Self {
38            max_tokens: 4096,
39            max_experiences: 50,
40            max_insights: 10,
41        }
42    }
43}
44
45/// Estimate the token count for a text string.
46///
47/// Uses the rough approximation of 1 token ≈ 4 characters for English text.
48/// Adds a small overhead for formatting.
49pub fn estimate_tokens(text: &str) -> u32 {
50    (text.len() as u32) / 4 + 20
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    #[test]
58    fn test_context_budget_default() {
59        let budget = ContextBudget::default();
60        assert_eq!(budget.max_tokens, 4096);
61        assert_eq!(budget.max_experiences, 50);
62        assert_eq!(budget.max_insights, 10);
63    }
64
65    #[test]
66    fn test_context_budget_from_lens() {
67        let lens = Lens {
68            attention_budget: 25,
69            ..Lens::default()
70        };
71        let budget = ContextBudget::from_lens(&lens);
72        assert_eq!(budget.max_experiences, 25);
73    }
74
75    #[test]
76    fn test_estimate_tokens() {
77        assert_eq!(estimate_tokens(""), 20); // Just overhead
78        assert_eq!(estimate_tokens("Hello world"), 22); // 11/4 + 20 = 22
79                                                        // 400 chars ≈ 100 tokens + 20 overhead = 120
80        let long_text = "a".repeat(400);
81        assert_eq!(estimate_tokens(&long_text), 120);
82    }
83}