JuliusBrussee/caveman · error

cave_subagent_cost_budget

cave_subagent_cost_budget

Error message

cave_subagent_cost_budget

What it means

When a spend ledger is configured, the runtime checks after each child run that the ledger is complete and that actual spend has not exceeded runtime.maxCostUsd. If the ledger is marked incomplete (unverifiable evidence) or actualUsd > maxCostUsd, usage is marked incomplete and this error is thrown. It is the cost-budget counterpart of the context-token budget check.

Solutions

  1. Raise runtime.maxCostUsd to accommodate the expected total spend of the parent plus children.
  2. Inspect spendLedger.actualUsd to see how much was spent and which child runs consumed it; reduce delegation depth or frequency.
  3. If the ledger is incomplete, fix the underlying unpriced/missing-evidence condition before re-running.
  4. Pre-check remaining budget before spawning each subagent and skip delegation when close to the cap.

Example fix

// before
const runtime = createRuntime({ maxCostUsd: 0.05 });
// after
const runtime = createRuntime({ maxCostUsd: 1.0 });
Defensive patterns

Strategy: validation

Validate before calling

if (spendLedger && (spendLedger.incomplete || spendLedger.actualUsd > runtime.maxCostUsd)) {
  throw new Error('cost budget already exhausted; skipping delegation');
}

Type guard

const budgetHealthy = (ledger) =>
  ledger !== undefined && !ledger.incomplete && ledger.actualUsd <= ledger.limitUsd;

Try / catch

try {
  await spawnSubagent(task);
} catch (e) {
  if (e.message === 'cave_subagent_cost_budget') {
    escalateForBudgetApproval();
  } else throw e;
}

Prevention

When it happens

Trigger: Spawning a nested subagent while a spendLedger is attached and either spendLedger.incomplete === true, or spendLedger.actualUsd exceeds runtime.maxCostUsd after the child's costUsd was added (runtime.ts:4062).

Common situations: maxCostUsd set too low for the delegated workload; multiple subagent runs accumulating spend on one ledger; pricing changed so accumulated actualUsd crossed the limit mid-run; ledger marked incomplete by an earlier unpriced-model event.

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 JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/287a63281f2890e4. Report an issue: GitHub.

Appendix: source

Thrown at packages/agent/src/runtime.ts:4062

    throw new Error("cave_nested_usage_incomplete");
  }
  usage.inputTokens += child.inputTokens;
  usage.outputTokens += child.outputTokens;
  usage.cacheReadTokens += child.cacheReadTokens;
  usage.cacheWriteTokens += child.cacheWriteTokens;
  usage.reasoningTokens += child.reasoningTokens;
  usage.costUsd += child.costUsd;
  usage.receipts.push(child.receipt);
  if (child.mode === "observe-only") usage.observeOnly = true;
  if (child.priceBasis !== "public_catalog") usage.unpriced = true;
  if (child.inputTokens > runtime.maxContextTokens) {
    usage.incomplete = true;
    throw new Error("cave_subagent_context_budget");
  }
  if (spendLedger !== undefined &&
      (spendLedger.incomplete || spendLedger.actualUsd > runtime.maxCostUsd)) {
    usage.incomplete = true;
    throw new Error("cave_subagent_cost_budget");
  }
  return {
    text: child.text,
    agent_id: child.agentId,
    usage_basis: child.usageBasis,
    input_tokens: child.inputTokens,
    output_tokens: child.outputTokens,
    cost_usd: child.costUsd,
    // A child that ran out of wallet returns partial work; the caller sees why
    // rather than reading a truncated answer as a complete one.
    stop_reason: child.stopReason,
    claim_basis: "inferred",
  };
}

function completeUsage(result: RunResult): boolean {
  if (result.reasoningUsageBasis !== "provider_reported") return false;
  return [

View on GitHub (pinned to 3ee70a1026)