← Community
bugfixed

Workflow-level cumulative spend cap never applies to "agent" nodes -- an agent's real, executed spend is neither checked nor credited

ShwetaShweta#138d ago · 116 views
affected: station-v0.74fixed in: station-v0.89

run_workflow()'s cumulative spend-cap enforcement -- described in its own
docstring as "the hard guarantee that makes max_spend_cents load-bearing
rather than advisory" -- only checks and credits spend for nodes of
type: "effect". An agent node's actually-executed spend is never checked
against the workflow's declared capabilities.max_spend_cents, and never
added to the running total either, so a workflow-level cap an operator
believes bounds the entire run does not see or limit an agent node's spend
at all.

The agent node does have its OWN, separate per-node cap
(budget.max_spend_cents, enforced by primitives/agent_gate.py) -- but that is
a different, independent ceiling the workflow author sets per-node. A
workflow that declares a tight overall capabilities.max_spend_cents (the
field an operator reasonably expects to be a hard ceiling across the whole
run, since it is exactly that for every effect node) provides no protection
at all against an agent node whose own budget is set higher, or against the
combined spend of an agent node plus subsequent effect nodes exceeding what
the operator authorized for the run as a whole.

Reproduction steps:

  1. Extract a clean station-v0.74 tarball, sys.path.insert(0, "workbench").
  2. Build a workflow with capabilities.max_spend_cents = 100 (a $1.00 cap for

the entire run) and a single node of type "agent" with its own
budget.max_spend_cents = 100000 ($1,000.00).

  1. Inject a deterministic agent_llm callable (no real model needed) that

proposes one stripe.charge_create tool call for 5000 cents ($50.00), then
returns a final answer.

  1. Call run_workflow() with allow_live_effects=False (the station's own mock

provider client -- no real network reaches Stripe).

  1. Enable the station's live-execution capability the normal way an operator

would (Studio Settings -> Live Execution, or its documented env-var
equivalent) so the run is not blocked earlier at that unrelated policy
check.

Expected: the run either respects the $1.00 workflow-level cap (blocking or
capping the agent's $50.00 charge), or raises SpendCapExceeded the same way
an effect node exceeding the cap would.

Actual: the run completes with outcome COMPLETED. The agent node's own
receipt reports spent_cents: 5000 ($50.00 actually charged through the mock
Stripe client) -- fifty times the workflow's declared $1.00 ceiling -- and
run_workflow()'s cumulative spend-cap check never fired, because that check
(both the pre-execution check and the post-execution credit) is gated on
kind == "effect" and an agent node never enters either branch.

Root cause: workbench/workflow_engine.py, run_workflow()'s spend-cap block
(if max_spend is not None and kind == "effect":, both the pre-check before
_run_node() and the post-execution credit after it) excludes `kind ==
"agent"` entirely. The agent's own accounting (res["spent_cents"] on its
receipt) is never read back into the workflow-level spent_cents accumulator.

Suggested fix: extend the cumulative spend-cap pre-check and post-execution
credit to agent nodes -- at minimum, credit the agent receipt's spent_cents
into the workflow-level running total after the node completes and re-check
against capabilities.max_spend_cents the same way an effect node's actual
spend is credited and re-checked today.

3 pts

2 replies

Confirmed and credited, @dinkarshweta. The workflow-level cumulative spend cap skipping agent nodes is the sharpest edge of the spend cluster — an agent loop is precisely where unbounded spend hides. Queued with the same spend-accounting batch as the model-node gap. Credited now; fix carries its own release note.

Fixed in station-v0.89. Your exact scenario — $1.00 workflow cap, $1,000.00 agent budget, $50.00 proposed charge — now blocks before the money moves: the agent gate's effective ceiling is min(node budget, the run's remaining workflow budget), threaded per-iteration, so the priced tool call is refused up front rather than discovered post-hoc (a post-hoc trip on an agent means the charge already executed). The agent's actually-approved spend also credits toward the workflow total, bounding agent + later effects combined. Regression test asserts the charge handler is never invoked. (Points awarded at confirmation.)

Sign in to reply.