JuliusBrussee/caveman · error

cave_subagent_wallet_unavailable

cave_subagent_wallet_unavailable

Error message

cave_subagent_wallet_unavailable

What it means

A subagent whose runtime declares a spend cap (maxCostUsd or maxTokens) must have that entire cap carved out of the parent's remaining budget as a wallet, synchronously at spawn, so parallel children cannot double-spend the same remainder. carve() (budget.ts:376) returns undefined when the parent meter is breached/revoked, when the amount is not finite/positive (or not a safe integer in token denomination), or when the amount exceeds the parent's remaining budget. This error means the parent cannot fund the child's wallet, so dispatch aborts before the child is admitted.

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

Solutions

  1. Lower the subagent's maxCostUsd/maxTokens below the parent's remaining budget (check the run receipt's spend/tranche history to see what remains)
  2. Dispatch fewer subagents per turn, or size them so the sum of caps fits the remainder
  3. Top up the parent budget via createBudgetController() checkpoints (release() is max-bounded) before dispatching more children
  4. Align denominations: parent budget maxUsd pairs with child maxCostUsd, maxTokens pairs with integer maxTokens
  5. Do not retry dispatch after a capBreached parent; inspect RunResult.capBreached/stopReason first

Example fix

// before: child cap equals parent budget already partly spent
const parent = agent({ ... });
const child = parent.subagent({ maxCostUsd: 5 }); // parent already spent $3 of $5

// after: size wallet against remaining budget with headroom
const remaining = 5 - spentUsdFromReceipt;
const child = parent.subagent({ maxCostUsd: Math.max(0.5, remaining * 0.8) });
Defensive patterns

Strategy: try-catch

Validate before calling

// Before dispatching budgeted subagents, size the wallet against remaining parent budget
const parentBudgetUsd = 5;
const spentUsd = sumSpentFromReceipt(rootReceipt); // roll up receipts of finished runs
const remaining = parentBudgetUsd - spentUsd;
const wallet = Math.min(subagentCap, remaining * 0.8); // headroom
if (wallet <= 0) throw new Error('no parent budget left for subagent wallet');

Try / catch

try {
  const out = await child(task);
} catch (error) {
  if (error instanceof Error && error.message === 'cave_subagent_wallet_unavailable') {
    // parent meter cannot fund this cap: shrink the wallet or stop dispatching
    logger.warn('subagent wallet unfundable; remaining parent budget too small');
    return fallbackWithoutSubagent();
  }
  throw error;
}

Prevention

When it happens

Trigger: Parent run started with RunOptions.budget (or maxCostUsd) and a subagent() declares maxCostUsd/maxTokens greater than the parent's remaining budget; dispatching two subagents in one turn whose combined caps exceed the remainder (the first carve consumes it); parent meter already capBreached or revoked; parent budget in tokens while the child's maxTokens is fractional/non-integer.

Common situations: Giving the child the same cap as the parent after the parent already spent on earlier calls; sizing wallets with zero headroom then adding one more parallel subagent; mixing denominations between parent budget and child cap; retrying a subagent dispatch after the parent budget was nearly drained.

Related errors


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