Hmbown/CodeWhale · error · anyhow::Error

Sub-agent token budget exhausted for scope

Error message

Sub-agent token budget exhausted for scope {scope_id}: {spent}/{limit} tokens spent, {remaining} remaining (including reserved reporting allowance). Wait for the parent/Workflow to summarize results or start a fresh agent run.

What it means

Thrown by `resolve_budget_scope` when the token budget for a scope is effectively exhausted: after subtracting already-spent tokens, the reserved reporting/handback allowance, and reserved handback tokens, less than `MIN_SUBAGENT_SPAWN_TOKEN_RESERVE` remains — so no new sub-agent work can be funded. It deliberately tells the caller to stop spawning and let the parent summarize instead of continuing to burn the budget.

Solutions

  1. Let the parent/Workflow summarize the results already produced and end the run — this is the error's own recommendation.
  2. Start a fresh agent run (new session or new scope id) with a reset budget.
  3. Raise the token limit for the scope if the workload legitimately needs more tokens.
  4. Audit which sub-agents spent the budget (via the budget accounting) and reduce per-child allowances or task sizes.
Defensive patterns

Strategy: try-catch

Validate before calling

let spent = engine.aggregate_budget_spent(&scope_id);
if limit.saturating_sub(spent) < MIN_WORK_TOKENS { /* summarize or restart before spawning */ }

Try / catch

match engine.spawn_sub_agent(scope_id, task) {
    Err(e) if e.to_string().contains("token budget exhausted") => {
        let summary = parent_summarize_results();
        start_fresh_scope_run(summary);
    }
    other => other?,
}

Prevention

When it happens

Trigger: Attempting to spawn/resume sub-agent work under scope `scope_id` when `aggregate_budget_spent(scope_id)` plus reserves leave `work_remaining < MIN_SUBAGENT_SPAWN_TOKEN_RESERVE` (mod.rs:5255). Triggered by large model responses, many child runs under one scope, or a limit set too low for the workload.

Common situations: Long-running Workflow runs where children consumed the whole token budget; a parent orchestrator spawning many sub-agents under a single scope id; misconfigured (too small) budget limit; retries of expensive tasks that each consume large allocations.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/7e28156275afdcad. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/tools/subagent/mod.rs:5255

        });

        let Some((scope_id, limit)) = scope else {
            return Ok(None);
        };
        self.resolve_budget_scope(scope_id, limit).map(Some)
    }

    fn resolve_budget_scope(&self, scope_id: String, limit: u64) -> Result<AgentUsageBudgetScope> {
        if scope_id.trim().is_empty() {
            return Err(anyhow!("Sub-agent token budget scope must not be empty"));
        }
        let spent = self.aggregate_budget_spent(&scope_id);
        let remaining = limit.saturating_sub(spent);
        let work_remaining = remaining
            .saturating_sub(budget_handback::token_reserve(limit))
            .saturating_sub(self.reserved_handback_tokens(&scope_id, Some(&scope_id)));
        if work_remaining < MIN_SUBAGENT_SPAWN_TOKEN_RESERVE {
            return Err(anyhow!(
                "Sub-agent token budget exhausted for scope {scope_id}: {spent}/{limit} tokens spent, {remaining} remaining (including reserved reporting allowance). Wait for the parent/Workflow to summarize results or start a fresh agent run."
            ));
        }
        Ok(AgentUsageBudgetScope {
            scope_id,
            limit,
            spent,
            remaining,
        })
    }

    /// Follow both delegation and continuation edges. A set makes malformed
    /// persisted cycles finite and ensures shared descendants count once.
    fn budget_ancestors(&self, worker_id: &str) -> BTreeSet<String> {
        let mut ancestors = BTreeSet::new();
        let mut pending = vec![worker_id.to_string()];
        while let Some(id) = pending.pop() {
            if !ancestors.insert(id.clone()) {

View on GitHub (pinned to 433685b202)