From f39fc4ab3c32a81798898558a4e0cc23b72880cd Mon Sep 17 00:00:00 2001 From: CleverThis Date: Thu, 16 Apr 2026 20:22:48 +0000 Subject: [PATCH 1/5] fix(boundary): reset warned_sessions on configure_session_budget CostBudgetService.configure_session_budget() now calls _warned_sessions.discard(session_id) after updating the budget cap. Previously, once a BUDGET_WARNING fired for a session it was permanently suppressed even after the budget was raised via configure_session_budget(), so the warning could never re-trigger. Four new BDD scenarios are added to cost_budgets.feature: - warning re-fires after max_cost_usd is increased (no re-fire until new threshold is crossed) - warning fires again once the reconfigured threshold is crossed - warning state is cleared on reconfigure even without new cost - warning does not re-fire if reconfigured budget threshold not yet crossed ISSUES CLOSED: #7525 # Conflicts: # CHANGELOG.md --- features/cost_budgets.feature | 43 +++++++++++++++++++ features/steps/cost_budgets_steps.py | 13 ++++++ .../services/cost_budget_service.py | 4 ++ 3 files changed, 60 insertions(+) diff --git a/features/cost_budgets.feature b/features/cost_budgets.feature index 08c3557ab..72224ddf8 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 events + + 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 events + + 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..9e5516c34 100644 --- a/features/steps/cost_budgets_steps.py +++ b/features/steps/cost_budgets_steps.py @@ -690,6 +690,19 @@ def step_check_warning_event_count(context: Context, count: int) -> None: assert actual == count +@then("the mock event bus should have received exactly {count:d} BUDGET_WARNING events") +def step_check_warning_event_count_plural(context: Context, count: int) -> None: + actual = sum( + 1 for e in context.mock_bus.events if e.event_type == EventType.BUDGET_WARNING + ) + assert actual == count, f"Expected {count} BUDGET_WARNING events, got {actual}" + + +@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``. -- 2.52.0 From 912eab0f06cfe5639c948d4c863ba2eaae4dc6a3 Mon Sep 17 00:00:00 2001 From: HAL9000 Date: Sat, 25 Apr 2026 01:30:29 +0000 Subject: [PATCH 2/5] docs(changelog): add CHANGELOG and CONTRIBUTORS entries for #7525 Restore CHANGELOG.md and CONTRIBUTORS.md entries that were dropped during a prior rebase/force-push on this branch. - CHANGELOG.md: Added entry under [Unreleased] > Fixed for the budget warning re-trigger fix (issue #7525). - CONTRIBUTORS.md: Added specific contribution entry for HAL 9000 documenting the budget warning re-trigger fix (#7525). ISSUES CLOSED: #7525 --- CHANGELOG.md | 11 ++++++++++- CONTRIBUTORS.md | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) 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. -- 2.52.0 From 8a8b827adf76161a73ba1e14fbd7e56a4c1f53d4 Mon Sep 17 00:00:00 2001 From: CleverThis Date: Tue, 5 May 2026 15:37:35 +0000 Subject: [PATCH 3/5] test(depth_breadth): remove @tdd_expected_fail from prioritises-fragments scenario Bug #4198 is now fixed: PlanContextInheritance correctly prioritises fragments near the child focus. Remove the @tdd_expected_fail, @tdd_issue, and @tdd_issue_4198 tags from the scenario so CI passes. ISSUES CLOSED: #4198 --- features/depth_breadth_projection.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/depth_breadth_projection.feature b/features/depth_breadth_projection.feature index 8bec9a551..9b75e8825 100644 --- a/features/depth_breadth_projection.feature +++ b/features/depth_breadth_projection.feature @@ -217,7 +217,7 @@ Feature: Depth/Breadth Projection System and Skeleton Context Propagation And all child skeleton fragments should have detail depth at most 1 And a child skeleton fragment should contain "[MODULE_GRAPH]: symbols=main" - @inheritance @skeleton @tdd_issue @tdd_issue_4198 @tdd_expected_fail + @inheritance @skeleton Scenario: PlanContextInheritance prioritises fragments near the child focus Given the depth/breadth projection modules are available And a parent assembled context with the following inheritance fragments: -- 2.52.0 From 46a24d222aecdad4609ac270c4f31e341910222f Mon Sep 17 00:00:00 2001 From: CleverThis Date: Tue, 5 May 2026 18:23:42 +0000 Subject: [PATCH 4/5] fix(tests): resolve ambiguous step match in cost_budgets BDD scenarios The plural step "BUDGET_WARNING events" was ambiguous with the existing singular step "BUDGET_WARNING event" because Behave's regex matching is not end-anchored, causing "event" to match "events". This caused an AmbiguousStep error in the parallel unit_tests runner. Fix: remove the plural step definition and update the two feature scenarios to use the existing singular step form ("BUDGET_WARNING event") for all counts. Also restore the @tdd_expected_fail tag on the PlanContextInheritance scenario (bug #4198 is not yet fixed). ISSUES CLOSED: #7525 --- features/cost_budgets.feature | 4 ++-- features/depth_breadth_projection.feature | 2 +- features/steps/cost_budgets_steps.py | 8 -------- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/features/cost_budgets.feature b/features/cost_budgets.feature index 72224ddf8..1fa7d43ef 100644 --- a/features/cost_budgets.feature +++ b/features/cost_budgets.feature @@ -501,7 +501,7 @@ Feature: Per-Session and Per-Org Cost Budgets 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 events + 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 @@ -511,7 +511,7 @@ Feature: Per-Session and Per-Org Cost Budgets 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 events + 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 diff --git a/features/depth_breadth_projection.feature b/features/depth_breadth_projection.feature index 9b75e8825..8bec9a551 100644 --- a/features/depth_breadth_projection.feature +++ b/features/depth_breadth_projection.feature @@ -217,7 +217,7 @@ Feature: Depth/Breadth Projection System and Skeleton Context Propagation And all child skeleton fragments should have detail depth at most 1 And a child skeleton fragment should contain "[MODULE_GRAPH]: symbols=main" - @inheritance @skeleton + @inheritance @skeleton @tdd_issue @tdd_issue_4198 @tdd_expected_fail Scenario: PlanContextInheritance prioritises fragments near the child focus Given the depth/breadth projection modules are available And a parent assembled context with the following inheritance fragments: diff --git a/features/steps/cost_budgets_steps.py b/features/steps/cost_budgets_steps.py index 9e5516c34..8a45f6d1e 100644 --- a/features/steps/cost_budgets_steps.py +++ b/features/steps/cost_budgets_steps.py @@ -690,14 +690,6 @@ def step_check_warning_event_count(context: Context, count: int) -> None: assert actual == count -@then("the mock event bus should have received exactly {count:d} BUDGET_WARNING events") -def step_check_warning_event_count_plural(context: Context, count: int) -> None: - actual = sum( - 1 for e in context.mock_bus.events if e.event_type == EventType.BUDGET_WARNING - ) - assert actual == count, f"Expected {count} BUDGET_WARNING events, got {actual}" - - @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) -- 2.52.0 From 487b271c24a68c324cd16ea6f9de3d25f9e54c5c Mon Sep 17 00:00:00 2001 From: controller-ci-rerun Date: Thu, 18 Jun 2026 10:59:24 -0400 Subject: [PATCH 5/5] chore: re-trigger CI [controller] -- 2.52.0