JuliusBrussee/caveman · error

cave_subagent_wallet_unavailable

cave_subagent_wallet_unavailable

Error message

cave_subagent_wallet_unavailable

What it means

After resolving the wallet amount, the runtime calls parentMeter.carve(walletAmount) to reserve the child's budget from the parent's remaining funds. If the carve returns undefined — the parent meter cannot cover the requested amount — cave_subagent_wallet_unavailable is thrown, refusing to start an unfunded subagent. This prevents children from spending budget the parent does not have.

Solutions

  1. Lower the subagent's maxCostUsd/maxTokens so it fits within the parent's remaining budget
  2. Increase the parent agent's total budget
  3. Dispatch fewer subagents per turn, or sequence them and re-check remaining budget
  4. Check parentMeter remaining balance before dispatch and skip/fallback when insufficient

Example fix

// before
runtime: { kind: "subagent", maxCostUsd: 5.0 } // parent budget only $1 left
// after
runtime: { kind: "subagent", maxCostUsd: 0.25 } // fits remaining parent budget
Defensive patterns

Strategy: validation

Validate before calling

if (parentMeter !== undefined && parentMeter.remaining() < requiredWalletAmount) {
  // skip dispatch or request a smaller carve
}

Try / catch

try {
  await dispatchSubagent({ task });
} catch (e) {
  if (e.message === "cave_subagent_wallet_unavailable") {
    result = await doWorkInlineWithoutBudget();
  } else throw e;
}

Prevention

When it happens

Trigger: Dispatching a subagent whose runtime.maxCostUsd/maxTokens exceeds the parent BudgetMeter's remaining balance, so carve() cannot reserve the full amount.

Common situations: Long-running parent that already consumed most of its budget; maxCostUsd/maxTokens set higher than the parent's total budget; several subagents dispatched in one turn draining the shared wallet before later ones; budget-denominated (USD) parents with aggressive per-child costs.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/4135bea9d9548534. Report an issue: GitHub.

Appendix: source

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

  const depthLimit = Math.min(
    parentOptions.maxSubagentDepth ?? DEFAULT_SUBAGENT_DEPTH_LIMIT,
    ABSOLUTE_SUBAGENT_DEPTH_LIMIT,
  );
  if (depth + 1 > depthLimit) throw new Error("cave_subagent_depth_limit");
  // The wallet is carved here, still synchronously, for the same reason: two
  // subagents dispatched in one turn must not both be funded out of the same
  // remaining budget.
  const walletAmount = parentMeter === undefined
    ? undefined
    : parentMeter.denomination === "usd" ? runtime.maxCostUsd : runtime.maxTokens;
  if (parentMeter !== undefined && walletAmount === undefined) {
    throw new Error("cave_subagent_wallet_denomination_unavailable");
  }
  const carve = parentMeter === undefined || walletAmount === undefined
    ? undefined
    : parentMeter.carve(walletAmount);
  if (parentMeter !== undefined && carve === undefined) {
    throw new Error("cave_subagent_wallet_unavailable");
  }
  let releaseAdmission: (() => void) | undefined;
  try {
    releaseAdmission = admitSubagent(executionContext.invocationState);
    return await runSubagent({
      toolDefinition,
      runtime,
      task,
      signal,
      parentOptions,
      usage,
      executionContext,
      depth,
      childMeter: carve?.child,
      parentDeadlineAt,
    });
  } finally {
    releaseAdmission?.();

View on GitHub (pinned to 3ee70a1026)