Skip to content

Keep the run budget across wait/resume - #14

Open
saitej123 wants to merge 1 commit into
theschoolofai:mainfrom
saitej123:fix/resume-keeps-run-budget
Open

saitej123 wants to merge 1 commit into
theschoolofai:mainfrom
saitej123:fix/resume-keeps-run-budget

Conversation

@saitej123

@saitej123 saitej123 commented Aug 13, 2026 •

Copy link
Copy Markdown

What breaks

A budgeted run that parks on request_approval (or any other wait) resumes with budget=None. HTTP resume, channel-thread approval, job completions, and the UI /v1/action path all call runtime.run(..., resume=True) without the original ceiling. Graph context restored the prompt and side-effects, not the allowance or spend, so BudgetedGateway was never rebuilt. The control that held before the wait did not hold after it: the same run could continue unmetered.

Restoring only the original ceiling with spend reset to zero would still be a bypass (a second full allowance after every wait). Spend has to come back too.

Reproduction

On current main, this test fails because the resumed body has budget: null:

uv run pytest tests/test_budget_runtime.py::test_resume_keeps_the_run_budget_and_prior_spend -q

Steps without the test:

  1. POST /v1/agent/runs with "budget": 0.05 and allowed_side_effects: ["request_approval"] until a node is waiting.
  2. Complete the wait handle.
  3. POST /v1/agent/runs/{run_id}/resume (no budget in the body; the route does not accept one).
  4. Before this change the resumed payload has "budget": null and gateway calls are not metered through BudgetedGateway. After it, budget.total is still 0.05 and budget.spent is at least what the parked run had already charged.

Fix

  • Persist budget, principal, and budget_spent in graph context on start and after every run() return (including waits).
  • On resume=True, rebuild RunBudget + BudgetedGateway from that context even when the caller passes budget=None, and restore prior spend.
  • Pass transport on the HTTP resume and UI action routes so the metered seam can actually be reconstructed.

uv run pytest tests/test_budget_runtime.py tests/test_runtime.py tests/test_channel_connections.py tests/test_control_plane_auth.py and uv run ruff check on the touched files pass. No .env, tokens, or real messages in the diff.

A parked graph used to resume with budget=None, so the ceiling that held
before request_approval did not hold after it. Persist the allowance and
spend in graph context and rebuild BudgetedGateway on resume.

Co-authored-by: Cursor <cursoragent@cursor.com>
@theschoolofai

Copy link
Copy Markdown
Owner

Session 16 — graded ✅

Score: +100.

The run budget was lost across wait/resume, so a run that survived a restart came back unbounded.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants