Enforce USD budget at each agent invocation (incl. pruning): skip pruning when tripped, fail immediately with no retries at zero/negative balance #76
Labels
No labels
auto/blocked-by-deps
auto/ci-timeout
auto/claimed-implementer
auto/claimed-merge
auto/claimed-reviewer
auto/driver-down
auto/invariant-violation
auto/last-attempt-tier-0
auto/last-attempt-tier-1
auto/last-attempt-tier-2
auto/last-attempt-tier-min
Automation Tracking
auto/needs-conflict-resolution
auto/needs-implementer
auto/postmortem
auto/ready-to-merge
auto/restart-throttled
auto/revert
auto/sentinel
auto/stale-inactivity
auto/unstable
Blocked
Bounty
$100
Bounty
$1000
Bounty
$10000
Bounty
$20
Bounty
$2000
Bounty
$250
Bounty
$50
Bounty
$500
Bounty
$5000
Bounty
$750
MoSCoW
Could have
MoSCoW
Must have
MoSCoW
Should have
Needs Feedback
Points
1
Points
13
Points
2
Points
21
Points
3
Points
34
Points
5
Points
55
Points
8
Points
88
Priority
Backlog
Priority
CI Blocker
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Signed-off: Owner
Signed-off: Scrum Master
Signed-off: Tech Lead
Spike
State
Completed
State
Duplicate
State
In Progress
State
In Review
State
Paused
State
Unverified
State
Verified
State
Wont Do
Type
Automation
Type
Bug
Type
Discussion
Type
Documentation
Type
Epic
Type
Feature
Type
Legendary
Type
Refactor
Type
Support
Type
Task
Type
Testing
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Blocks
Depends on
#77 Epic: LLM Agent Runtime Stabilization — reliability, resource enforcement & correctness hardening
cleveragents/cleveractors-core
#80 feat(graph): enforce USD budget at each agent invocation with pre-flight gate
cleveragents/cleveractors-core
Reference
cleveragents/cleveractors-core#76
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Metadata
Branch name:
bugfix/m2-76-budget-pre-flightCommit description:
fix(graph): enforce USD budget at each agent invocation with pre-flight gateSummary
Cost/credit enforcement (ADR-2029, issue #15) is currently a post-node accumulate-then-check guard living entirely in
langgraph/pure_graph.py. On each agent node the executor prices the node's reported token usage, adds it toself._accumulated_cost, and only then compares againstmax_cost_usd(main pathpure_graph.py:916-960, streaming path:1722-1750, plus the interactive path). TheExecutionError(kind="cost", reason="budget_exhausted")is re-raised and never swallowed, so the graph halts before the next node.Two gaps follow from checking only at the node boundary:
agents/llm.py:636 _run_pruning_pass→:704-708 _prune_invoke→prune_model.ainvoke). This spend happens inside the node and is only reconciled after the node returns. So once the budget is already tripped we can still burn credit on pruning passes for the remainder of that node.agents/retry.py) and only fails after incurring a full node's cost. There is no "we're already broke, don't start" check.(Note: the
token_budget_percentlogic inagents/llm.py:281-294, 833-860is a context-window cap, not a dollar cap — unrelated to credit enforcement.)Requested behavior
Enforce the USD budget at each agent invocation, including pruning calls, not just at the node boundary:
max_cost_usdhas already been tripped by_accumulated_cost._run_pruning_pass) once we are at/over budget — pruning is optimization spend and must not push us further past the ceiling. Fall back to the unpruned tool output.ExecutionError(kind="cost", reason="budget_exhausted")and no further retries — do not start the node, do not enter the retry mechanism (agents/retry.py).Acceptance criteria
ainvokeround and before each pruning pass, not only after the node completes._accumulated_costhas already crossedmax_cost_usd, no pruning pass is issued for the remainder of that node; unpruned output is used.<= 0and a main-agent invocation would start, the graph fails immediately withkind="cost",reason="budget_exhausted", and the retry path is not entered (0 retries)._execute_from_node, the streaming path (~:1670-1750), and the interactive path.$0fallback is introduced (preserve the existingmissing_pricing_entryhard-fail contract,pure_graph.py:864-915).budget_exhausted, zero retries, no node work started; (c) budget tripped mid tool-loop → no further pruning for that node.Notes / open design points
This narrows the overshoot window but does not by itself make spend strictly
<= balancewithin a single main-agent call — a single main invocation still runs to completion before its cost is reconciled. A true "never exceeds balance" guarantee still needs either a router-side safety margin onmax_cost_usdsized to worst-case single-call cost, or a pre-flight per-call cost reservation. This ticket covers the pruning-gate + zero-balance-pre-flight asks; a strict per-call reservation can be a follow-up.Refs:
langgraph/pure_graph.py:916-960,:1670-1750;agents/llm.py:636,:704-708,:747-1019;agents/retry.py. Related: ADR-2029, issue #15.