{"record":{"id":"605b0a21ff21a20d","repo":"JuliusBrussee/caveman","slug":"cave-sandbox-subagent-cycle","errorCode":"cave_sandbox_subagent_cycle","errorMessage":"cave_sandbox_subagent_cycle","messagePattern":"cave_sandbox_subagent_cycle","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/tool-worker.ts","lineNumber":121,"sourceCode":"  if (request.allowNetwork !== true) installNetworkDeny();\n  const imported = await import(request.entry) as { default?: AgentDefinition; agent?: AgentDefinition };\n  let definition = imported.default ?? imported.agent;\n  if (!definition || definition.kind !== \"agent\") throw new Error(\"cave_sandbox_agent_export_missing\");\n  validateAgentGraph(definition);\n  if (agentDefinitionSHA256(definition) !== request.rootDefinitionSha256) {\n    throw new Error(\"cave_sandbox_definition_mismatch\");\n  }\n  const visited = new Set<AgentDefinition>([definition]);\n  for (const name of request.agentPath) {\n    const delegated = definition.tools.filter((item) =>\n      item.name === name && item.runtime?.kind === \"subagent\"\n    );\n    if (delegated.length !== 1) throw new Error(\"cave_sandbox_unknown_subagent\");\n    const child = delegated[0]!.runtime!.definition as AgentDefinition;\n    if (!child || child.kind !== \"agent\") {\n      throw new Error(\"cave_sandbox_subagent_definition_invalid\");\n    }\n    if (visited.has(child)) throw new Error(\"cave_sandbox_subagent_cycle\");\n    visited.add(child);\n    definition = child;\n  }\n  const selectedTools = definition.tools.filter((item) => item.name === request.tool);\n  if (selectedTools.length !== 1 || selectedTools[0]!.runtime?.kind === \"subagent\") {\n    throw new Error(\"cave_sandbox_unknown_tool\");\n  }\n  const selected = selectedTools[0]!;\n  if (toolDefinitionSHA256(selected) !== request.toolDefinitionSha256) {\n    throw new Error(\"cave_sandbox_tool_definition_mismatch\");\n  }\n  if (selected.effect !== \"read\" && request.allowSideEffects !== true) {\n    throw new Error(\"cave_sandbox_side_effect_denied\");\n  }\n  const value = await selected.execute(request.params as never, AbortSignal.timeout(selected.timeoutMs));\n  writeResult({ ok: true, value });\n} catch (error) {\n  writeResult({ ok: false, code: failureCode(error) });","sourceCodeStart":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/agent/src/tool-worker.ts#L103-L139","documentation":"While descending the subagent path, the worker keeps a `visited` set of definitions starting with the root. If the next child definition was already visited, the graph contains a delegation cycle, and the worker throws `cave_sandbox_subagent_cycle`. Agent graphs must be acyclic so termination and containment are provable.","triggerScenarios":"Agent A's subagent tool delegates to B, and B (directly or transitively) delegates back to A, so the worker encounters a definition it has already visited while walking request.agentPath.","commonSituations":"Two agents defined in one module referencing each other's builders; a shared helper closing over a parent agent and reused as a child; refactor introducing mutual delegation (A→B→A) that previously worked at shallower depth; recursive 'orchestrator' patterns mistakenly built as static subagents.","solutions":["Break the cycle: extract shared logic into a plain tool both agents use instead of cross-delegating.","Restructure so delegation forms a DAG — a child must never reference (directly or transitively) an ancestor.","If recursion is needed, implement it as runtime-level repeated invocation of a single-level graph, not a cyclic static definition.","Audit the definitions in the cycle and remove the back-reference to the ancestor."],"exampleFix":"// before\nconst a = agent(\"a\").tool(subagent(\"to_b\", b)).build();\nconst b = agent(\"b\").tool(subagent(\"to_a\", a)).build();\n\n// after\nconst a = agent(\"a\").tool(subagent(\"to_b\", b)).build();\nconst b = agent(\"b\").tool(sharedHelperTool).build();","handlingStrategy":"validation","validationCode":"function assertAcyclic(root: AgentDefinition): void {\n  const visiting = new Set<AgentDefinition>();\n  const done = new Set<AgentDefinition>();\n  const walk = (d: AgentDefinition): void => {\n    if (visiting.has(d)) throw new Error(\"subagent delegation cycle detected\");\n    if (done.has(d)) return;\n    visiting.add(d);\n    for (const t of d.tools) {\n      if (t.runtime?.kind === \"subagent\") walk(t.runtime.definition as AgentDefinition);\n    }\n    visiting.delete(d);\n    done.add(d);\n  };\n  walk(root);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await run(agentGraph);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"cave_sandbox_subagent_cycle\")) {\n    console.error(\"agent graph has a delegation cycle — restructure so children never reference ancestors\");\n  }\n  throw err;\n}","preventionTips":["Design subagent graphs strictly as DAGs: a child must never delegate to any ancestor.","Extract shared logic into plain tools instead of cross-delegating between agents.","Implement recursion via repeated runtime invocation of a single-level graph, not cyclic definitions.","Run an acyclicity check (validateAgentGraph) in CI on every agent graph."],"tags":["sandbox","subagent","cycle","graph"],"backgroundTag":"invalid-state-transition","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}