diff --git a/src/dashboard/app.py b/src/dashboard/app.py index 5701ea2a..a66098a6 100644 --- a/src/dashboard/app.py +++ b/src/dashboard/app.py @@ -377,73 +377,78 @@ def _startup_background_tasks() -> list[asyncio.Task]: ] +def _try_prune(label: str, prune_fn, days: int) -> None: + """Run a prune function, log results, swallow errors.""" + try: + pruned = prune_fn() + if pruned: + logger.info( + "%s auto-prune: removed %d entries older than %d days", + label, + pruned, + days, + ) + except Exception as exc: + logger.debug("%s auto-prune skipped: %s", label, exc) + + +def _check_vault_size() -> None: + """Warn if the memory vault exceeds the configured size limit.""" + try: + vault_path = Path(settings.repo_root) / "memory" / "notes" + if vault_path.exists(): + total_bytes = sum(f.stat().st_size for f in vault_path.rglob("*") if f.is_file()) + total_mb = total_bytes / (1024 * 1024) + if total_mb > settings.memory_vault_max_mb: + logger.warning( + "Memory vault (%.1f MB) exceeds limit (%d MB) — consider archiving old notes", + total_mb, + settings.memory_vault_max_mb, + ) + except Exception as exc: + logger.debug("Vault size check skipped: %s", exc) + + def _startup_pruning() -> None: """Auto-prune old memories, thoughts, and events on startup.""" if settings.memory_prune_days > 0: - try: - from timmy.memory_system import prune_memories + from timmy.memory_system import prune_memories - pruned = prune_memories( + _try_prune( + "Memory", + lambda: prune_memories( older_than_days=settings.memory_prune_days, keep_facts=settings.memory_prune_keep_facts, - ) - if pruned: - logger.info( - "Memory auto-prune: removed %d entries older than %d days", - pruned, - settings.memory_prune_days, - ) - except Exception as exc: - logger.debug("Memory auto-prune skipped: %s", exc) + ), + settings.memory_prune_days, + ) if settings.thoughts_prune_days > 0: - try: - from timmy.thinking import thinking_engine + from timmy.thinking import thinking_engine - pruned = thinking_engine.prune_old_thoughts( + _try_prune( + "Thought", + lambda: thinking_engine.prune_old_thoughts( keep_days=settings.thoughts_prune_days, keep_min=settings.thoughts_prune_keep_min, - ) - if pruned: - logger.info( - "Thought auto-prune: removed %d entries older than %d days", - pruned, - settings.thoughts_prune_days, - ) - except Exception as exc: - logger.debug("Thought auto-prune skipped: %s", exc) + ), + settings.thoughts_prune_days, + ) if settings.events_prune_days > 0: - try: - from swarm.event_log import prune_old_events + from swarm.event_log import prune_old_events - pruned = prune_old_events( + _try_prune( + "Event", + lambda: prune_old_events( keep_days=settings.events_prune_days, keep_min=settings.events_prune_keep_min, - ) - if pruned: - logger.info( - "Event auto-prune: removed %d entries older than %d days", - pruned, - settings.events_prune_days, - ) - except Exception as exc: - logger.debug("Event auto-prune skipped: %s", exc) + ), + settings.events_prune_days, + ) if settings.memory_vault_max_mb > 0: - try: - vault_path = Path(settings.repo_root) / "memory" / "notes" - if vault_path.exists(): - total_bytes = sum(f.stat().st_size for f in vault_path.rglob("*") if f.is_file()) - total_mb = total_bytes / (1024 * 1024) - if total_mb > settings.memory_vault_max_mb: - logger.warning( - "Memory vault (%.1f MB) exceeds limit (%d MB) — consider archiving old notes", - total_mb, - settings.memory_vault_max_mb, - ) - except Exception as exc: - logger.debug("Vault size check skipped: %s", exc) + _check_vault_size() async def _shutdown_cleanup( diff --git a/src/infrastructure/events/bus.py b/src/infrastructure/events/bus.py index f9e81f2f..beb0fa30 100644 --- a/src/infrastructure/events/bus.py +++ b/src/infrastructure/events/bus.py @@ -64,7 +64,7 @@ class EventBus: @bus.subscribe("agent.task.*") async def handle_task(event: Event): - logger.debug(f"Task event: {event.data}") + logger.debug("Task event: %s", event.data) await bus.publish(Event( type="agent.task.assigned", diff --git a/src/infrastructure/router/cascade.py b/src/infrastructure/router/cascade.py index fae8afe6..e9ec675e 100644 --- a/src/infrastructure/router/cascade.py +++ b/src/infrastructure/router/cascade.py @@ -221,65 +221,56 @@ class CascadeRouter: raise RuntimeError("PyYAML not installed") content = self.config_path.read_text() - # Expand environment variables content = self._expand_env_vars(content) data = yaml.safe_load(content) - # Load cascade settings - cascade = data.get("cascade", {}) - - # Load fallback chains - fallback_chains = data.get("fallback_chains", {}) - - # Load multi-modal settings - multimodal = data.get("multimodal", {}) - - self.config = RouterConfig( - timeout_seconds=cascade.get("timeout_seconds", 30), - max_retries_per_provider=cascade.get("max_retries_per_provider", 2), - retry_delay_seconds=cascade.get("retry_delay_seconds", 1), - circuit_breaker_failure_threshold=cascade.get("circuit_breaker", {}).get( - "failure_threshold", 5 - ), - circuit_breaker_recovery_timeout=cascade.get("circuit_breaker", {}).get( - "recovery_timeout", 60 - ), - circuit_breaker_half_open_max_calls=cascade.get("circuit_breaker", {}).get( - "half_open_max_calls", 2 - ), - auto_pull_models=multimodal.get("auto_pull", True), - fallback_chains=fallback_chains, - ) - - # Load providers - for p_data in data.get("providers", []): - # Skip disabled providers - if not p_data.get("enabled", False): - continue - - provider = Provider( - name=p_data["name"], - type=p_data["type"], - enabled=p_data.get("enabled", True), - priority=p_data.get("priority", 99), - url=p_data.get("url"), - api_key=p_data.get("api_key"), - base_url=p_data.get("base_url"), - models=p_data.get("models", []), - ) - - # Check if provider is actually available - if self._check_provider_available(provider): - self.providers.append(provider) - else: - logger.warning("Provider %s not available, skipping", provider.name) - - # Sort by priority - self.providers.sort(key=lambda p: p.priority) + self.config = self._parse_router_config(data) + self._load_providers(data) except Exception as exc: logger.error("Failed to load config: %s", exc) + def _parse_router_config(self, data: dict) -> RouterConfig: + """Build a RouterConfig from parsed YAML data.""" + cascade = data.get("cascade", {}) + cb = cascade.get("circuit_breaker", {}) + multimodal = data.get("multimodal", {}) + + return RouterConfig( + timeout_seconds=cascade.get("timeout_seconds", 30), + max_retries_per_provider=cascade.get("max_retries_per_provider", 2), + retry_delay_seconds=cascade.get("retry_delay_seconds", 1), + circuit_breaker_failure_threshold=cb.get("failure_threshold", 5), + circuit_breaker_recovery_timeout=cb.get("recovery_timeout", 60), + circuit_breaker_half_open_max_calls=cb.get("half_open_max_calls", 2), + auto_pull_models=multimodal.get("auto_pull", True), + fallback_chains=data.get("fallback_chains", {}), + ) + + def _load_providers(self, data: dict) -> None: + """Load, filter, and sort providers from parsed YAML data.""" + for p_data in data.get("providers", []): + if not p_data.get("enabled", False): + continue + + provider = Provider( + name=p_data["name"], + type=p_data["type"], + enabled=p_data.get("enabled", True), + priority=p_data.get("priority", 99), + url=p_data.get("url"), + api_key=p_data.get("api_key"), + base_url=p_data.get("base_url"), + models=p_data.get("models", []), + ) + + if self._check_provider_available(provider): + self.providers.append(provider) + else: + logger.warning("Provider %s not available, skipping", provider.name) + + self.providers.sort(key=lambda p: p.priority) + def _expand_env_vars(self, content: str) -> str: """Expand ${VAR} syntax in YAML content.