Repository: mastra-ai/mastra
File: packages/core/src/agent/agent.ts (TypeScript)

Code before:

        const toolObj = createTool({
          id: `workflow-${workflowName}`,
          description: workflow.description || `Workflow: ${workflowName}`,
          inputSchema: extendedInputSchema,
          outputSchema: z.union([
            z.object({
              // @ts-expect-error - zod types mismatch between v3 and v4
              result: workflow.outputSchema,
              runId: z.string().describe('Unique identifier for the workflow run'),
            }),
            z.object({
              runId: z.string().describe('Unique identifier for the workflow run'),
              error: z.string().describe('Error message if workflow execution failed'),
            }),
          ]),
          mastra: this.#mastra,
          // manually wrap workflow tools with tracing, so that we can pass the
          // current tool span onto the workflow to maintain continuity of the trace
          // @ts-expect-error - context type mismatch in workflow tool
          execute: async (inputData, context) => {
            try {
              const { initialState, inputData: workflowInputData, suspendedToolRunId } = inputData as any;
              // Use a unique runId for each workflow tool call to prevent parallel calls
              // from sharing the same cached Run instance (see #13473).
              // For resume cases, suspendedToolRunId correctly identifies the existing run.
              const runIdToUse = suspendedToolRunId || randomUUID();
              this.logger.debug(`[Agent:${this.name}] - Executing workflow as tool ${workflowName}`, {
                name: workflowName,
                description: workflow.description,
                args: inputData,
                runId: runIdToUse,
                threadId,
                resourceId,
              });

              const run = await workflow.createRun({ runId: runIdToUse });
              const { resumeData, suspend } = context?.agent ?? {};

              let result: WorkflowResult<any, any, any, any> | undefined = undefined;

              if (methodType === 'generate' || methodType === 'generateLegacy') {
                if (resumeData) {
                  result = await run.resume({
                    resumeData,
                    requestContext,
                    tracingContext: context?.tracingContext,
                  });
                } else {
                  result = await run.start({
                    inputData: workflowInputData,

Review comment (suggestion):
this is only valid for when resuming via `autoResumeSuspendedTools` though. When you resume using `agent.resumeStream()` or `agent.resumeGenerate()`, `suspendedToolRunId` won't exist because the llm won't add it to the tool inputData. It's only added if `autoResumeSuspendedTools: true` .

I think you'll have to pass the `runId` from the workflowRun as one of the `suspendOptions` here
https://github.com/echoVic/mastra/blob/65b9f42e9d2babd2e573f024df1ed97f0e26bd90/packages/core/src/agent/agent.ts#L2848.

then in tool-call-step - https://github.com/mastra-ai/mastra/blob/death-to-agent-network/packages/core/src/loop/workflows/agentic-execution/tool-call-step.ts#L30, you'll have to make sure it's saved to memory and get it from there to pass to the tool args when resuming a workflow tool.

You can run the tool-approval.e2e.test.ts to confirm that workflow suspension and resumption works fine for both autoResume and manual resumption

Code after:
                fullSubAgentMessages = [userMessage, ...agentResponseMessages];

                // Save response messages to sub-agent's memory so the UI can display them
                const memory = await agent.getMemory({ requestContext });
                if (memory) {
                  try {
                    await memory.createThread({
                      resourceId: subAgentResourceId,
                      threadId: subAgentThreadId,
                    });

                    await memory.saveMessages({
                      messages: fullSubAgentMessages,
                    });
                  } catch (memoryError) {
                    this.logger.error(
                      `[Agent:${this.name}] - Failed to save messages to sub-agent memory: ${memoryError}`,
                    );
                  }
                }

                if (generateResult.finishReason === 'suspended') {
                  return suspend?.(generateResult.suspendPayload, {
                    resumeSchema: generateResult.resumeSchema,
                    runId: generateResult.runId,
                    isAgentSuspend: true,
                  });
                }

                result = { text: generateResult.text, subAgentThreadId, subAgentResourceId };
              } else if (methodType === 'generate' && modelVersion === 'v1') {
                const generateResult = await agent.generateLegacy(messagesForSubAgent, {
                  requestContext,
                  ...resolveObservabilityContext(context ?? {}),
                });
                result = { text: generateResult.text };
              } else if (
                (methodType === 'stream' || methodType === 'streamLegacy') &&
                supportedLanguageModelSpecifications.includes(modelVersion)
              ) {
                const streamResult = resumeData
                  ? await agent.resumeStream(resumeData, {
                      runId: suspendedToolRunId,
                      requestContext,
                      ...resolveObservabilityContext(context ?? {}),
                      ...(effectiveInstructions && { instructions: effectiveInstructions }),
                      ...(effectiveMaxSteps && { maxSteps: effectiveMaxSteps }),
                      ...(resourceId && threadId
                        ? {
                            memory: {
                              resource: subAgentResourceId,