JuliusBrussee/caveman · error · Error

cave_agent_definition_invalid

cave_agent_definition_invalid

Error message

cave_agent_definition_invalid

What it means

While walking the agent definition graph, each AgentDefinition must be a truthy object with kind === 'agent' and a tools array. A malformed definition — null, wrong kind, or tools not an array — is rejected before any traversal, because every later guarantee (name uniqueness, sandbox inheritance) assumes this shape.

Source

Thrown at packages/agent/src/definition-graph.ts:20

const TOOL_IMPLEMENTATION_SOURCE = Symbol.for(
  "@caveman-ai/agent:tool-implementation-source",
);

export function validateAgentGraph(root: AgentDefinition): void {
  // Memoize per inherited containment posture: the same child definition
  // reached under a sandbox-required ancestor must be re-checked, not skipped.
  const visited = [new Set<AgentDefinition>(), new Set<AgentDefinition>()];
  const active = new Set<AgentDefinition>();

  const visit = (
    definition: AgentDefinition,
    depth: number,
    sandboxRequired: boolean,
  ): void => {
    if (!definition || definition.kind !== "agent" ||
        !Array.isArray(definition.tools)) {
      throw new Error("cave_agent_definition_invalid");
    }
    if (depth > 8) throw new Error("cave_subagent_depth_limit");
    if (active.has(definition)) throw new Error("cave_subagent_definition_cycle");
    // Host mode is an opt-in the root makes for itself. A descendant cannot use
    // it to run closures outside an ancestor's required containment.
    if (sandboxRequired && definition.sandbox === "host") {
      throw new Error("cave_host_sandbox_nested_under_required");
    }
    const memo = visited[sandboxRequired ? 1 : 0]!;
    if (memo.has(definition)) return;
    active.add(definition);
    const childSandboxRequired = sandboxRequired ||
      definition.sandbox === "required";
    const names = new Set<string>();
    for (const declared of definition.tools) {
      if (!declared || declared.kind !== "tool" ||
          typeof declared.name !== "string") {
        throw new Error("cave_tool_definition_invalid");

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Ensure the object passed at every agent slot has { kind: 'agent', name, tools: [...], ... } with tools as a real array
  2. Add a type guard on dynamically loaded definitions before registering them (see defense section)
  3. Validate definitions deserialized from JSON against the expected shape before graph construction

Example fix

// before
registerAgent({ kind: "tool", name: "helper", tools: {} });

// after
registerAgent({ kind: "agent", name: "helper", tools: [someTool] });
Defensive patterns

Strategy: type-guard

Validate before calling

function assertAgentDefinition(d: unknown): void {
  if (!d || typeof d !== "object" || (d as any).kind !== "agent" || !Array.isArray((d as any).tools)) {
    throw new Error(`not a valid AgentDefinition: ${JSON.stringify(d)?.slice(0, 80)}`);
  }
}

Type guard

function isAgentDefinition(d: unknown): d is { kind: "agent"; tools: unknown[] } {
  return typeof d === "object" && d !== null &&
    (d as { kind?: unknown }).kind === "agent" &&
    Array.isArray((d as { tools?: unknown }).tools);
}

Prevention

When it happens

Trigger: Passing an AgentDefinition that is null/undefined, has kind 'tool' or a typo'd kind, or whose tools field is missing, a single object, or a non-array; commonly a tool object accidentally used where an agent definition was expected.

Common situations: Dynamically assembled definitions from config; spreading a ToolDefinition into an AgentDefinition slot; deserialized definitions from unvalidated JSON.

Related errors


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