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}