feat(executor): let provably zero-cost invocations pass the budget pre-flight #151
No reviewers
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
#150 Budget pre-flight gate (accumulated >= limit) blocks provably zero-cost runs when max_cost_usd=0.0
cleveragents/cleveractors-core
Reference
cleveragents/cleveractors-core!151
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feature/m3-budget-gate-zero-cost-carveout"
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?
Summary
A fresh execution starts at
accumulated = 0, so amax_cost_usdlimit of0.0(the embedding-platform wallet capmin(platform_cap, wallet.available_balance)on an empty wallet) tripped the>=pre-flight gate before the first node — for every run, regardless of its cost. This blocked runs whose every LLM invocation is provably free: an all-0/0-rate pricing entry (the growing real-world class:big-pickle,glm-5-free, …), the exact case cleveragents-webapp had to work around with a platform-side static zero-cost estimator (cleveragents/cleveragents-webapp#473/#531).The gate now resolves the upcoming invocation's
(provider, model)rates through the exact ladder the gate charges by (lifted from_compute_costintocore.exceptions._resolve_pricing_rates, with a new conservative_is_zero_rated_invocationpredicate) and lets a provably free invocation — both resolved rates exactly0.0— pass the tripped gate. Everything unresolvable stays blocked: missing/incomplete/invalid entries, half-zero entries, non-AGENT nodes, non-LLM agents, unresolved agent names, empty pricing tables (AC5). PR #80 semantics are preserved: the post-node/post-invocation checks still use>, an exactly-at-limit node still completes, and only the next payable node/invocation is refused (AC6).What changed
PureLangGraph._check_budget_pre_flight— per node, with the conservative taxonomy helperPureLangGraph._upcoming_node_invocation_is_zero_rated(serves both execute and stream via the shared_node_budget_context). A provably-free node is logged at INFO and allowed.LLMAgent._check_budget_before_invoke— tool-loop pre-invoke, viacurrent_pricing+self.provider/self.model(correct at all five call sites; each precedesself.chat_model) — giving it consistent semantics with the per-node pre-flight (AC3)._should_skip_pruningis deliberately unchanged (ADR-2038 D-3: the carve-out does not resurrect extra pruning calls).runtime_dispatch._check_single_llm_budget_pre_flighton both singletype: llmdispatch functions (AC4): a payable upstream invocation is refused before any agent creation/call; in_execute_llm_streamit raises inside the early-validation wrapper so the billing-integrityexecutor.last_resultplaceholder is still populated._execute_llmpreviously had nomax_cost_usdenforcement at all — it now mirrors the stream/graph paths with the post-invocation>cost check, the bool/non-numeric limit ladder, and the ADR-2029 "non-empty pricing table ⇒ entry mandatory" validation, so the same config no longer succeeds or fails depending on which executor shape it lands in.docs/adr/ADR-2038-budget-gate-zero-cost-carveout.md(D-1..D-6 + rejected alternatives A-1..A-5, continuing the ADR-2029/PR-#80 lineage) and aCHANGELOG.md[Unreleased] entry. Nopyproject.tomlbump: no release tag exists and v2.1.0 is still unreleased (analysis recorded on the issue); consumers re-pin by commit SHA.Tests (written first — confirmed red against the pre-change tree)
features/execution_limits.feature); tool-loop path incl. pruning-skip and no-pricing-table locks (features/llm_agent_tool_loop.feature); new single-LLM executor suite with invocation-counting mock models asserting no upstream invocation on a payable zero-wallet run (features/budget_gate_single_llm.feature). Two stream scenarios whose enforcement point moved from post-stream to pre-flight are updated so both gates keep dedicated coverage (features/execute_stream.feature).create_executoractors (graph execute, single-LLM execute + stream, tool-loop round;robot/budget_gate_zero_cost.robot).Quality gates (final tree)
nox -s format/lintnox -s typecheck(pyright strict)nox -s security_scan(+dead_code)nox -s unit_testsnox -s integration_testsnox -s coverage_reportAcceptance criteria check
max_cost_usd: 0.0(BDD + Robot).budget_exhaustedbefore any upstream invocation._check_budget_before_invokeshares the per-node pre-flight semantics.>=/>split preserved (exactly-at-limit completes; next payable blocked).Closes #150
Design record:
docs/adr/ADR-2038-budget-gate-zero-cost-carveout.md· Issue journal: full TDD/decision/coverage notes on #150.Base branch — retargeted to
develop(2026-10-20)The PR originally targeted
master(the only long-lived branch when the feature branch was cut; decision recorded on #150). Per the updated project policy — new features land ondevelop— the branch was rebased and the PR retargeted:origin/develop(9631ee6, virtual-provider docs): new head4a9818c(previous17b2e1c; the old base was14d2132, master's tip and the merge base with develop). Still a single atomic commit — same message,ISSUES CLOSED: #150footer preserved, no merge commits.CHANGELOG.md[Unreleased] › Added— develop's #155 (backends/strategy) entry kept first, then this PR's #150 entry (newest-first). Thellm.pyauto-merge was verified sound: develop's virtual-provider wiring and this PR's carve-out touch disjoint regions.format(ruff, clean) ·lint(ruff) ·typecheck(pyright strict) ·security_scan(bandit/semgrep) ·dead_code(vulture) ·complexity— all pass.unit_tests: 156 features, 3174 scenarios, 0 failed (includes develop's virtual-provider suite).integration_tests: 374 tests, 374 passed, 0 failed.coverage_report: 97.2% (97.156% raw; threshold 96.5%, ≥97% standard met).build: wheel OK.17b2e1c35b4a9818cbdfSelf-QA Review — ✅ Approve
PR: #151 · Ticket: #150 · Reviewed head:
4a9818c· Base: develop (9631ee6)Verdict: Approve — 0 critical / 0 major / 3 minor / 2 nits. The change faithfully implements the ticket's zero-cost carve-out at all three pre-flight enforcement points, preserves PR #80's >=/> split (AC6), and closes the single-LLM enforcement gaps (AC4).
Verified in depth:
_resolve_pricing_ratesextraction (lifted from_compute_cost) is a pure, behavior-preserving refactor — same ladder, same errors/coercion;exceptions.pyat 100% line coverage._check_budget_before_invokecall sites precedeself.chat_model;_should_skip_pruningdeliberately unchanged (ADR-2038 D-3).executor.last_resultplaceholder stays populated._execute_llmnow mirrors the stream/graph post-invocation semantics (bool/non-numeric limit ladder, ADR-2029 entry validation).ISSUES CLOSED: #150footer, no merge commits.Quality gates — run locally (CI is currently broken; CI status was not used):
* Observation (pre-existing, not this PR):
pyrightconfig.jsonsetstypeCheckingMode: "off", which overridesstrictinpyproject.toml— worth a separate issue.Non-blocking follow-ups (nothing blocks merge):
_node_budget_context; low risk).ADR-2038-*already exists on develop (virtual-provider-load-balanced-backend-routing); resolve before v2.1.0 is tagged._is_zero_rated_invocationannotation should acceptdict | None(runtime-safe today).Self-QA loop: iteration 1/5 — approved on the first cycle; no fix cycle was required.