Enforces the USD budget (max_cost_usd) as a hard pre-flight gate before
every node execution and at each main-agent ainvoke round inside the
multi-turn tool loop, with pruning passes silently skipped when the
budget is exhausted.
Pre-flight gate (pure_graph.py):
_check_budget_pre_flight() raises ExecutionError(kind='cost',
reason='budget_exhausted') immediately before node execution in both
_execute_from_node and all three branches of _stream_from_node.
In-node budget gating (llm.py):
_check_budget_before_invoke() gates every _retry_ainvoke() call
inside _execute_tool_loop, process_message, and stream_message.
_should_skip_pruning() skips pruning passes when budget exhausted.
_accumulate_cost_after_invoke() computes USD cost from token counts
using the pricing table and advances current_accumulated_cost so
subsequent budget checks see the updated cost.
ContextVar plumbing (retry.py, pure_graph.py):
current_accumulated_cost, current_max_cost_usd, and current_pricing
ContextVars carry per-node budget state from PureLangGraph into
LLMAgent. Set before node.execute() via _set_budget_context_for_node,
reset in finally blocks via _reset_budget_context.
Reviewer fixes (rui.hu #80):
- Move cost accumulation inside try block in terminal streaming branch
to prevent ContextVar leak (was set after finally reset).
- Standardize log precision to $%.6f across all budget messages.
- Remove orphaned 'already exhausted' step (criterion unreachable).
- Add dedicated streaming budget enforcement scenario.
- Remove redundant = None default on _reset_budget_context pricing_token.
ISSUES CLOSED: #76