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
- Raise runtime.maxCostUsd to accommodate the expected total spend of the parent plus children.
- Inspect spendLedger.actualUsd to see how much was spent and which child runs consumed it; reduce delegation depth or frequency.
- If the ledger is incomplete, fix the underlying unpriced/missing-evidence condition before re-running.
- 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
- Track spendLedger.actualUsd before each delegation and stop near the cap.
- Size maxCostUsd from measured per-run costs, not guesses.
- Fix incomplete ledgers promptly; they cascade into later runs.
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
- cave_subagent_call_budget
- cave_subagent_spend_evidence_incomplete
- cave_subagent_unpriced_budget
- cave_subagent_wallet_denomination_unavailable
- cave_subagent_wallet_unavailable
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)