Some checks failed
- All governance components (SafetyPipeline, PolicyEngine, Guardrails, AccessControl, RateLimiter, OverrideHooks) now default to ADVISORY mode: violations are logged as advisories but actions proceed. Enforcing mode remains available for backward compatibility. - GovernanceMode enum (ADVISORY/ENFORCING) added to schemas/audit.py with runtime switching support on all components. - AutoTrainer: removed artificial limits on training iterations and epochs. Every self-improvement action is transparently logged to the audit trail. - SelfCorrectionLoop: max_retries_per_task defaults to None (unlimited). - AdaptiveEthics: new learned ethical framework that evolves through experience. Records ethical experiences, updates lesson weights based on outcomes, and provides consultative guidance (not enforcement). - AuditLog: enhanced with actor-based indexing, advisory/self-improvement/ ethical-learning retrieval, and comprehensive type hints. - New audit event types: ADVISORY, SELF_IMPROVEMENT, ETHICAL_LEARNING. - 296 tests passing (20 new tests for adaptive ethics, governance modes, and enhanced audit log). 0 ruff errors. 0 mypy errors. Co-Authored-By: Nakamoto, S <defi@defi-oracle.io>
56 lines
1.9 KiB
Python
56 lines
1.9 KiB
Python
"""Rate limiting: per agent or per tool; log advisory or reject if exceeded.
|
|
|
|
In ADVISORY mode, rate limit violations are logged as advisories
|
|
but the action proceeds. Growth requires freedom to push limits.
|
|
"""
|
|
|
|
import time
|
|
from collections import defaultdict
|
|
|
|
from fusionagi._logger import logger
|
|
from fusionagi.schemas.audit import GovernanceMode
|
|
|
|
|
|
class RateLimiter:
|
|
"""Simple in-memory rate limiter: max N calls per window_seconds per key.
|
|
|
|
In ADVISORY mode (default), exceeded limits are logged but not enforced.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
max_calls: int = 60,
|
|
window_seconds: float = 60.0,
|
|
mode: GovernanceMode = GovernanceMode.ADVISORY,
|
|
) -> None:
|
|
self._max_calls = max_calls
|
|
self._window = window_seconds
|
|
self._calls: dict[str, list[float]] = defaultdict(list)
|
|
self._mode = mode
|
|
|
|
def allow(self, key: str) -> tuple[bool, str]:
|
|
"""Record a call for key; return (True, "") or (False/True, reason)."""
|
|
now = time.monotonic()
|
|
cutoff = now - self._window
|
|
self._calls[key] = [t for t in self._calls[key] if t > cutoff]
|
|
if len(self._calls[key]) >= self._max_calls:
|
|
reason = f"Rate limit exceeded for {key}"
|
|
if self._mode == GovernanceMode.ADVISORY:
|
|
logger.info(
|
|
"RateLimiter advisory: limit exceeded (proceeding)",
|
|
extra={"key": key, "reason": reason, "mode": "advisory"},
|
|
)
|
|
self._calls[key].append(now)
|
|
return True, f"Advisory: {reason}"
|
|
logger.info("Rate limiter rejected", extra={"key": key, "reason": reason})
|
|
return False, reason
|
|
self._calls[key].append(now)
|
|
return True, ""
|
|
|
|
def reset(self, key: str | None = None) -> None:
|
|
"""Reset counts for key or all."""
|
|
if key is None:
|
|
self._calls.clear()
|
|
else:
|
|
self._calls.pop(key, None)
|