JuliusBrussee/caveman · error · Error

cave_subagent_framework_runner_required

Error message

cave_subagent_framework_runner_required

What it means

The subagent tool factory returns a ToolDefinition whose runtime is { kind: "subagent", ... }; a host framework that understands that runtime replaces execute(). The stub execute() that throws cave_subagent_framework_runner_required exists so that running the tool through a plain tool executor (which would call execute directly) fails loudly instead of silently doing nothing.

Source

Thrown at packages/agent/src/index.ts:204

  }
  return tool({
    name: options.name,
    description: options.description,
    input: schema.object({ task: schema.string() }),
    effect: "read",
    result: "auto",
    ...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
    runtime: {
      kind: "subagent",
      definition: options.agent,
      maxInputChars,
      maxCalls,
      maxCostUsd,
      ...(options.maxTokens === undefined ? {} : { maxTokens: options.maxTokens }),
      maxContextTokens,
    },
    async execute() {
      throw new Error("cave_subagent_framework_runner_required");
    },
  });
}

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Run the tool through the caveman agent runtime that handles runtime.kind === "subagent" instead of calling execute() yourself
  2. If you need a plain executable tool, supply your own execute in the options rather than relying on the factory stub
  3. In tests, mock at the runtime boundary (the framework runner), not at execute()
  4. Check that @caveman-ai host packages (agent runner) are version-matched with this primitives package

Example fix

// before
const def = subagentTool({ agent, name: "worker" });
const out = await def.execute({ task: "do it" }); // throws cave_subagent_framework_runner_required

// after
// Register the definition with the agent runtime; the framework installs the real runner
const runner = createAgentRunner({ tools: [subagentTool({ agent, name: "worker" })] });
const out = await runner.callTool("worker", { task: "do it" });
Defensive patterns

Strategy: type-guard

Validate before calling

const def = subagentTool({ agent, name: "worker" });
if (def.runtime?.kind === "subagent") {
  // must be executed by the framework runner, never def.execute() directly
  registerWithAgentRuntime([def]);
} else {
  await def.execute(input); // safe only for plain tools
}

Type guard

const hasSubagentRuntime = (
  d: ToolDefinition,
): d is ToolDefinition & { runtime: { kind: "subagent" } } =>
  (d as { runtime?: { kind?: string } }).runtime?.kind === "subagent";

Try / catch

try {
  await def.execute(input);
} catch (e) {
  if (e instanceof Error && e.message === "cave_subagent_framework_runner_required") {
    throw new Error("subagent tools must run through the agent runtime, not execute()");
  }
  throw e;
}

Prevention

When it happens

Trigger: Registering the subagent tool with a generic tool-runner that invokes definition.execute() itself, rather than passing the ToolDefinition to the caveman agent runtime that recognizes the subagent runtime kind. Also triggered by test harnesses that call tool.execute() directly.

Common situations: Mixing primitives from this library into a custom executor, unit tests that stub the runtime layer and call execute, or upgrading the agent package while the host runner stays on an older version that predates subagent runtime support.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/c9278e87c68c6b05. Report an issue: GitHub.