JuliusBrussee/caveman · error

cave_sandbox_unknown_tool

cave_sandbox_unknown_tool

Error message

cave_sandbox_unknown_tool

What it means

At the end of the subagent path walk, the worker filters the final definition's tools for request.tool. If the filter does not yield exactly one match, or the single match is itself a subagent-runtime tool (which cannot be executed directly here), it throws `cave_sandbox_unknown_tool`. The resolved agent must own exactly one directly executable tool with the requested name.

Solutions

  1. Invoke the tool on the agent definition that actually declares it — add the tool to the leaf subagent if it should own it.
  2. Make tool names unique within each agent definition to eliminate the ambiguous multi-match case.
  3. Target a concrete executable tool; do not address a subagent delegation tool directly as the executed tool.
  4. Re-run the build after renaming tools so the parent's request reflects the current definitions.

Example fix

// before
const child = agent("child").tool(subagent("delegate", grandchild)).build();
await invoke(child, "search"); // search not on child

// after
const child = agent("child").tool(subagent("delegate", grandchild)).tool(search).build();
await invoke(child, "search");
Defensive patterns

Strategy: validation

Validate before calling

function toolIsDirectlyInvokable(def: AgentDefinition, name: string): boolean {
  const matches = def.tools.filter((t) => t.name === name);
  return matches.length === 1 && matches[0]!.runtime?.kind !== "subagent";
}
// assert toolIsDirectlyInvokable(leafDefinition, requestedTool) before invoking

Type guard

const isExecutableTool = (t: unknown): t is { name: string; runtime?: { kind?: string } } =>
  typeof t === "object" && t !== null &&
  typeof (t as any).name === "string" &&
  (t as any).runtime?.kind !== "subagent";

Try / catch

try {
  await invokeTool(agent, toolName);
} catch (err) {
  if (err instanceof Error && err.message.includes("cave_sandbox_unknown_tool")) {
    console.error(`tool '${toolName}' is not a unique executable tool on the leaf agent`);
  }
  throw err;
}

Prevention

When it happens

Trigger: request.tool is not present on the final (leaf) agent definition, appears more than once (duplicate tool names), or names a subagent delegation tool instead of a concrete executable tool.

Common situations: Requesting a root-level tool by name on a subagent that does not have it (tools are not inherited down the graph); duplicate registration of the same tool name in the leaf agent; mistakenly targeting `invoke_agent`/a subagent tool directly; tool renamed after the parent hashed its digest.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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

Appendix: source

Thrown at packages/agent/src/tool-worker.ts:127

    throw new Error("cave_sandbox_definition_mismatch");
  }
  const visited = new Set<AgentDefinition>([definition]);
  for (const name of request.agentPath) {
    const delegated = definition.tools.filter((item) =>
      item.name === name && item.runtime?.kind === "subagent"
    );
    if (delegated.length !== 1) throw new Error("cave_sandbox_unknown_subagent");
    const child = delegated[0]!.runtime!.definition as AgentDefinition;
    if (!child || child.kind !== "agent") {
      throw new Error("cave_sandbox_subagent_definition_invalid");
    }
    if (visited.has(child)) throw new Error("cave_sandbox_subagent_cycle");
    visited.add(child);
    definition = child;
  }
  const selectedTools = definition.tools.filter((item) => item.name === request.tool);
  if (selectedTools.length !== 1 || selectedTools[0]!.runtime?.kind === "subagent") {
    throw new Error("cave_sandbox_unknown_tool");
  }
  const selected = selectedTools[0]!;
  if (toolDefinitionSHA256(selected) !== request.toolDefinitionSha256) {
    throw new Error("cave_sandbox_tool_definition_mismatch");
  }
  if (selected.effect !== "read" && request.allowSideEffects !== true) {
    throw new Error("cave_sandbox_side_effect_denied");
  }
  const value = await selected.execute(request.params as never, AbortSignal.timeout(selected.timeoutMs));
  writeResult({ ok: true, value });
} catch (error) {
  writeResult({ ok: false, code: failureCode(error) });
}

View on GitHub (pinned to 3ee70a1026)