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
- 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
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
- 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
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
- caveman-code: old_string and new_string are identical
- option not found
- cave_harness_adapter_version_invalid
- cave_harness_model_invalid
- cave_harness_model_identity_missing
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/d0f457a5d1185db3.
Report an issue: GitHub.