"""Agent behavior policies and rules.""" import logging from datetime import datetime, timedelta logger = logging.getLogger(__name__) class ChatActivityPolicy: """Policy for detecting chat activity and inactivity periods.""" def __init__(self, inactivity_threshold_minutes: int = 15): """ Initialize policy. Args: inactivity_threshold_minutes: Minutes of no chat before Hearthkeeper activates """ self.inactivity_threshold = timedelta(minutes=inactivity_threshold_minutes) self.last_message_time: dict[str, datetime] = {} def record_activity(self, session_id: str, occurred_at: datetime | None = None) -> None: """Record that chat activity occurred.""" self.last_message_time[session_id] = occurred_at or datetime.utcnow() def last_activity_at(self, session_id: str) -> datetime | None: """Get the most recent chat activity time for a session.""" return self.last_message_time.get(session_id) def minutes_since_activity(self, session_id: str) -> int: """Get minutes since last chat message.""" if session_id not in self.last_message_time: return 0 elapsed = datetime.utcnow() - self.last_message_time[session_id] return int(elapsed.total_seconds() / 60) def should_hearthkeeper_prompt(self, session_id: str) -> bool: """Determine if Hearthkeeper should send a prompt.""" minutes = self.minutes_since_activity(session_id) should = minutes >= self.inactivity_threshold.total_seconds() / 60 if should: logger.info(f"Chat inactive for {minutes} minutes. Hearthkeeper may prompt.") return should class ResponseSuppression: """Policy for when the agent should NOT respond.""" # Suppress responses when chat is very active (humans are talking) ACTIVE_CHAT_THRESHOLD = 5 # 5+ messages per minute = suppress @staticmethod def should_suppress_response(recent_message_count: int, time_window_minutes: int = 1) -> bool: """ Determine if agent should stay silent due to active chat. Args: recent_message_count: Number of messages in the time window time_window_minutes: Time window in minutes Returns: True if agent should suppress response """ messages_per_minute = recent_message_count / time_window_minutes suppress = messages_per_minute >= ResponseSuppression.ACTIVE_CHAT_THRESHOLD if suppress: logger.debug(f"Response suppressed due to active chat ({messages_per_minute:.1f} msg/min)") return suppress class SuspiciousContentPolicy: """Policy for detecting suspicious content.""" # Patterns that raise Warden alerts SUSPICIOUS_KEYWORDS = [ "join our discord", "discord.gg", "grow your channel", "easy money", "limited offer", ] @staticmethod def is_suspicious(message: str) -> bool: """ Check if a message matches suspicious patterns. Args: message: Message content Returns: True if message is suspicious """ message_lower = message.lower() for keyword in SuspiciousContentPolicy.SUSPICIOUS_KEYWORDS: if keyword in message_lower: logger.warning(f"Suspicious content detected: {keyword}") return True # Check for multiple links link_count = message.count("http") + message.count("www") if link_count > 1: logger.warning("Suspicious content detected: multiple links") return True return False