JuliusBrussee/caveman · error
cave_sandbox_subagent_cycle
cave_sandbox_subagent_cycle
Error message
cave_sandbox_subagent_cycle
What it means
The worker keeps a `visited` Set of AgentDefinition objects (identity-based) as it descends agentPath. If the next child definition is reference-identical to one already on the current chain, the delegation graph contains a cycle and the worker aborts instead of recursing infinitely.
Source
Thrown at packages/agent/src/tool-worker.ts:121
if (request.allowNetwork !== true) installNetworkDeny();
const imported = await import(request.entry) as { default?: AgentDefinition; agent?: AgentDefinition };
let definition = imported.default ?? imported.agent;
if (!definition || definition.kind !== "agent") throw new Error("cave_sandbox_agent_export_missing");
validateAgentGraph(definition);
if (agentDefinitionSHA256(definition) !== request.rootDefinitionSha256) {
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 27d5a3981a)
Solutions
- Break the cycle: give each level a distinct definition object (wrap or clone) so the chain is a DAG, or terminate recursion with a leaf agent.
- Audit the graph with validateAgentGraph in the parent — cycle detection there catches this before spawn.
- If recursive behavior is intended, restructure so depth is bounded by distinct definitions per level rather than object reuse.
Example fix
// before
const a = agent({ tools: [subagent("b", b)] });
const b = agent({ tools: [subagent("a", a)] }); // a -> b -> a cycle
// after
const b = agent({ tools: [leafTool] }); // b terminates; recursion handled at orchestration layer Defensive patterns
Strategy: validation
Validate before calling
function pathIsAcyclic(root: AgentDefinition, path: string[]): boolean {
const visited = new Set<object>([root]);
let def = root;
for (const name of path) {
const child = def.tools.find((t) => t.name === name && t.runtime?.kind === "subagent")?.runtime?.definition;
if (!child || visited.has(child as object)) return false;
visited.add(child as object);
def = child as AgentDefinition;
}
return true;
} Prevention
- Model delegation graphs as DAGs; never reuse one definition instance twice on a chain.
- Rely on validateAgentGraph's recursive checks before spawning.
When it happens
Trigger: Agent A delegates to agent B, and B (or a deeper descendant) delegates back to the same A definition object; a self-referential definition that includes itself as a subagent; mutually recursive builder functions that return the same object instance.
Common situations: Composing recursive agent graphs (reviewer delegates to worker, worker back to reviewer) without introducing distinct wrapper definitions; a builder memoizing a definition and reusing the instance in two places on one chain.
Related errors
- cave_sandbox_unknown_subagent
- cave_sandbox_subagent_definition_invalid
- cave_subagent_depth_limit
- cave_host_sandbox_nested_under_required
- cave_sandbox_agent_export_missing
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/605b0a21ff21a20d.
Report an issue: GitHub.