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

  1. Read the log entry's current_usd, limit_usd, and period to see how far over the scope is and when it resets
  2. Raise the limit for the affected budget scope if the workload is legitimate, or wait for the period reset
  3. Cut per-turn cost: lower max_iterations, cheaper model, tighter max_tool_result_chars
  4. 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

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


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/453c562311deae4f. Report an issue: GitHub.