@@ -6,6 +6,7 @@ import { z } from 'zod/v4';
66import { Mastra } from '../../mastra' ;
77import { MockMemory } from '../../memory/mock' ;
88import type { Processor , ProcessOutputResultArgs } from '../../processors/index' ;
9+ import { RequestContext , MASTRA_THREAD_ID_KEY , MASTRA_RESOURCE_ID_KEY } from '../../request-context' ;
910import { InMemoryStore } from '../../storage' ;
1011import { createTool } from '../../tools' ;
1112import { Agent } from '../agent' ;
@@ -3232,6 +3233,128 @@ describe('Supervisor Pattern - Message history transfer to sub-agents', () => {
32323233 }
32333234 } ) ;
32343235
3236+ it ( 'should isolate sub-agent memory when threadId and resourceId are set via requestContext reserved keys' , async ( ) => {
3237+ const subAgentMockModel = new MockLanguageModelV2 ( {
3238+ doGenerate : async ( ) => ( {
3239+ rawCall : { rawPrompt : null , rawSettings : { } } ,
3240+ finishReason : 'stop' ,
3241+ usage : { inputTokens : 5 , outputTokens : 10 , totalTokens : 15 } ,
3242+ content : [ { type : 'text' , text : 'Sub-agent response' } ] ,
3243+ warnings : [ ] ,
3244+ } ) ,
3245+ } ) ;
3246+
3247+ const memoryStore = new InMemoryStore ( ) ;
3248+ const subAgentMemory = new MockMemory ( { storage : memoryStore } ) ;
3249+
3250+ const subAgent = new Agent ( {
3251+ id : 'sub-agent-reserved-keys-test' ,
3252+ name : 'Sub Agent Reserved Keys Test' ,
3253+ description : 'A sub-agent for testing reserved key isolation' ,
3254+ instructions : 'Answer questions.' ,
3255+ model : subAgentMockModel ,
3256+ memory : subAgentMemory ,
3257+ } ) ;
3258+
3259+ let supervisorCallCount = 0 ;
3260+ const resourceId = randomUUID ( ) ;
3261+ const threadId = randomUUID ( ) ;
3262+
3263+ const supervisorAgent = new Agent ( {
3264+ id : 'supervisor-reserved-keys' ,
3265+ name : 'Supervisor Reserved Keys' ,
3266+ instructions : 'Delegate to sub-agent.' ,
3267+ model : new MockLanguageModelV2 ( {
3268+ doGenerate : async ( ) => {
3269+ supervisorCallCount ++ ;
3270+ if ( supervisorCallCount === 1 ) {
3271+ return {
3272+ rawCall : { rawPrompt : null , rawSettings : { } } ,
3273+ finishReason : 'tool-calls' as const ,
3274+ usage : { inputTokens : 10 , outputTokens : 20 , totalTokens : 30 } ,
3275+ text : '' ,
3276+ content : [
3277+ {
3278+ type : 'tool-call' as const ,
3279+ toolCallId : 'call-1' ,
3280+ toolName : 'agent-subAgent' ,
3281+ input : JSON . stringify ( { prompt : 'What is my name?' , threadId, resourceId } ) ,
3282+ } ,
3283+ ] ,
3284+ warnings : [ ] ,
3285+ } ;
3286+ }
3287+ return {
3288+ rawCall : { rawPrompt : null , rawSettings : { } } ,
3289+ finishReason : 'stop' as const ,
3290+ usage : { inputTokens : 10 , outputTokens : 20 , totalTokens : 30 } ,
3291+ content : [ { type : 'text' , text : 'Sub-agent says: Sub-agent response' } ] ,
3292+ warnings : [ ] ,
3293+ } ;
3294+ } ,
3295+ } ) ,
3296+ agents : { subAgent } ,
3297+ memory : new MockMemory ( ) ,
3298+ } ) ;
3299+
3300+ // Set reserved keys on requestContext (simulates middleware + body merge)
3301+ const requestContext = new RequestContext ( ) ;
3302+ requestContext . set ( MASTRA_THREAD_ID_KEY , threadId ) ;
3303+ requestContext . set ( MASTRA_RESOURCE_ID_KEY , resourceId ) ;
3304+
3305+ await supervisorAgent . generate ( [ { role : 'user' , content : 'What is my name?' } ] , {
3306+ maxSteps : 3 ,
3307+ requestContext,
3308+ memory : {
3309+ resource : resourceId ,
3310+ thread : threadId ,
3311+ } ,
3312+ } ) ;
3313+
3314+ // Sub-agent should have its own isolated thread, not the parent's
3315+ const subAgentResourceId = `${ resourceId } -subAgent` ;
3316+ const memoryStorage = await subAgentMemory . storage . getStore ( 'memory' ) ;
3317+ expect ( memoryStorage ) . toBeDefined ( ) ;
3318+
3319+ if ( memoryStorage ) {
3320+ const allThreadsResult = await memoryStorage . listThreads ( { filter : { resourceId : subAgentResourceId } } ) ;
3321+ const allThreads = allThreadsResult . threads ;
3322+
3323+ // Sub-agent should have its own thread
3324+ expect ( allThreads . length ) . toBeGreaterThan ( 0 ) ;
3325+
3326+ const subAgentThread = allThreads [ 0 ] ;
3327+ expect ( subAgentThread ) . toBeDefined ( ) ;
3328+
3329+ if ( subAgentThread ) {
3330+ // Sub-agent thread ID should NOT be the parent's thread ID
3331+ expect ( subAgentThread . id ) . not . toBe ( threadId ) ;
3332+
3333+ const subAgentMessages = await memoryStorage . listMessages ( {
3334+ threadId : subAgentThread . id ,
3335+ perPage : 100 ,
3336+ } ) ;
3337+
3338+ expect ( subAgentMessages . messages . length ) . toBeGreaterThanOrEqual ( 2 ) ;
3339+
3340+ // First message should be the delegation prompt
3341+ expect ( subAgentMessages . messages [ 0 ] . role ) . toBe ( 'user' ) ;
3342+ const userContent =
3343+ typeof subAgentMessages . messages [ 0 ] . content === 'string'
3344+ ? subAgentMessages . messages [ 0 ] . content
3345+ : JSON . stringify ( subAgentMessages . messages [ 0 ] . content ) ;
3346+ expect ( userContent ) . toContain ( 'What is my name' ) ;
3347+
3348+ // Second message should be the sub-agent's response
3349+ expect ( subAgentMessages . messages [ 1 ] . role ) . toBe ( 'assistant' ) ;
3350+ }
3351+ }
3352+
3353+ // Verify reserved keys are restored for the parent after sub-agent execution
3354+ expect ( requestContext . get ( MASTRA_THREAD_ID_KEY ) ) . toBe ( threadId ) ;
3355+ expect ( requestContext . get ( MASTRA_RESOURCE_ID_KEY ) ) . toBe ( resourceId ) ;
3356+ } ) ;
3357+
32353358 describe ( 'Sub-agent instructions merge' , ( ) => {
32363359 it ( 'should preserve sub-agent own instructions when parent LLM provides instructions via tool call' , async ( ) => {
32373360 const capturedSystemMessages : string [ ] = [ ] ;
0 commit comments