{"record":{"id":"5ffb2a7c341dca5c","repo":"JuliusBrussee/caveman","slug":"cave-subagent-call-budget","errorCode":"cave_subagent_call_budget","errorMessage":"cave_subagent_call_budget","messagePattern":"cave_subagent_call_budget","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/runtime.ts","lineNumber":3874,"sourceCode":"  toolDefinition: ToolDefinition,\n  params: unknown,\n  signal: AbortSignal | undefined,\n  parentOptions: InternalRunOptions,\n  usage: NestedUsage,\n  executionContext: InternalExecutionContext,\n  parentMeter: BudgetMeter | undefined,\n  parentDeadlineAt: number | undefined,\n): Promise<unknown> {\n  const runtime = toolDefinition.runtime;\n  if (runtime?.kind !== \"subagent\") throw new Error(\"cave_subagent_runtime_missing\");\n  if (params === null || typeof params !== \"object\" || Array.isArray(params) ||\n      typeof (params as { task?: unknown }).task !== \"string\") {\n    throw new Error(\"cave_subagent_arguments_invalid\");\n  }\n  const task = (params as { task: string }).task;\n  if (task.length > runtime.maxInputChars) throw new Error(\"cave_subagent_input_limit\");\n  const calls = usage.calls.get(toolDefinition.name) ?? 0;\n  if (calls >= runtime.maxCalls) throw new Error(\"cave_subagent_call_budget\");\n  // Reserve synchronously before any await so parallel Pi tool dispatch cannot\n  // pass the same maxCalls check twice.\n  usage.calls.set(toolDefinition.name, calls + 1);\n  const depth = executionContext.depth;\n  const depthLimit = Math.min(\n    parentOptions.maxSubagentDepth ?? DEFAULT_SUBAGENT_DEPTH_LIMIT,\n    ABSOLUTE_SUBAGENT_DEPTH_LIMIT,\n  );\n  if (depth + 1 > depthLimit) throw new Error(\"cave_subagent_depth_limit\");\n  // The wallet is carved here, still synchronously, for the same reason: two\n  // subagents dispatched in one turn must not both be funded out of the same\n  // remaining budget.\n  const walletAmount = parentMeter === undefined\n    ? undefined\n    : parentMeter.denomination === \"usd\" ? runtime.maxCostUsd : runtime.maxTokens;\n  if (parentMeter !== undefined && walletAmount === undefined) {\n    throw new Error(\"cave_subagent_wallet_denomination_unavailable\");\n  }","sourceCodeStart":3856,"sourceCodeEnd":3892,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/766dce6b1394ebb56a3090748d5a0240a5aefb36/packages/agent/src/runtime.ts#L3856-L3892","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Raise runtime.maxCalls for that subagent tool","Instruct the model to batch multiple items into a single subagent task","Check remaining budget before dispatch and steer the model to finish the work in-run"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const used = usage.calls.get(tool.name) ?? 0;\nif (used >= tool.runtime.maxCalls) {\n  return { error: `${tool.name} call budget exhausted (${tool.runtime.maxCalls}) — batch remaining work into one call` };\n}\nreturn executeSubagent(tool, params, signal, ...);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Size runtime.maxCalls from the tool's expected fan-out before shipping","Instruct the model to batch multiple items per subagent call","Track per-tool call counts in your harness and warn near the cap"],"tags":["subagents","budget","limits"],"backgroundTag":"call-budget-exhausted","analyzedSha":"766dce6b1394ebb56a3090748d5a0240a5aefb36","analyzedAt":"2026-08-18T03:14:35.516Z","contentChangedAt":"2026-08-18T03:14:35.516Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}