Skip to content

Commit b5c711b

Browse files
graysonhicksclaude
andauthored
fix: propagate requestContext to workflow in networkLoop (#12379)
## Description Fixed issue #12330 where requestContext was not being propagated to the internal workflow's stream() and resumeStream() calls in networkLoop(). This caused workflow_run spans to lack metadata (userId, resourceId), breaking observability pipelines that rely on span metadata for billing, analytics, and audit logging. The fix adds requestContext to both run.stream() and run.resumeStream() calls when creating MastraAgentNetworkStream. ## Related Issue(s) Fixes #12330 ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [ ] Code refactoring - [ ] Performance improvement - [ ] Test update ## Checklist - [x] I have added tests that prove my fix is effective or that my feature works <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Propagates request context across network workflows so tool executions inherit user and resource metadata; workflow run spans now include user and resource identifiers for improved tracing and auditability. * Stream resumption consistently applies the request context. * **Tests** * Added coverage verifying request context propagation through network workflows and tools. * **Documentation** * Changelog updated to document the fix and observability improvements. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 11c374f commit b5c711b

File tree

3 files changed

+93
-0
lines changed

3 files changed

+93
-0
lines changed

.changeset/deep-laws-sort.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@mastra/core': patch
3+
---
4+
5+
Fixed agent.network() to properly pass requestContext to workflow runs. Workflow execution now includes user metadata (userId, resourceId) for observability and analytics. (Fixes #12330)

packages/core/src/agent/agent-network.test.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5621,3 +5621,89 @@ describe('Agent - network - output processors', () => {
56215621
).toBeGreaterThan(0);
56225622
});
56235623
});
5624+
5625+
describe('Agent - network - requestContext propagation (issue #12330)', () => {
5626+
it('should propagate requestContext to tools executed within the network', async () => {
5627+
const memory = new MockMemory();
5628+
const capturedRequestContext: { userId?: string; resourceId?: string } = {};
5629+
5630+
const contextCaptureTool = createTool({
5631+
id: 'context-capture-tool-12330',
5632+
description: 'A tool that captures requestContext values for testing',
5633+
inputSchema: z.object({ message: z.string() }),
5634+
execute: async ({ message }, context) => {
5635+
capturedRequestContext.userId = context?.requestContext?.get('userId') as string | undefined;
5636+
capturedRequestContext.resourceId = context?.requestContext?.get('resourceId') as string | undefined;
5637+
return { result: `Captured for: ${message}` };
5638+
},
5639+
});
5640+
5641+
const routingSelectTool = JSON.stringify({
5642+
primitiveId: 'context-capture-tool-12330',
5643+
primitiveType: 'tool',
5644+
prompt: JSON.stringify({ message: 'test' }),
5645+
selectionReason: 'Testing requestContext propagation',
5646+
});
5647+
5648+
const completionResponse = JSON.stringify({
5649+
isComplete: true,
5650+
finalResult: 'Done',
5651+
completionReason: 'Tool executed',
5652+
});
5653+
5654+
let callCount = 0;
5655+
const mockModel = new MockLanguageModelV2({
5656+
doGenerate: async () => {
5657+
callCount++;
5658+
const text = callCount === 1 ? routingSelectTool : completionResponse;
5659+
return {
5660+
rawCall: { rawPrompt: null, rawSettings: {} },
5661+
finishReason: 'stop',
5662+
usage: { inputTokens: 10, outputTokens: 20, totalTokens: 30 },
5663+
content: [{ type: 'text', text }],
5664+
warnings: [],
5665+
};
5666+
},
5667+
doStream: async () => {
5668+
callCount++;
5669+
const text = callCount === 1 ? routingSelectTool : completionResponse;
5670+
return {
5671+
stream: convertArrayToReadableStream([
5672+
{ type: 'stream-start', warnings: [] },
5673+
{ type: 'response-metadata', id: 'id-0', modelId: 'mock-model-id', timestamp: new Date(0) },
5674+
{ type: 'text-delta', id: 'id-0', delta: text },
5675+
{ type: 'finish', finishReason: 'stop', usage: { inputTokens: 10, outputTokens: 20, totalTokens: 30 } },
5676+
]),
5677+
};
5678+
},
5679+
});
5680+
5681+
const networkAgent = new Agent({
5682+
id: 'test-agent-12330',
5683+
name: 'RequestContext Test Agent',
5684+
instructions: 'Use the context-capture-tool-12330 when asked.',
5685+
model: mockModel,
5686+
tools: { 'context-capture-tool-12330': contextCaptureTool },
5687+
memory,
5688+
});
5689+
5690+
const requestContext = new RequestContext<{ userId: string; resourceId: string }>();
5691+
requestContext.set('userId', 'network-user-12330');
5692+
requestContext.set('resourceId', 'network-resource-12330');
5693+
5694+
const anStream = await networkAgent.network('Test requestContext propagation', {
5695+
requestContext,
5696+
memory: {
5697+
thread: 'test-thread-12330',
5698+
resource: 'test-resource-12330',
5699+
},
5700+
});
5701+
5702+
for await (const _chunk of anStream) {
5703+
// consume
5704+
}
5705+
5706+
expect(capturedRequestContext.userId).toBe('network-user-12330');
5707+
expect(capturedRequestContext.resourceId).toBe('network-resource-12330');
5708+
});
5709+
});

packages/core/src/loop/network/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2260,6 +2260,7 @@ export async function networkLoop<OUTPUT = undefined>({
22602260
if (resumeDataToUse) {
22612261
return run.resumeStream({
22622262
resumeData: resumeDataToUse,
2263+
requestContext,
22632264
}).fullStream;
22642265
}
22652266
return run.stream({
@@ -2274,6 +2275,7 @@ export async function networkLoop<OUTPUT = undefined>({
22742275
isOneOff: false,
22752276
verboseIntrospection: true,
22762277
},
2278+
requestContext,
22772279
}).fullStream;
22782280
},
22792281
});

0 commit comments

Comments
 (0)