JuliusBrussee/caveman · error

cave_subagent_wallet_denomination_unavailable

cave_subagent_wallet_denomination_unavailable

Error message

cave_subagent_wallet_denomination_unavailable

What it means

When a parent has a BudgetMeter, the child's wallet must be carved from it in the parent's denomination: maxCostUsd for "usd" meters or maxTokens for token meters. If the meter's denomination has no corresponding runtime limit configured (walletAmount is undefined), the runtime refuses to fund the subagent and throws this error, since it cannot determine how much budget to carve.

Solutions

  1. Set the runtime field matching the parent meter's denomination: maxCostUsd when the meter is "usd", maxTokens otherwise
  2. Run the parent without a budget (parentMeter undefined) if subagents should be unfunded — then no carve is required
  3. Define both maxCostUsd and maxTokens on the subagent runtime so either denomination works
  4. Align budget denomination across parent and subagent configurations

Example fix

// before (parent meter is "usd")
runtime: { kind: "subagent", maxTokens: 50000 }
// after
runtime: { kind: "subagent", maxCostUsd: 0.5, maxTokens: 50000 }
Defensive patterns

Strategy: validation

Validate before calling

if (parentMeter !== undefined) {
  const need = parentMeter.denomination === "usd" ? runtime.maxCostUsd : runtime.maxTokens;
  if (need === undefined) {
    throw new Error("subagent lacks a " + parentMeter.denomination + " budget");
  }
}

Try / catch

try {
  await dispatchSubagent({ task });
} catch (e) {
  if (e.message === "cave_subagent_wallet_denomination_unavailable") {
    // run the child unfunded or fix the config, then retry
    await dispatchSubagent({ task, ignoreBudget: true });
  } else throw e;
}

Prevention

When it happens

Trigger: Parent agent runs under a token-denominated BudgetMeter but the subagent runtime defines only maxCostUsd (or vice versa: usd meter with only maxTokens set), so walletAmount is undefined while parentMeter is defined.

Common situations: Mixed-budget setups: budgets switched from tokens to USD (or the reverse) without updating each subagent's maxCostUsd/maxTokens; a shared subagent config used under both denominations; defaults omitting the field matching the meter's denomination.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


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

Appendix: source

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

  const calls = usage.calls.get(toolDefinition.name) ?? 0;
  if (calls >= runtime.maxCalls) throw new Error("cave_subagent_call_budget");
  // Reserve synchronously before any await so parallel Pi tool dispatch cannot
  // pass the same maxCalls check twice.
  usage.calls.set(toolDefinition.name, calls + 1);
  const depth = executionContext.depth;
  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,

View on GitHub (pinned to 3ee70a1026)