zeroclaw-labs/zeroclaw · error
Budget exceeded: ${:.4} of ${:.2} {:?} limit. Cannot make fu
Error message
Budget exceeded: ${:.4} of ${:.2} {:?} limit. Cannot make further API calls until the budget resets. What it means
enforce_tool_loop_budget runs at the top of each tool-loop iteration and compares accumulated USD spend (current_usd) against the configured limit for a period (rendered with Debug formatting, e.g. Daily). Once spend reaches the limit the turn aborts and no further provider calls are made until the period resets. Budget scopes can be shared — peer cost-scope tests attribute recipient usage to a shared budget — so spend by other participants counts against yours.
Source
Thrown at crates/zeroclaw-runtime/src/agent/turn/provider_call.rs:139
if let Some(BudgetCheck::Exceeded {
current_usd,
limit_usd,
period,
}) = check_tool_loop_budget()
{
::zeroclaw_log::record!(
WARN,
::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)
.with_category(::zeroclaw_log::EventCategory::Provider)
.with_outcome(::zeroclaw_log::EventOutcome::Failure)
.with_attrs(::serde_json::json!({
"current_usd": current_usd,
"limit_usd": limit_usd,
"period": format!("{period:?}"),
})),
"tool-call loop budget exceeded"
);
anyhow::bail!(
"Budget exceeded: ${:.4} of ${:.2} {:?} limit. Cannot make further API calls until the budget resets.",
current_usd,
limit_usd,
period
);
}
Ok(())
}
/// One provider call: streaming via `consume_provider_streaming_response`
/// with non-streaming fallback, or plain non-streaming chat with optional
/// per-step timeout and cancel select. See [`ProviderCallOutcome`] for the
/// cancel asymmetry this function must preserve.
pub(crate) async fn call_provider(
ctx: &TurnCtx<'_>,
active_model_provider: &dyn ModelProvider,
active_model: &str,
prepared_messages: &[ChatMessage],View on GitHub (pinned to 88bb9c8533)
Solutions
- Read the log entry's current_usd, limit_usd, and period to see how far over the scope is and when it resets
- Raise the limit for the affected budget scope if the workload is legitimate, or wait for the period reset
- Cut per-turn cost: lower max_iterations, cheaper model, tighter max_tool_result_chars
- Re-scope budgets so one peer or cron job cannot exhaust another participant's allowance
Example fix
// before
budget = { limit_usd: 1.0, period: Daily }
// after — size the limit to real loop cost (iterations x avg call cost)
budget = { limit_usd: 5.0, period: Daily } Defensive patterns
Strategy: validation
Validate before calling
// before launching a turn in a metered scope
let usage = cost_tracker.scope_usage(&scope).await?;
if usage.current_usd >= usage.limit_usd {
return Ok(Schedule::DeferUntil(usage.resets_at));
} Try / catch
match agent.run_turn(req).await {
Err(ref e) if e.to_string().starts_with("Budget exceeded") => {
// defer the turn until the budget period resets; do not retry now
}
other => other,
} Prevention
- Track burn rate per scope and alert before the limit, not at it
- Scope budgets per task/agent so peers cannot exhaust each other's allowance
- Use cheaper models for exploratory tool loops
- Account for cron jobs when sizing daily/weekly limits
When it happens
Trigger: A long tool loop whose cumulative provider usage crosses the limit mid-turn; or starting a turn when the (possibly peer-shared) budget scope is already at or over the limit from earlier turns or other agents in the same scope.
Common situations: Deep agent tasks on expensive models with tight limits; several channel peers or cron jobs sharing one budget scope; per-day limits not accounting for scheduled work; cost estimates drifting from real token prices.
Related errors
- Estimated cost must be a finite, non-negative value
- Agent loop aborted: repeated prompt-required tool call '{too
- Agent exceeded maximum tool iterations ({max_iterations})
- Final summary LLM call timed out after {step_secs}s (step_ti
- LLM call cancelled by hook: {reason}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/453c562311deae4f.
Report an issue: GitHub.