JuliusBrussee/caveman · error
cave_subagent_invocation_limit
Error message
cave_subagent_invocation_limit
What it means
Thrown by admitSubagent when the root-owned invocation ledger has already admitted maxInvocations subagent calls. It enforces RunOptions.maxSubagentInvocations, a monotonic cap on total descendant admissions across all tools and depths in one run. The check runs before admission, so a rejected call consumes no tree slot and increments ledger.invocationRejections (surfaced on the root span as cave.agent.tree.invocation_limit_rejections).
Source
Thrown at packages/agent/src/runtime.ts:3830
appliedPlan: AppliedPlan,
): { description: string; input: TSchema } {
const segment = lowered.ir.segments.find((item) => item.id === `tool.${definition.name}`);
if (!segment) throw new Error(`cave_context_segment_missing:tool.${definition.name}`);
const body = appliedPlan.bodies.get(segment.bodyHandle);
if (!body) throw new Error(`cave_context_body_missing:tool.${definition.name}`);
const parsed = JSON.parse(new TextDecoder().decode(body)) as unknown;
if (!isRecord(parsed) || parsed.name !== definition.name ||
typeof parsed.description !== "string" || !isRecord(parsed.input)) {
throw new Error(`cave_tool_schema_invalid:${definition.name}`);
}
return { description: parsed.description, input: parsed.input as TSchema };
}
function admitSubagent(state: InvocationState): () => void {
const ledger = state.ledger;
if (ledger.maxInvocations !== undefined && ledger.admitted >= ledger.maxInvocations) {
ledger.invocationRejections++;
throw new Error("cave_subagent_invocation_limit");
}
if (ledger.maxConcurrent !== undefined && ledger.active >= ledger.maxConcurrent) {
ledger.concurrencyRejections++;
throw new Error("cave_subagent_concurrency_limit");
}
ledger.admitted++;
ledger.active++;
ledger.peakActive = Math.max(ledger.peakActive, ledger.active);
let released = false;
return () => {
if (released) return;
released = true;
ledger.active = Math.max(0, ledger.active - 1);
};
}
function childInvocationTrace(parent: InvocationTrace): InvocationTrace {
return Object.freeze({View on GitHub (pinned to 27d5a3981a)
Solutions
- Raise or remove RunOptions.maxSubagentInvocations if the tree width is legitimate.
- Reduce per-turn fan-out in the subagent tool definition so fewer descendants are admitted.
- Inspect the root span attribute cave.agent.tree.invocation_limit_rejections to confirm this cap (not maxCalls or depth) is the one rejecting.
Example fix
// before
const result = await run(agent, { input, maxSubagentInvocations: 4 });
// agent's fan-out tool spawns 8 children -> cave_subagent_invocation_limit
// after
const result = await run(agent, { input, maxSubagentInvocations: 8 }); Defensive patterns
Strategy: try-catch
Validate before calling
// No public pre-check exists: the ledger is framework-internal.
// Budget your tree width yourself before the run:
const expected = toolFanOut * levels;
if (opts.maxSubagentInvocations !== undefined && expected > opts.maxSubagentInvocations) {
throw new Error(`fan-out ${expected} exceeds maxSubagentInvocations ${opts.maxSubagentInvocations}`);
} Type guard
const isInvocationLimit = (e: unknown): boolean => e instanceof Error && e.message === "cave_subagent_invocation_limit";
Try / catch
try {
await run(agent, { input, maxSubagentInvocations: 16 });
} catch (error) {
if (error instanceof Error && error.message === "cave_subagent_invocation_limit") {
// tree cap hit: decide between widening the cap or trimming fan-out
} else throw error;
} Prevention
- Estimate total descendant admissions (fan-out x depth) before setting maxSubagentInvocations.
- Cap per-turn fan-out in subagent tools so a single turn cannot exhaust the tree budget.
- Watch cave.agent.tree.invocation_limit_rejections on the root span during load tests.
When it happens
Trigger: Calling run() with RunOptions.maxSubagentInvocations set to N while the agent tree admits more than N subagent invocations in total (e.g. a fan-out tool spawns many children, or nested subagents each spawn more).
Common situations: Developer sets a small maxSubagentInvocations to bound cost, then adds a parallel-fan-out subagent tool whose children exceed the cap; or a recursive task delegation pattern admits more descendants than expected because the cap counts the whole tree, not per-level.
Related errors
- cave_subagent_concurrency_limit
- caveman agent: subagent maxInputChars must be a positive int
- cave_subagent_framework_runner_required
- option not found
- cave_harness_adapter_version_invalid
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/b980168c88686aaa.
Report an issue: GitHub.