JuliusBrussee/caveman · error
A dynamic Strands scope requires the native agent…
Error message
A dynamic Strands scope requires the native agent registration
What it means
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.
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).
Example fix
// before
const model = cavemanStrands({ runtime, scope: (agent) => `agent:${agent.id}` });
await model.stream(...); // throws
// after
const model = cavemanStrands({ runtime, scope: (agent) => `agent:${agent.id}` } as any);
model.initAgent(myStrandsAgent);
await model.stream(...); Defensive patterns
Strategy: type-guard
Validate before calling
// Before streaming with a dynamic scope:
const bundle = createCavemanStrandsModel(options);
bundle.initAgent(agent); // must precede any stream that resolves a function-valued scope
if (typeof options.scope === 'function' && !bundle.isBound()) throw new Error('call initAgent before streaming'); Type guard
const canResolveDynamicScope = (b: { bound(o?: unknown): boolean }, opts: { scope: unknown }) =>
typeof opts.scope !== 'function' || b.bound(); Try / catch
try {
await model.stream(messages, opts);
} catch (e) {
if (e instanceof Error && /native agent registration/.test(e.message)) {
console.error('Register the wrapper with initAgent(agent) before using a dynamic scope.');
throw e;
}
throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- Create a separate Caveman Strands bundle for each agent
- Create a separate Caveman Strands bundle for each agent
- recovery_unavailable
- cave_budget_cap_breached
- cave_budget_controller_in_use
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/e29aeeac987f95c2.
Report an issue: GitHub.
Appendix: source
Thrown at packages/middleware/typescript/src/strands.ts:33
cache_read_tokens:count(value.cacheReadInputTokens),cache_write_tokens:count(value.cacheWriteInputTokens),reasoning_tokens:null};
}
/** Native streamAggregated and agent orchestration remain in Strands. */
export class CavemanStrandsModel<T extends BaseModelConfig=BaseModelConfig> extends Model<T>{
registration:StrandsRegistration|null=null;
private readonly versionSupported:boolean;
constructor(readonly inner:Model<T>,readonly options:StrandsOptions){
super();this.versionSupported=adapterCompatible('strands');
if(!this.versionSupported&&options.runtime.mode!=='off')options.runtime.decline('unsupported_version');
}
override get stateful(){return this.inner.stateful;}
updateConfig(config:T){this.inner.updateConfig(config);}
getConfig():T{return this.inner.getConfig();}
override countTokens(messages:Message[],options?:CountTokensOptions){return this.inner.countTokens(messages,options);}
private scope():Scope{
if(typeof this.options.scope!=='function')return this.options.scope;
const agent=this.registration?.agent?.deref();
if(!agent)throw new Error('A dynamic Strands scope requires the native agent registration');
return this.options.scope(agent);
}
private async prepare(messages:Message[],options:StreamOptions):Promise<{messages:Message[];attempt:Attempt|null}>{
if(options.cancelSignal?.aborted&&!currentOwner())this.options.runtime.report(null,{reason:'cancelled',adapter:adapter.id});
options.cancelSignal?.throwIfAborted();
if(currentOwner())return {messages,attempt:null};
const passive=(reason:string)=>({messages,attempt:{runtime:this.options.runtime,
scope:{namespace:'caveman-report',session_id:'passive',branch_id:'main',cache_epoch:'0'},
logicalCallId:crypto.randomUUID(),attemptId:crypto.randomUUID(),optimization:null,wireSHA256:null,passive:true,reason,adapter:adapter.id}});
if(this.options.runtime.mode==='off')return passive('disabled');
if(!this.versionSupported)return passive('unsupported_version');
if(this.stateful)return passive('opaque_context');
const context=await manifest([{system:options.systemPrompt??null},...messages.map(m=>m.toJSON())]);
if(!context)return passive('unsupported_shape');
const names=new Map<string,string>();
for(const message of messages)for(const block of message.content)if(block.type==='toolUseBlock')names.set(block.toolUseId,block.name);
const candidates:Candidate[]=[],paths=new Map<string,{mi:number;bi:number;pi:number}>();View on GitHub (pinned to 3ee70a1026)