Skip to main content

autonomic_core/
context.rs

1//! Context compression regulation types.
2//!
3//! `ContextRuling` is the Autonomic controller's decision about whether
4//! to compress, dilate, or hold the conversation context. It replaces
5//! hard-coded token thresholds with a regulated control signal.
6
7use serde::{Deserialize, Serialize};
8
9/// The Autonomic controller's ruling on context compression.
10///
11/// Maps to biological analogy: breathing. The context window is a lung —
12/// it fills (inspiration) and must periodically release (expiration).
13/// Autonomic regulation decides the breathing rate.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
15#[serde(rename_all = "snake_case")]
16pub enum ContextRuling {
17    /// Context pressure is low. No action needed.
18    Breathe,
19    /// Context filling but agent doing valuable work. Delay compression.
20    Dilate,
21    /// Context should be compressed. Extract memories and compact.
22    Compress,
23    /// Critical pressure. Compact immediately to avoid API errors.
24    Emergency,
25}
26
27/// Advice package returned by the `ContextCompressionRule`.
28///
29/// Contains the ruling plus the rationale signals that informed it,
30/// so the shell can log why a particular decision was made.
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct ContextCompressionAdvice {
33    /// The ruling: what the shell should do.
34    pub ruling: ContextRuling,
35    /// Context pressure that triggered evaluation (0.0..1.0).
36    pub pressure: f32,
37    /// Target token count if compression is needed.
38    pub target_tokens: Option<usize>,
39    /// Human-readable rationale for the ruling.
40    pub rationale: String,
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46
47    #[test]
48    fn context_ruling_serde_roundtrip() {
49        for ruling in [
50            ContextRuling::Breathe,
51            ContextRuling::Dilate,
52            ContextRuling::Compress,
53            ContextRuling::Emergency,
54        ] {
55            let json = serde_json::to_string(&ruling).unwrap();
56            let back: ContextRuling = serde_json::from_str(&json).unwrap();
57            assert_eq!(ruling, back);
58        }
59    }
60
61    #[test]
62    fn context_compression_advice_serde_roundtrip() {
63        let advice = ContextCompressionAdvice {
64            ruling: ContextRuling::Dilate,
65            pressure: 0.68,
66            target_tokens: None,
67            rationale: "high tool density, quality stable".into(),
68        };
69        let json = serde_json::to_string(&advice).unwrap();
70        let back: ContextCompressionAdvice = serde_json::from_str(&json).unwrap();
71        assert_eq!(back.ruling, ContextRuling::Dilate);
72        assert!((back.pressure - 0.68).abs() < f32::EPSILON);
73        assert!(back.target_tokens.is_none());
74    }
75
76    #[test]
77    fn compress_advice_has_target() {
78        let advice = ContextCompressionAdvice {
79            ruling: ContextRuling::Compress,
80            pressure: 0.75,
81            target_tokens: Some(70_000),
82            rationale: "quality degrading".into(),
83        };
84        assert_eq!(advice.target_tokens, Some(70_000));
85    }
86}