{"record":{"id":"242812b7d7c554dd","repo":"JuliusBrussee/caveman","slug":"create-a-separate-caveman-strands-bundle-for-each-agent","errorCode":null,"errorMessage":"Create a separate Caveman Strands bundle for each agent","messagePattern":"Create a separate Caveman Strands bundle for each agent","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/middleware/typescript/src/strands.ts","lineNumber":110,"sourceCode":"      }\n    }catch(error){done=true;observe(attempt,options.cancelSignal?.aborted?'cancelled':'failed');throw error;}\n    finally{if(!done)observe(attempt,'cancelled');await withOwner(attempt,()=>iterator.return?.());}\n  }\n}\n\nclass StrandsRegistration implements Plugin{\n  readonly name='caveman:middleware';\n  agent:WeakRef<LocalAgent>|null=null;\n  readonly recoveryTool:FunctionTool;\n  constructor(readonly options:StrandsOptions){\n    this.recoveryTool=new FunctionTool({name:'caveman_retrieve',description:recoveryToolDescription,inputSchema:{...structuredClone(recoveryInputSchema),required:[...recoveryInputSchema.required]} as NonNullable<FunctionToolConfig['inputSchema']>,\n      callback:async(input,context)=>{\n        const scope=typeof options.scope==='function'?options.scope(context.agent):options.scope;\n        return JSON.stringify(await options.runtime.retrieve(scope,input as RetrieveArgs,context.cancelSignal));\n      }});\n  }\n  initAgent(agent:LocalAgent){\n    if(this.agent&&this.agent.deref()!==agent)throw new Error('Create a separate Caveman Strands bundle for each agent');\n    this.agent=new WeakRef(agent);\n    // Registration runs after the host discovers its own tools. A collision\n    // keeps the host's tool and prevents this model wrapper from using lossiness.\n    if(this.options.runtime.mode==='compress'&&!agent.toolRegistry.get('caveman_retrieve'))agent.toolRegistry.add(this.recoveryTool);\n  }\n  bound(options:StreamOptions):boolean{\n    const agent=this.agent?.deref(),matches=options.toolSpecs?.filter(t=>t.name==='caveman_retrieve')??[];\n    return agent?.toolRegistry.get('caveman_retrieve')===this.recoveryTool&&matches.length===1&&JSON.stringify(matches[0])===JSON.stringify(this.recoveryTool.toolSpec);\n  }\n}\n\nexport function withCavemanStrandsModel<T extends BaseModelConfig>(model:Model<T>,options:StrandsOptions):Model<T>{\n  return new CavemanStrandsModel(model,options);\n}\n\nexport function withCavemanStrands(input:AgentConfig&{model:Model},options:StrandsOptions):AgentConfig{\n  const model=new CavemanStrandsModel(input.model,options);\n  if(options.runtime.mode==='off'||!adapterCompatible('strands'))return {...input,model};","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/middleware/typescript/src/strands.ts#L92-L128","documentation":"Each Caveman Strands bundle keeps a WeakRef to exactly one native agent and registers its recovery tool on that agent. `initAgent` in packages/middleware/typescript/src/strands.ts:110 throws this Error if the bundle is re-initialized with a different agent than the one it already holds, since recovery tool registration and dynamic scope resolution are per-agent.","triggerScenarios":"Calling `bundle.initAgent(agentB)` after `bundle.initAgent(agentA)` where agentA !== agentB.","commonSituations":"A shared wrapper instance passed to multiple agents in an agent-pool or multi-agent app; a caching layer reusing one bundle across sessions; accidentally reusing a module-level singleton bundle.","solutions":["Create a new Caveman Strands bundle (factory call) for each agent.","Route each agent through its own wrapper instance rather than sharing a singleton.","If the same agent object is intentionally re-registered, ensure identity matches (deref comparison passes)."],"exampleFix":"// before\nconst shared = cavemanStrands({...});\nagentA.model = shared; agentB.model = shared; // throws on second initAgent\n// after\nagentA.model = cavemanStrands({...});\nagentB.model = cavemanStrands({...});","handlingStrategy":"validation","validationCode":"const bundles = new WeakMap<object, Bundle>();\nfunction bundleFor(agent: LocalAgent, make: () => Bundle): Bundle {\n  let b = bundles.get(agent);\n  if (!b) { b = make(); b.initAgent(agent); bundles.set(agent, b); }\n  return b;\n}","typeGuard":"const isFreshBundle = (b: Bundle, agent: LocalAgent) => (b as any).agent === undefined || (b as any).agent.deref() === agent;","tryCatchPattern":"try {\n  bundle.initAgent(agent);\n} catch (e) {\n  if (e instanceof Error && /separate Caveman Strands bundle/.test(e.message)) {\n    bundle = cavemanStrands(options); // create a per-agent bundle\n    bundle.initAgent(agent);\n  } else throw e;\n}","preventionTips":["Never share a Strands model wrapper across agents; use a factory per agent.","Avoid module-level singleton wrappers in multi-agent apps.","Track bundle ownership in a WeakMap keyed by the agent."],"tags":["strands","lifecycle","multi-agent","misuse"],"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"}