JuliusBrussee/caveman · error

cave_subagent_depth_limit

cave_subagent_depth_limit

Error message

cave_subagent_depth_limit

What it means

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).

Solutions

  1. Increase parentOptions.maxSubagentDepth in the parent agent's options (it cannot exceed the absolute cap)
  2. Restructure the workflow to be flatter: have top-level agents do the nested work directly
  3. Detect which level of the chain re-dispatched and remove the delegation from that child's prompt/tools
  4. Deny subagent tools to deep-level child agents so they cannot recurse

Example fix

// before
runAgent({ maxSubagentDepth: 1 });
// after
runAgent({ maxSubagentDepth: 3 }); // or flatten the workflow to avoid recursion
Defensive patterns

Strategy: validation

Validate before calling

if (executionContext.depth + 1 > depthLimit) {
  // do the work inline instead of spawning a subagent
}

Try / catch

try {
  await dispatchSubagent({ task });
} catch (e) {
  if (e.message === "cave_subagent_depth_limit") {
    result = await doWorkInline(task); // flatten the hierarchy
  } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


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

Appendix: source

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

  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");
  }
  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);

View on GitHub (pinned to 3ee70a1026)