#![allow(dead_code)]
use std::hash::{Hash, Hasher};
use std::time::{Duration, Instant};
const MAX_TRACKED_MESSAGES: usize = 10;
const REPETITION_WINDOW_SECS: u64 = 60;
const REPETITION_THRESHOLD: usize = 3;
#[derive(Debug, Clone, Eq)]
pub struct MessageHash {
pub content_hash: u64,
pub agent_name: String,
pub timestamp: Instant,
}
impl PartialEq for MessageHash {
fn eq(&self, other: &Self) -> bool {
self.content_hash == other.content_hash && self.agent_name == other.agent_name
}
}
impl Hash for MessageHash {
fn hash<H: Hasher>(&self, state: &mut H) {
self.content_hash.hash(state);
self.agent_name.hash(state);
}
}
#[derive(Debug, Clone)]
pub struct CorrelationTracker {
pub correlation_id: String,
messages: Vec<MessageHash>,
created_at: Instant,
}
impl CorrelationTracker {
pub fn new(correlation_id: String) -> Self {
Self {
correlation_id,
messages: Vec::new(),
created_at: Instant::now(),
}
}
pub fn add(&mut self, content_hash: u64, agent_name: String) {
let window = Duration::from_secs(REPETITION_WINDOW_SECS);
self.messages.retain(|m| m.timestamp.elapsed() < window);
self.messages.push(MessageHash {
content_hash,
agent_name,
timestamp: Instant::now(),
});
if self.messages.len() > MAX_TRACKED_MESSAGES {
self.messages.remove(0);
}
}
pub fn is_repetitive(&self, content_hash: u64, agent_name: &str) -> bool {
let window = Duration::from_secs(REPETITION_WINDOW_SECS);
self.messages
.iter()
.filter(|m| m.agent_name == agent_name && m.timestamp.elapsed() < window)
.filter(|m| m.content_hash == content_hash)
.count()
>= REPETITION_THRESHOLD
}
#[allow(dead_code)]
pub fn repetition_count(&self, content_hash: u64, agent_name: &str) -> usize {
let window = Duration::from_secs(REPETITION_WINDOW_SECS);
self.messages
.iter()
.filter(|m| m.agent_name == agent_name && m.timestamp.elapsed() < window)
.filter(|m| m.content_hash == content_hash)
.count()
}
}
#[derive(Debug, Clone)]
pub struct LoopGuard {
trackers: Vec<CorrelationTracker>,
self_name: String,
}
impl LoopGuard {
pub fn new(self_name: String) -> Self {
Self {
trackers: Vec::new(),
self_name,
}
}
pub fn hash_content(content: &str) -> u64 {
use std::collections::hash_map::DefaultHasher;
let mut hasher = DefaultHasher::new();
content.hash(&mut hasher);
hasher.finish()
}
pub fn should_block(&self, correlation_id: &str, content: &str) -> bool {
if let Some(tracker) = self
.trackers
.iter()
.find(|t| t.correlation_id == correlation_id)
{
let hash = Self::hash_content(content);
tracker.is_repetitive(hash, &self.self_name)
} else {
false
}
}
pub fn record(&mut self, correlation_id: &str, content: &str) {
let hash = Self::hash_content(content);
if let Some(tracker) = self
.trackers
.iter_mut()
.find(|t| t.correlation_id == correlation_id)
{
tracker.add(hash, self.self_name.clone());
} else {
let mut tracker = CorrelationTracker::new(correlation_id.to_string());
tracker.add(hash, self.self_name.clone());
self.trackers.push(tracker);
}
self.trackers
.retain(|t| t.created_at.elapsed() < Duration::from_secs(REPETITION_WINDOW_SECS * 2));
}
pub fn get_block_notice(&self, correlation_id: &str, content: &str) -> Option<String> {
if self.should_block(correlation_id, content) {
Some(format!(
"Blocked self-repetition in correlation {}",
correlation_id
))
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hash_content_deterministic() {
let h1 = LoopGuard::hash_content("hello world");
let h2 = LoopGuard::hash_content("hello world");
assert_eq!(h1, h2);
}
#[test]
fn test_hash_content_different() {
let h1 = LoopGuard::hash_content("hello world");
let h2 = LoopGuard::hash_content("hello world!");
assert_ne!(h1, h2);
}
#[test]
fn test_loop_guard_blocks_repetition() {
let mut guard = LoopGuard::new("test_agent".to_string());
let correlation_id = "corr1";
let content = "same message";
assert!(!guard.should_block(correlation_id, content));
guard.record(correlation_id, content);
guard.record(correlation_id, content);
guard.record(correlation_id, content);
assert!(guard.should_block(correlation_id, content));
}
#[test]
fn test_loop_guard_different_correlation() {
let mut guard = LoopGuard::new("test_agent".to_string());
let content = "same message";
guard.record("corr1", content);
guard.record("corr1", content);
guard.record("corr1", content);
assert!(!guard.should_block("corr2", content));
}
}