Add a pre-call USD belt on top of the post-charge token/round meter (§8),
so no future live run can loop past its run budget. Belt-and-braces above
the SDK's per-call max_budget_usd cap.
- budget.py: optional run-total `max_cost_usd` on BudgetMeter (fail-fast on
non-positive, §10) + `guard_before_call(spent_usd)` raising the same
structured stop event (BudgetKind widened with "cost_usd"; limit/observed
→ float). Reaching the cap exactly does not stop; crossing it does
(mirrors the token cap).
- loop.py: `_guarded_complete` helper reads the client's accumulated
total_cost_usd (0.0 for scripted clients) and guards BEFORE every
client.complete; all three call sites routed through it — one detach point.
- sdk_client.py: total_cost_usd already exposed/accumulated — untouched.
- tests/test_budget.py: meter-level cap tests + load-bearing loop-wiring
test (counting client; detach the guard → unguarded loop runs to the round
cap → kind "rounds" not "cost_usd" → red).
457→462 green, golden byte-exact, full gate clean (ruff+format+mypy strict,
22 src files), run_s10.py/runs/ byte-untouched. README test-count sync ×2 +
budget.py belt note.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RiTwaKLesgcwXx2mDviqpt