"""Configuration for loop detection middleware.""" from pydantic import BaseModel, Field, model_validator class ToolFreqOverride(BaseModel): """Per-tool frequency threshold override. Can be higher or lower than the global defaults. Commonly used to raise thresholds for high-frequency tools like bash in batch workflows (e.g. RNA-seq pipelines) without weakening protection on every other tool. """ warn: int = Field(ge=1) hard_limit: int = Field(ge=1) @model_validator(mode="after") def _validate(self) -> "ToolFreqOverride": if self.hard_limit < self.warn: raise ValueError("hard_limit must be >= warn") return self class LoopDetectionConfig(BaseModel): """Configuration for repetitive tool-call loop detection.""" enabled: bool = Field( default=True, description="Whether to enable repetitive tool-call loop detection", ) warn_threshold: int = Field( default=3, ge=1, description="Number of identical tool-call sets before injecting a warning", ) hard_limit: int = Field( default=5, ge=1, description="Number of identical tool-call sets before forcing a stop", ) window_size: int = Field( default=20, ge=1, description="Number of recent tool-call sets to track per thread", ) max_tracked_threads: int = Field( default=100, ge=1, description="Maximum number of thread histories to keep in memory", ) tool_freq_warn: int = Field( default=30, ge=1, description="Number of calls to the same tool type before injecting a frequency warning", ) tool_freq_hard_limit: int = Field( default=50, ge=1, description="Number of calls to the same tool type before forcing a stop", ) tool_freq_overrides: dict[str, ToolFreqOverride] = Field( default_factory=dict, description=("Per-tool overrides for tool_freq_warn / tool_freq_hard_limit, keyed by tool name. Values can be higher or lower than the global defaults. Commonly used to raise thresholds for high-frequency tools like bash."), ) @model_validator(mode="after") def validate_thresholds(self) -> "LoopDetectionConfig": """Ensure hard stop cannot happen before the warning threshold.""" if self.hard_limit < self.warn_threshold: raise ValueError("hard_limit must be greater than or equal to warn_threshold") if self.tool_freq_hard_limit < self.tool_freq_warn: raise ValueError("tool_freq_hard_limit must be greater than or equal to tool_freq_warn") return self