Skip to content

Commit 187f8cc

Browse files
fix: bound breach history + CostGuard thread safety (#253)
- RingBreachDetector: change _breach_history from unbounded list to deque(maxlen=10_000) with configurable max_breach_history (#247) - CostGuard: add threading.Lock protecting _org_spent_month read-modify- write in check_task() and record_cost() (#246) Closes #247, closes #246 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 6f37c49 commit 187f8cc

File tree

2 files changed

+15
-10
lines changed

2 files changed

+15
-10
lines changed

packages/agent-hypervisor/src/hypervisor/rings/breach_detector.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ def __init__(
8484
window_seconds: int = 60,
8585
baseline_rate: float = 10.0,
8686
max_events_per_agent: int = 1_000,
87+
max_breach_history: int = 10_000,
8788
) -> None:
8889
self.window_seconds = window_seconds
8990
self.baseline_rate = baseline_rate
@@ -93,8 +94,8 @@ def __init__(
9394
self._call_windows: dict[str, deque[float]] = {}
9495
# Per-agent circuit-breaker flag
9596
self._tripped: dict[str, bool] = {}
96-
# Global breach history
97-
self._breach_history: list[BreachEvent] = []
97+
# Global breach history (bounded)
98+
self._breach_history: deque[BreachEvent] = deque(maxlen=max_breach_history)
9899

99100
# ------------------------------------------------------------------
100101
# Public API

packages/agent-sre/src/agent_sre/cost/guard.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from __future__ import annotations
77

88
import math
9+
import threading
910
import time
1011
from collections import deque
1112
from dataclasses import dataclass, field
@@ -146,6 +147,7 @@ def __init__(
146147
self._alerts: list[CostAlert] = []
147148
self._cost_history: deque[float] = deque(maxlen=1000)
148149
self._org_spent_month: float = 0.0
150+
self._lock = threading.Lock()
149151

150152
def get_budget(self, agent_id: str) -> AgentBudget:
151153
"""Get or create budget for an agent."""
@@ -177,11 +179,12 @@ def check_task(self, agent_id: str, estimated_cost: float = 0.0) -> tuple[bool,
177179
return False, f"Would exceed daily budget (${budget.remaining_today_usd:.2f} remaining)"
178180

179181
if self.org_monthly_budget > 0:
180-
if self._org_spent_month + estimated_cost > self.org_monthly_budget:
181-
return False, (
182-
f"Would exceed org monthly budget "
183-
f"(${self.org_remaining_month:.2f} remaining)"
184-
)
182+
with self._lock:
183+
if self._org_spent_month + estimated_cost > self.org_monthly_budget:
184+
return False, (
185+
f"Would exceed org monthly budget "
186+
f"(${self.org_remaining_month:.2f} remaining)"
187+
)
185188

186189
return True, "ok"
187190

@@ -208,9 +211,10 @@ def record_cost(
208211
self._cost_history.append(cost_usd)
209212

210213
# Update budget
211-
budget.spent_today_usd += cost_usd
212-
budget.task_count_today += 1
213-
self._org_spent_month += cost_usd
214+
with self._lock:
215+
budget.spent_today_usd += cost_usd
216+
budget.task_count_today += 1
217+
self._org_spent_month += cost_usd
214218

215219
# Per-task limit check
216220
if cost_usd > self.per_task_limit:

0 commit comments

Comments
 (0)