JuliusBrussee/caveman · error

cave_subagent_call_budget

cave_subagent_call_budget

Error message

cave_subagent_call_budget

What it means

Each subagent tool has a per-run call budget, runtime.maxCalls, tracked in usage.calls by tool name. The check-and-increment happens synchronously before any await so parallel dispatches cannot double-spend; once calls for that tool reach maxCalls, further invocations throw cave_subagent_call_budget.

Source

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

  toolDefinition: ToolDefinition,
  params: unknown,
  signal: AbortSignal | undefined,
  parentOptions: InternalRunOptions,
  usage: NestedUsage,
  executionContext: InternalExecutionContext,
  parentMeter: BudgetMeter | undefined,
  parentDeadlineAt: number | undefined,
): Promise<unknown> {
  const runtime = toolDefinition.runtime;
  if (runtime?.kind !== "subagent") throw new Error("cave_subagent_runtime_missing");
  if (params === null || typeof params !== "object" || Array.isArray(params) ||
      typeof (params as { task?: unknown }).task !== "string") {
    throw new Error("cave_subagent_arguments_invalid");
  }
  const task = (params as { task: string }).task;
  if (task.length > runtime.maxInputChars) throw new Error("cave_subagent_input_limit");
  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");
  }

View on GitHub (pinned to 766dce6b13)

Solutions

  1. Raise runtime.maxCalls for that subagent tool
  2. Instruct the model to batch multiple items into a single subagent task
  3. Check remaining budget before dispatch and steer the model to finish the work in-run
Defensive patterns

Strategy: validation

Validate before calling

const used = usage.calls.get(tool.name) ?? 0;
if (used >= tool.runtime.maxCalls) {
  return { error: `${tool.name} call budget exhausted (${tool.runtime.maxCalls}) — batch remaining work into one call` };
}
return executeSubagent(tool, params, signal, ...);

Prevention

When it happens

Trigger: The model invokes the same subagent tool more than maxCalls times in one run — e.g. one call per file across a large directory, or repeated retries of failed subtasks.

Common situations: Delegation loops with one micro-task per call; budgets sized for an earlier, smaller scope; models that retry a failing subtask by spawning a fresh subagent each time.

Related errors


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