JuliusBrussee/caveman · error
cave_subagent_runtime_missing
Error message
cave_subagent_runtime_missing
What it means
executeSubagent throws this when the tool definition it was handed does not carry runtime.kind === "subagent". It is an internal dispatch invariant: the subagent execution path may only run against tools declared with a subagent runtime. Hitting it as a user means the definition graph or tool wiring routed a normal tool into the nested-agent executor.
Source
Thrown at packages/agent/src/runtime.ts:3866
return Object.freeze({
traceId: parent.traceId,
spanId: randomBytes(8).toString("hex"),
parentSpanId: parent.spanId,
});
}
async function executeSubagent(
toolDefinition: ToolDefinition,
params: unknown,
signal: AbortSignal | undefined,
parentOptions: InternalRunOptions,
usage: NestedUsage,
executionContext: InternalExecutionContext,
parentMeter: BudgetMeter | undefined,
parentDeadlineAt: number | undefined,
): Promise<unknown> {
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: twoView on GitHub (pinned to 27d5a3981a)
Solutions
- Create nested-agent tools with the framework's subagent() builder rather than hand-authoring ToolDefinition objects.
- Verify toolDefinition.runtime?.kind === "subagent" for every tool you route into nested execution before dispatch.
- Check for definition copies/spreads that omit the runtime field (it is non-enumerable or stripped in some clone paths).
Example fix
// before
const tool: ToolDefinition = { name: "helper", description: "...", execute: nestedRun };
// after
const tool = subagent({ name: "helper", description: "...", /* agent definition */ }); Defensive patterns
Strategy: type-guard
Validate before calling
if (tool.runtime?.kind !== "subagent") {
throw new Error(`tool ${tool.name} lacks subagent runtime; build it with subagent()`);
} Type guard
const hasSubagentRuntime = (t: ToolDefinition): boolean => t.runtime?.kind === "subagent";
Try / catch
try {
await executeNested(tool);
} catch (error) {
if (error instanceof Error && error.message === "cave_subagent_runtime_missing") {
// definition wiring bug — rebuild the tool with subagent(), do not retry
} else throw error;
} Prevention
- Always construct nested-agent tools with the subagent() builder; never hand-write runtime objects.
- Assert runtime.kind on every tool routed into nested execution in tests.
- Avoid deep-cloning tool definitions; clone paths can drop the runtime field.
When it happens
Trigger: A tool definition reaches executeSubagent without a runtime object, or with runtime.kind other than "subagent" (e.g. hand-built ToolDefinition objects passed where subagent() was intended, or a graph mutation stripped the runtime field).
Common situations: Building tool definitions manually instead of via the subagent() builder; spreading/copying a tool definition and dropping the runtime property; version drift between the builder API and runtime dispatcher.
Related errors
- cave_subagent_depth_limit
- cave_host_sandbox_nested_under_required
- cave_tool_definition_invalid
- cave_duplicate_tool_name
- caveman agent: subagent maxInputChars must be a positive int
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/8bbd7a76df744dc4.
Report an issue: GitHub.