JuliusBrussee/caveman · error

cave_subagent_cost_budget

cave_subagent_cost_budget

Error message

cave_subagent_cost_budget

What it means

After the child settles, the tool's SpendLedger is re-checked: if it is flagged incomplete, or its actualUsd (catalog-priced dollars really spent by the child and its descendants) exceeds runtime.maxCostUsd, the parent aborts. The child exceeded its wallet, or an unpriced call inside the child poisoned the ledger; either way the cap is enforced terminally rather than silently exceeded.

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 766dce6b13)

Solutions

  1. Raise the subagent's maxCostUsd with headroom (the carve at spawn must still fit the parent's remaining budget)
  2. Shrink the child's task or limit its iterations/tool calls so actual spend stays under the wallet
  3. Ensure every model in the child's graph is catalog-priced to avoid the incomplete-ledger path
  4. Inspect the child receipt (usage.receipts) to see where the dollars went before resizing

Example fix

// before
const child = parent.subagent({ maxCostUsd: 0.5 }); // child burns 0.62 -> throw

// after
const child = parent.subagent({ maxCostUsd: 1.5 });
Defensive patterns

Strategy: try-catch

Try / catch

try {
  return await dispatchSubagent(task);
} catch (error) {
  if (error instanceof Error && error.message === 'cave_subagent_cost_budget') {
    // wallet exhausted mid-run: surface partial child work if captured, then stop this branch
    return reportWalletExhausted(toolName);
  }
  throw error;
}

Prevention

When it happens

Trigger: Child's real catalog-priced spend exceeded its maxCostUsd wallet (wallet sized exactly at expected cost with no headroom); a model inside the child's graph absent from the catalog marked the ledger incomplete, tripping the incomplete clause; descendants of the child spending against the same carved wallet.

Common situations: Wallets sized to the point estimate of a task whose cost varies (long research loops); retry-heavy children; unpriced regional models inside a budgeted child; maxCostUsd inherited from parent config without rescaling.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@766dce6b13 (2026-08-18). Data as JSON: /api/errors/287a63281f2890e4. Report an issue: GitHub.