{"record":{"id":"27d86b8f225c6d8d","repo":"JuliusBrussee/caveman","slug":"cave-subagent-depth-limit-27d86b","errorCode":"cave_subagent_depth_limit","errorMessage":"cave_subagent_depth_limit","messagePattern":"cave_subagent_depth_limit","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/runtime.ts","lineNumber":3883,"sourceCode":"  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  }\n  const carve = parentMeter === undefined || walletAmount === undefined\n    ? undefined\n    : parentMeter.carve(walletAmount);\n  if (parentMeter !== undefined && carve === undefined) {\n    throw new Error(\"cave_subagent_wallet_unavailable\");\n  }\n  let releaseAdmission: (() => void) | undefined;\n  try {\n    releaseAdmission = admitSubagent(executionContext.invocationState);","sourceCodeStart":3865,"sourceCodeEnd":3901,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/agent/src/runtime.ts#L3865-L3901","documentation":"Subagent nesting depth is bounded by min(parentOptions.maxSubagentDepth, ABSOLUTE_SUBAGENT_DEPTH_LIMIT). When the current execution depth plus one would exceed that limit, cave_subagent_depth_limit is thrown to stop runaway recursive subagent spawning (subagent spawning subagents).","triggerScenarios":"A subagent itself dispatching another subagent when executionContext.depth + 1 exceeds the effective depth limit — e.g. depth already at maxSubagentDepth (or the absolute cap) and another nested call is attempted.","commonSituations":"Recursive workflows where child prompts instruct further delegation; default depth limit too small for deeply hierarchical plans; parentOptions.maxSubagentDepth unset so the library default applies and the chain bottoms out unexpectedly.","solutions":["Increase parentOptions.maxSubagentDepth in the parent agent's options (it cannot exceed the absolute cap)","Restructure the workflow to be flatter: have top-level agents do the nested work directly","Detect which level of the chain re-dispatched and remove the delegation from that child's prompt/tools","Deny subagent tools to deep-level child agents so they cannot recurse"],"exampleFix":"// before\nrunAgent({ maxSubagentDepth: 1 });\n// after\nrunAgent({ maxSubagentDepth: 3 }); // or flatten the workflow to avoid recursion","handlingStrategy":"validation","validationCode":"if (executionContext.depth + 1 > depthLimit) {\n  // do the work inline instead of spawning a subagent\n}","typeGuard":null,"tryCatchPattern":"try {\n  await dispatchSubagent({ task });\n} catch (e) {\n  if (e.message === \"cave_subagent_depth_limit\") {\n    result = await doWorkInline(task); // flatten the hierarchy\n  } else throw e;\n}","preventionTips":["Set maxSubagentDepth explicitly to match your intended hierarchy","Strip subagent tools from deep-level children so recursion is impossible","Design workflows flat rather than recursive by default","Log depth on each dispatch to visualize the delegation tree"],"tags":["subagent","depth","recursion","limit"],"backgroundTag":"value-out-of-range","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"}