"""Configuration and safety limits for the sentiment agent. All guardrails are centralized here so they can be tuned from one place or overridden via CLI flags / env vars. """ from __future__ import annotations import os from dataclasses import dataclass, field @dataclass(frozen=True) class RateLimitConfig: """Per-platform rate limiting.""" requests_per_minute: int = 10 burst_size: int = 3 # max concurrent requests cooldown_after_429: float = 30.0 # seconds to wait after a 429 @dataclass(frozen=True) class SafetyConfig: """Top-level safety rails for the agent.""" # --- Agent-level limits --- max_turns: int = 20 max_budget_usd: float = 0.50 # hard cap on Claude API spend per run max_total_api_calls: int = 50 # across ALL platforms combined max_results_per_call: int = 50 # cap the `limit` param sent to any API # --- Per-platform rate limits --- bluesky_rate: RateLimitConfig = field(default_factory=lambda: RateLimitConfig( requests_per_minute=10, burst_size=2, )) reddit_rate: RateLimitConfig = field(default_factory=lambda: RateLimitConfig( requests_per_minute=10, burst_size=2, )) hackernews_rate: RateLimitConfig = field(default_factory=lambda: RateLimitConfig( requests_per_minute=15, burst_size=3, # HN Algolia is more generous )) # --- Content size limits --- max_post_text_chars: int = 2000 # truncate individual posts beyond this max_total_content_bytes: int = 500_000 # ~500KB total data gathered before agent stops # --- Timeout --- api_timeout_seconds: float = 15.0 # --- Credibility thresholds --- min_credibility_score: float = 0.3 # posts below this are flagged/excluded flag_bot_threshold: float = 0.5 # posts between min and this are flagged but included @classmethod def from_env(cls) -> SafetyConfig: """Build config with env var overrides. Env vars: SENTIMENT_MAX_TURNS, SENTIMENT_MAX_BUDGET_USD, SENTIMENT_MAX_API_CALLS, SENTIMENT_MIN_CREDIBILITY. """ kwargs: dict = {} if v := os.environ.get("SENTIMENT_MAX_TURNS"): kwargs["max_turns"] = int(v) if v := os.environ.get("SENTIMENT_MAX_BUDGET_USD"): kwargs["max_budget_usd"] = float(v) if v := os.environ.get("SENTIMENT_MAX_API_CALLS"): kwargs["max_total_api_calls"] = int(v) if v := os.environ.get("SENTIMENT_MIN_CREDIBILITY"): kwargs["min_credibility_score"] = float(v) return cls(**kwargs)