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
- 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
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
- 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
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
- cave_subagent_call_budget
- cave_subagent_concurrency_limit
- cave_subagent_context_budget
- cave_subagent_input_limit
- bounded
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)