{"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":"error_code","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/3ee70a102609e550bd2e68004bf5990a9341c851/packages/agent/src/runtime.ts#L3856-L3892","documentation":"Each subagent tool name has a per-execution call budget tracked in usage.calls; before dispatching, the runtime compares the current call count against runtime.maxCalls and throws cave_subagent_call_budget when it has already been reached. The counter is incremented synchronously right after the check so parallel dispatch cannot double-spend the budget.","triggerScenarios":"Invoking the same subagent tool more than runtime.maxCalls times within one parent agent execution (per usage.calls ledger) — e.g. a maxCalls: 3 budget hit on the 4th call.","commonSituations":"Looping agent that keeps re-dispatching the same researcher subagent; maxCalls configured too low for the task; retried tool calls consuming the budget after partial failures.","solutions":["Raise runtime.maxCalls for that subagent tool in the configuration","Restructure the prompt/workflow so fewer subagent invocations are needed (batch work into one call)","Use a differently-named subagent tool for additional budget","Cache subagent results and reuse them instead of re-dispatching"],"exampleFix":"// before\nruntime: { kind: \"subagent\", maxCalls: 3 }\n// after\nruntime: { kind: \"subagent\", maxCalls: 10 }","handlingStrategy":"validation","validationCode":"const used = getUsage().calls.get(subagentToolName) ?? 0;\nif (used >= subagentRuntime.maxCalls) {\n  // reuse cached result or skip dispatch\n}","typeGuard":null,"tryCatchPattern":"try {\n  await dispatchSubagent({ task });\n} catch (e) {\n  if (e.message === \"cave_subagent_call_budget\") {\n    result = getCachedSubagentResult(subagentToolName) ?? synthesizeWithoutSubagent();\n  } else throw e;\n}","preventionTips":["Budget maxCalls above the expected number of dispatches per run","Cache subagent outputs for repeated identical work","Avoid retry loops that re-dispatch after transient failures without counting them","Alert when usage.calls approaches maxCalls mid-run"],"tags":["subagent","budget","limit"],"backgroundTag":"rate-limit-exceeded","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}