lean-ctx 3.9.16

Context Runtime for AI Agents with CCP. 71 MCP tools, 10 read modes, 95+ compression patterns, cross-session memory (CCP), persistent AI knowledge with temporal facts + contradiction detection, multi-agent context sharing, LITM-aware positioning, AAAK compact format, adaptive compression with Thompson Sampling bandits. Supports 24+ AI tools. Reduces LLM token consumption by up to 99%.
Documentation
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

const DEFAULT_MAX_HOPS: usize = 5;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RelayHop {
    pub agent_id: String,
    pub received_at: DateTime<Utc>,
    pub forwarded_at: Option<DateTime<Utc>>,
    pub processing_ms: Option<u64>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RelayChain {
    pub hops: Vec<RelayHop>,
    pub max_hops: usize,
}

impl RelayChain {
    pub fn new(max_hops: usize) -> Self {
        Self {
            hops: Vec::new(),
            max_hops,
        }
    }

    pub fn add_hop(&mut self, hop: RelayHop) -> Result<(), RelayError> {
        if self.hops.len() >= self.max_hops {
            return Err(RelayError::MaxHopsExceeded(self.max_hops));
        }
        if self.contains_agent(&hop.agent_id) {
            return Err(RelayError::CycleDetected(hop.agent_id));
        }

        self.hops.push(hop);
        Ok(())
    }

    pub fn depth(&self) -> usize {
        self.hops.len()
    }

    pub fn total_latency_ms(&self) -> u64 {
        self.hops
            .iter()
            .filter_map(|hop| hop.processing_ms)
            .fold(0, u64::saturating_add)
    }

    pub fn contains_agent(&self, agent_id: &str) -> bool {
        self.hops.iter().any(|hop| hop.agent_id == agent_id)
    }

    pub fn origin(&self) -> Option<&str> {
        self.hops.first().map(|hop| hop.agent_id.as_str())
    }
}

impl Default for RelayChain {
    fn default() -> Self {
        Self::new(DEFAULT_MAX_HOPS)
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, thiserror::Error)]
pub enum RelayError {
    #[error("relay chain exceeded maximum of {0} hops")]
    MaxHopsExceeded(usize),
    #[error("relay cycle detected at agent {0}")]
    CycleDetected(String),
}

#[cfg(test)]
mod tests {
    use chrono::{TimeZone, Utc};

    use super::{RelayChain, RelayError, RelayHop};

    fn hop(agent_id: &str, processing_ms: Option<u64>) -> RelayHop {
        RelayHop {
            agent_id: agent_id.to_owned(),
            received_at: Utc.timestamp_millis_opt(1_700_000_000_000).unwrap(),
            forwarded_at: None,
            processing_ms,
        }
    }

    #[test]
    fn adds_hop_and_reports_chain_metadata() {
        let mut chain = RelayChain::new(3);

        chain.add_hop(hop("origin-agent", Some(4))).unwrap();

        assert_eq!(chain.depth(), 1);
        assert!(chain.contains_agent("origin-agent"));
        assert_eq!(chain.origin(), Some("origin-agent"));
    }

    #[test]
    fn rejects_cycle() {
        let mut chain = RelayChain::new(3);
        chain.add_hop(hop("relay-agent", None)).unwrap();

        let error = chain.add_hop(hop("relay-agent", Some(1))).unwrap_err();

        assert_eq!(error, RelayError::CycleDetected("relay-agent".to_owned()));
        assert_eq!(chain.depth(), 1);
    }

    #[test]
    fn rejects_hop_beyond_maximum() {
        let mut chain = RelayChain::new(1);
        chain.add_hop(hop("origin-agent", None)).unwrap();

        let error = chain.add_hop(hop("next-agent", None)).unwrap_err();

        assert_eq!(error, RelayError::MaxHopsExceeded(1));
        assert_eq!(chain.depth(), 1);
    }

    #[test]
    fn sums_available_processing_latency() {
        let mut chain = RelayChain::new(3);
        chain.add_hop(hop("origin-agent", Some(12))).unwrap();
        chain.add_hop(hop("relay-agent", None)).unwrap();
        chain.add_hop(hop("recipient-agent", Some(8))).unwrap();

        assert_eq!(chain.total_latency_ms(), 20);
    }

    #[test]
    fn default_chain_allows_five_hops() {
        let mut chain = RelayChain::default();
        for index in 0..5 {
            chain
                .add_hop(hop(&format!("agent-{index}"), Some(u64::MAX)))
                .unwrap();
        }

        assert_eq!(chain.depth(), 5);
        assert_eq!(chain.total_latency_ms(), u64::MAX);
        assert_eq!(
            chain.add_hop(hop("sixth-agent", None)),
            Err(RelayError::MaxHopsExceeded(5))
        );
    }
}