{"record":{"id":"d0f457a5d1185db3","repo":"JuliusBrussee/caveman","slug":"cave-agent-definition-invalid","errorCode":"cave_agent_definition_invalid","errorMessage":"cave_agent_definition_invalid","messagePattern":"cave_agent_definition_invalid","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/definition-graph.ts","lineNumber":20,"sourceCode":"\nconst TOOL_IMPLEMENTATION_SOURCE = Symbol.for(\n  \"@caveman-ai/agent:tool-implementation-source\",\n);\n\nexport function validateAgentGraph(root: AgentDefinition): void {\n  // Memoize per inherited containment posture: the same child definition\n  // reached under a sandbox-required ancestor must be re-checked, not skipped.\n  const visited = [new Set<AgentDefinition>(), new Set<AgentDefinition>()];\n  const active = new Set<AgentDefinition>();\n\n  const visit = (\n    definition: AgentDefinition,\n    depth: number,\n    sandboxRequired: boolean,\n  ): void => {\n    if (!definition || definition.kind !== \"agent\" ||\n        !Array.isArray(definition.tools)) {\n      throw new Error(\"cave_agent_definition_invalid\");\n    }\n    if (depth > 8) throw new Error(\"cave_subagent_depth_limit\");\n    if (active.has(definition)) throw new Error(\"cave_subagent_definition_cycle\");\n    // Host mode is an opt-in the root makes for itself. A descendant cannot use\n    // it to run closures outside an ancestor's required containment.\n    if (sandboxRequired && definition.sandbox === \"host\") {\n      throw new Error(\"cave_host_sandbox_nested_under_required\");\n    }\n    const memo = visited[sandboxRequired ? 1 : 0]!;\n    if (memo.has(definition)) return;\n    active.add(definition);\n    const childSandboxRequired = sandboxRequired ||\n      definition.sandbox === \"required\";\n    const names = new Set<string>();\n    for (const declared of definition.tools) {\n      if (!declared || declared.kind !== \"tool\" ||\n          typeof declared.name !== \"string\") {\n        throw new Error(\"cave_tool_definition_invalid\");","sourceCodeStart":2,"sourceCodeEnd":38,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/definition-graph.ts#L2-L38","documentation":"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.","triggerScenarios":"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.","commonSituations":"Dynamically assembled definitions from config; spreading a ToolDefinition into an AgentDefinition slot; deserialized definitions from unvalidated JSON.","solutions":["Ensure the object passed at every agent slot has { kind: 'agent', name, tools: [...], ... } with tools as a real array","Add a type guard on dynamically loaded definitions before registering them (see defense section)","Validate definitions deserialized from JSON against the expected shape before graph construction"],"exampleFix":"// before\nregisterAgent({ kind: \"tool\", name: \"helper\", tools: {} });\n\n// after\nregisterAgent({ kind: \"agent\", name: \"helper\", tools: [someTool] });","handlingStrategy":"type-guard","validationCode":"function assertAgentDefinition(d: unknown): void {\n  if (!d || typeof d !== \"object\" || (d as any).kind !== \"agent\" || !Array.isArray((d as any).tools)) {\n    throw new Error(`not a valid AgentDefinition: ${JSON.stringify(d)?.slice(0, 80)}`);\n  }\n}","typeGuard":"function isAgentDefinition(d: unknown): d is { kind: \"agent\"; tools: unknown[] } {\n  return typeof d === \"object\" && d !== null &&\n    (d as { kind?: unknown }).kind === \"agent\" &&\n    Array.isArray((d as { tools?: unknown }).tools);\n}","tryCatchPattern":null,"preventionTips":["Type-guard every dynamically loaded or deserialized definition before registration","Use the library's agent() factory instead of object literals","Watch for accidental spread of a ToolDefinition into an AgentDefinition slot"],"tags":["validation","agent-definition","graph","tool-input"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}