diff --git a/CHANGELOG.md b/CHANGELOG.md index 99894f63c..989b3cb28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -906,6 +906,15 @@ uko-oo:Class` triple emission in `PythonAnalyzer._extract_class()` so that limit. 19 BDD scenarios cover all v3 paths including tool actors, update mode, LSP dict bindings, and field propagation. +- **Budget Warning Re-trigger After Session Reconfiguration** (#7525): Fixed a bug + where `CostBudgetService._check_warning()` would never re-fire after a session's + budget was reconfigured via `configure_session_budget()`. The root cause was that + `configure_session_budget()` did not clear `_warned_sessions` when updating + `max_cost_usd`, so the session remained permanently suppressed. Fix: added + `self._warned_sessions.discard(session_id)` inside the `with self._lock:` block + at the end of `configure_session_budget()`, so warning state is reset on every + reconfiguration. + - **TDD Non-AssertionError Guard Visibility** (#8294): `apply_tdd_inversion` in `features/environment.py` now emits its non-assertion exception guard warning to both the structured logger and `stderr` via a new `_warning_with_stderr` helper. @@ -1407,4 +1416,4 @@ iteration` and data corruption under concurrent plan execution. All public - **TUI -- Permission Question Widget**: A new inline `PermissionQuestionWidget` renders permission requests directly in the conversation stream for single-key operations. Users can allow/reject with single-key shortcuts (`a`/`A`/`r`/`R`), - navigate with arrow keys, confirm with `Enter`, or press `v` to open the full \ No newline at end of file + navigate with arrow keys, confirm with `Enter`, or press `v` to open the full diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 5c06947aa..9d8520ed5 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -139,3 +139,4 @@ Below are some specific details of individual PR contributions. * HAL 9000 has contributed the plan tree JSON/YAML spec-compliant envelope fix (issue #11041): wrapped `agents plan tree` JSON and YAML output in the spec-required command envelope (`command`, `status`, `exit_code`, `data`, `timing`, `messages`), updated BDD step definitions to validate envelope structure, and removed the `@tdd_expected_fail` tag from the previously-failing JSON tree format test (issue #4254). * HAL 9000 has contributed the a2a session_id validation fix (PR #11098 / issue #9250): moved the session_id validation guard to the top of `_handle_session_close()` in `A2aLocalFacade`, closing the validation bypass path where empty or null session IDs could slip through to devcontainer cleanup when `SessionService` was not wired. * HAL 9000 has contributed ACMS budget enforcement for per-file and cumulative size constraints (PR #9673 / issue #9583): implemented ``BudgetEnforcer``, ``BudgetViolation``, and ``ContextFile`` dataclasses in ``src/cleveragents/acms/budget_enforcement.py`` with full type annotations, ruff linting compliance, 11 BDD Behave scenarios, Robot Framework integration tests, and per-file exclusion + cumulative budget cutoff strategies for max_file_size and max_total_size limits. +* HAL 9000 has contributed the budget warning re-trigger fix (#7525): added `_warned_sessions.discard(session_id)` inside the lock in `configure_session_budget()` so budget warnings can re-fire after session reconfiguration. diff --git a/features/cost_budgets.feature b/features/cost_budgets.feature index 08c3557ab..1fa7d43ef 100644 --- a/features/cost_budgets.feature +++ b/features/cost_budgets.feature @@ -479,3 +479,46 @@ Feature: Per-Session and Per-Org Cost Budgets Scenario: BudgetCheckResult model is frozen When I create a budget check result allowed=true Then modifying the budget check result should raise an error + + # ---- Bug fix: warning re-triggers after budget reconfiguration (#7525) ---- + + Scenario: Budget warning re-fires after max_cost_usd is increased + Given a cost budget service with mock event bus and warning threshold 0.8 + And session "reconf1" configured with max_cost_usd 100.0 + When I record plan cost 85.0 for session "reconf1" + And I check budget hierarchy for session "reconf1" with plan_cost 1.0 + Then the mock event bus should have received exactly 1 BUDGET_WARNING event + When I reconfigure session "reconf1" with max_cost_usd 200.0 + And I check budget hierarchy for session "reconf1" with plan_cost 1.0 + Then the mock event bus should have received exactly 1 BUDGET_WARNING event + + Scenario: Budget warning fires again once reconfigured budget threshold is crossed + Given a cost budget service with mock event bus and warning threshold 0.8 + And session "reconf2" configured with max_cost_usd 100.0 + When I record plan cost 85.0 for session "reconf2" + And I check budget hierarchy for session "reconf2" with plan_cost 1.0 + Then the mock event bus should have received exactly 1 BUDGET_WARNING event + When I reconfigure session "reconf2" with max_cost_usd 200.0 + And I record plan cost 80.0 for session "reconf2" + And I check budget hierarchy for session "reconf2" with plan_cost 1.0 + Then the mock event bus should have received exactly 2 BUDGET_WARNING event + + Scenario: Budget warning state is cleared on reconfigure even without new cost + Given a cost budget service with mock event bus and warning threshold 0.5 + And session "reconf3" configured with max_cost_usd 100.0 + When I record plan cost 60.0 for session "reconf3" + And I check budget hierarchy for session "reconf3" with plan_cost 1.0 + Then the mock event bus should have received exactly 1 BUDGET_WARNING event + When I reconfigure session "reconf3" with max_cost_usd 80.0 + And I check budget hierarchy for session "reconf3" with plan_cost 1.0 + Then the mock event bus should have received exactly 2 BUDGET_WARNING event + + Scenario: Budget warning does not re-fire if reconfigured budget threshold not yet crossed + Given a cost budget service with mock event bus and warning threshold 0.8 + And session "reconf4" configured with max_cost_usd 100.0 + When I record plan cost 85.0 for session "reconf4" + And I check budget hierarchy for session "reconf4" with plan_cost 1.0 + Then the mock event bus should have received exactly 1 BUDGET_WARNING event + When I reconfigure session "reconf4" with max_cost_usd 1000.0 + And I check budget hierarchy for session "reconf4" with plan_cost 1.0 + Then the mock event bus should have received exactly 1 BUDGET_WARNING event diff --git a/features/steps/cost_budgets_steps.py b/features/steps/cost_budgets_steps.py index bd7fa482e..8a45f6d1e 100644 --- a/features/steps/cost_budgets_steps.py +++ b/features/steps/cost_budgets_steps.py @@ -690,6 +690,11 @@ def step_check_warning_event_count(context: Context, count: int) -> None: assert actual == count +@when('I reconfigure session "{sid}" with max_cost_usd {value:g}') +def step_reconfigure_session(context: Context, sid: str, value: float) -> None: + context.budget_service.configure_session_budget(sid, max_cost_usd=value) + + # --------------------------------------------------------------------------- # AutonomyGuardrailService integration steps # --------------------------------------------------------------------------- diff --git a/src/cleveragents/application/services/cost_budget_service.py b/src/cleveragents/application/services/cost_budget_service.py index 22b51c06d..acbc97bd1 100644 --- a/src/cleveragents/application/services/cost_budget_service.py +++ b/src/cleveragents/application/services/cost_budget_service.py @@ -106,6 +106,10 @@ class CostBudgetService: ) if org_id is not None: self._session_org[session_id] = org_id + # Reset warning state so the warning can re-fire against the new + # budget cap. Without this, a session that already triggered a + # warning would never warn again even after the budget is raised. + self._warned_sessions.discard(session_id) def get_session_budget(self, session_id: str) -> SessionCostBudget | None: """Return the budget for *session_id*, or ``None``.