{"record":{"id":"e29aeeac987f95c2","repo":"JuliusBrussee/caveman","slug":"a-dynamic-strands-scope-requires-the-native-agent","errorCode":null,"errorMessage":"A dynamic Strands scope requires the native agent registration","messagePattern":"A dynamic Strands scope requires the native agent registration","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/middleware/typescript/src/strands.ts","lineNumber":33,"sourceCode":"    cache_read_tokens:count(value.cacheReadInputTokens),cache_write_tokens:count(value.cacheWriteInputTokens),reasoning_tokens:null};\n}\n\n/** Native streamAggregated and agent orchestration remain in Strands. */\nexport class CavemanStrandsModel<T extends BaseModelConfig=BaseModelConfig> extends Model<T>{\n  registration:StrandsRegistration|null=null;\n  private readonly versionSupported:boolean;\n  constructor(readonly inner:Model<T>,readonly options:StrandsOptions){\n    super();this.versionSupported=adapterCompatible('strands');\n    if(!this.versionSupported&&options.runtime.mode!=='off')options.runtime.decline('unsupported_version');\n  }\n  override get stateful(){return this.inner.stateful;}\n  updateConfig(config:T){this.inner.updateConfig(config);}\n  getConfig():T{return this.inner.getConfig();}\n  override countTokens(messages:Message[],options?:CountTokensOptions){return this.inner.countTokens(messages,options);}\n  private scope():Scope{\n    if(typeof this.options.scope!=='function')return this.options.scope;\n    const agent=this.registration?.agent?.deref();\n    if(!agent)throw new Error('A dynamic Strands scope requires the native agent registration');\n    return this.options.scope(agent);\n  }\n\n  private async prepare(messages:Message[],options:StreamOptions):Promise<{messages:Message[];attempt:Attempt|null}>{\n    if(options.cancelSignal?.aborted&&!currentOwner())this.options.runtime.report(null,{reason:'cancelled',adapter:adapter.id});\n    options.cancelSignal?.throwIfAborted();\n    if(currentOwner())return {messages,attempt:null};\n    const passive=(reason:string)=>({messages,attempt:{runtime:this.options.runtime,\n      scope:{namespace:'caveman-report',session_id:'passive',branch_id:'main',cache_epoch:'0'},\n      logicalCallId:crypto.randomUUID(),attemptId:crypto.randomUUID(),optimization:null,wireSHA256:null,passive:true,reason,adapter:adapter.id}});\n    if(this.options.runtime.mode==='off')return passive('disabled');\n    if(!this.versionSupported)return passive('unsupported_version');\n    if(this.stateful)return passive('opaque_context');\n    const context=await manifest([{system:options.systemPrompt??null},...messages.map(m=>m.toJSON())]);\n    if(!context)return passive('unsupported_shape');\n    const names=new Map<string,string>();\n    for(const message of messages)for(const block of message.content)if(block.type==='toolUseBlock')names.set(block.toolUseId,block.name);\n    const candidates:Candidate[]=[],paths=new Map<string,{mi:number;bi:number;pi:number}>();","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/middleware/typescript/src/strands.ts#L15-L51","documentation":"When a Strands bundle is configured with a dynamic `scope` (a function), the model wrapper must resolve that function against the native Strands agent instance it is registered with. `private scope()` in packages/middleware/typescript/src/strands.ts:33 dereferences a stored WeakRef; if the agent was never registered via `initAgent` or has been garbage-collected, it throws this Error instead of calling the scope callback with undefined.","triggerScenarios":"Using a function-valued `options.scope` while streaming before `initAgent(agent)` has been called, or after the WeakRef target was collected; constructing a wrapper and using it detached from any agent.","commonSituations":"Dynamic scopes driven by agent context but the wrapper is shared/reused across agents without registration; long-lived wrappers where the agent became unreachable; forgetting to wire `initAgent` in a custom host.","solutions":["Call `initAgent(agent)` on the bundle with the native Strands agent before streaming.","Keep a strong reference to the agent for the wrapper's lifetime so the WeakRef is not collected.","Use one bundle per agent (a static scope avoids the registration requirement entirely)."],"exampleFix":"// before\nconst model = cavemanStrands({ runtime, scope: (agent) => `agent:${agent.id}` });\nawait model.stream(...); // throws\n// after\nconst model = cavemanStrands({ runtime, scope: (agent) => `agent:${agent.id}` } as any);\nmodel.initAgent(myStrandsAgent);\nawait model.stream(...);","handlingStrategy":"type-guard","validationCode":"// Before streaming with a dynamic scope:\nconst bundle = createCavemanStrandsModel(options);\nbundle.initAgent(agent); // must precede any stream that resolves a function-valued scope\nif (typeof options.scope === 'function' && !bundle.isBound()) throw new Error('call initAgent before streaming');","typeGuard":"const canResolveDynamicScope = (b: { bound(o?: unknown): boolean }, opts: { scope: unknown }) =>\n  typeof opts.scope !== 'function' || b.bound();","tryCatchPattern":"try {\n  await model.stream(messages, opts);\n} catch (e) {\n  if (e instanceof Error && /native agent registration/.test(e.message)) {\n    console.error('Register the wrapper with initAgent(agent) before using a dynamic scope.');\n    throw e;\n  }\n  throw e;\n}","preventionTips":["Always call initAgent(agent) immediately after constructing the Strands model wrapper.","Keep a strong reference to the agent (store it on your host object) so the WeakRef stays live.","Prefer a static scope string when per-agent scopes are not needed."],"tags":["strands","weakref","lifecycle","dynamic-scope"],"backgroundTag":"missing-required-argument","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"}