JuliusBrussee/caveman · error

cave_subagent_runtime_missing

cave_subagent_runtime_missing

Error message

cave_subagent_runtime_missing

What it means

The runtime looks up toolDefinition.runtime and requires it to be a subagent-kind runtime descriptor before executing a subagent tool call. If runtime is missing entirely or has a kind other than "subagent", the tool definition is not wired as a subagent and execution cannot proceed, so this error is thrown. It indicates a tool-registration/definition defect rather than bad user input.

Solutions

  1. Ensure the tool definition is created via the library's subagent tool factory so runtime.kind === "subagent" is set
  2. Inspect toolDefinition.runtime before dispatch; only route tools with a subagent runtime into this path
  3. Upgrade/align library versions if tool definitions come from a plugin built against a different schema
  4. Verify the tool name resolves to the intended subagent definition, not a regular tool

Example fix

// before
defineTool({ name: "researcher", execute });
// after
defineTool({ name: "researcher", runtime: { kind: "subagent", maxInputChars: 100000, maxCalls: 5, maxTokens: 50000 }, execute });
Defensive patterns

Strategy: type-guard

Validate before calling

if (toolDefinition.runtime?.kind !== "subagent") {
  throw new Error(toolDefinition.name + " is not registered as a subagent tool");
}

Type guard

function isSubagentTool(t) {
  return t.runtime?.kind === "subagent";
}

Try / catch

try {
  await dispatchSubagent({ task });
} catch (e) {
  if (e.message === "cave_subagent_runtime_missing") {
    throw new Error("Tool is not wired as a subagent; check its ToolDefinition");
  } else throw e;
}

Prevention

When it happens

Trigger: Invoking a tool whose ToolDefinition was created without a runtime field, or whose runtime.kind is not "subagent", through the subagent execution path.

Common situations: Hand-built tool definitions missing the runtime descriptor; a plugin/extension passing a normal tool into the subagent runner; schema/refactor renaming the runtime field; version mismatch where tool definitions created by an older factory lack runtime.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


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

Appendix: 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: two

View on GitHub (pinned to 3ee70a1026)