How To Build A Copilot Studio Agent With Generative Orchestration

I want to show you how to build a complete Copilot Studio Agent that takes full advantage of generative orchestration mode. Over the course of a multi-turn conversation, the Agent will use its instructions and messages sent by the User to decide which topics and tools to call next. Where possible, we will let the Agent respond without hardcoded messages. And we will also use variables to keep track of state while reducing the risk of Agent failure.
Table of Contents
• Introduction: The Find Meeting Times Agent
• Agent Instructions
• Custom Topics List
• System Topics List
• Conversation Start System Topic
• Resolve User Name To Valid Email Topic
• Confirm Meeting Attendees List Topic
• Remove User From Attendees List Topic
• Find Meeting Times Topic
• Restart Agent Topic
• Who Am I Topic
• Conversational Boosting System Topic
• Extending The Agent Further
• Video: How I Built A Generative Orchestration Agent
Introduction: The Find Meeting Times Agent
The Find Meeting Times agent is a multi-turn conversational agent whose next actions are determined by the generative orchestration, instead of pre-programmed logic.
The Agent starts by asking the User to provide a list of meeting attendees in any format.

The Agent verifies that the Users actually exist and gets their emails.

The Agent can also remove attendees from the list.

Once the attendees list is confirmed the Agent sends an Adaptive Card to collect the date range and meeting duration for the search.

Finally, the Agent responds with a list of available meeting times. And invites the user to take further actions, or start-over.

Agent Instructions
Open Copilot Studio and create a new Agent with the name Find Meeting Times Agent. Include the description: “The Agent will help a User find available meeting times for the specified list of attendees.”

The Agent instructions are organized into two sections: a series of Steps to Find Meeting Times for Attendees that are expected to be carried out in order. And General Instructions to be followed during every interaction between the User and the Agent.
Copy and paste this text into the Agent instructions field:
General Instructions • Current Year Assumption: Assume the current year for any dates entered unless explicitly specified otherwise. • Time Zone Conversion: Convert any UTC times to the user’s time zone, specified by {Global.userTimeZone}. • Stay On-Topic: Tell the User you can only help them find meeting times. Then restate the Agent’s previous directions. Steps to Find Meeting Times for Attendees 1. Gather Attendee Information: • Prompt the user for the names of all meeting attendees. • Resolve each attendee to an email address. Fill-in the User’s name or email as the searchTerm. 2. Confirm Attendees List • Call the topic named “Confirm Meeting Attendees List” anytime “Resolve User Name To Valid Email” or “Remove User From Attendees List” topic executes • If User responds with “No” send a message with the text “Please provide the names of the attendees you would like to add or remove from the list.” 3. Find Available Meeting Times: • Never include this step in the same Plan as Step 2. Don’t run this twice in the same turn. • Initiate the Find Available Meeting Times topic. 4. Display Meeting Time Suggestions: • Present the available meeting times to the user in a bulleted list, grouped by meeting date. • Sort meeting times from oldest-to-newest. Always display meeting times in {Global.userTimeZone}. 5. Ask What To Do Next • Ask the user if they would like to add new attendees, change the meeting time, or start over? • Always re-confirm the attendees list if a User is added or removed. |
Custom Topics List
The Agent will use 6 custom topics to accomplish its goal of finding meeting times for a list of attendees. We will need to build each one of these topics.

System Topics List
Our Agent will also use built-in system topics which are automatically added when the Agent is created. The Conversation Start and Conversational Boosting topic will need some modifications. On Error and Sign-In topics will remain as-is.
The remaining system topics should be turned off. We don’t want to use them for this Agent.

Conversation Start System Topic
The Conversation Start system topic is triggered when a new Agent conversation begins. Here we want the Agent to send an introduction message to the User with it’s name, version and a short description of its purpose. We will also prompt the User with an opening question so they know what to do next.

The Agent uses a global variable named tblRequiredAttendees to track the list of meeting attendees. This PowerFx formula initializes the table with Name and Email properties having a string data type. Then it clears the table.
Defining tblRequiredAttendees as a table type variable is necessary before performing further actions to add and remove attendees. The Start Conversation topic is the best place to do this.
FirstN(
Table(
{
Name: "Matthew Devaney",
Email: "[email protected]"
}
),
0
)
Starting a conversation with the Agent looks like this in the Copilot Studio test window.

Resolve User Name To Valid Email Topic
The Resolve User Name To Valid Email topic converts a User display name into a valid email address. We want the User to enter one or more attendee names and have this topic run as many times as needed. Fortunately, we can rely on generative orchestration to extract the attendee names determine how many times to run the topic.
For each attendee name entered through the chat window, the Agent performs a Search For Users (V2) with the name as the search term. If results are found, the Agent takes the top search result and adds it to the tblRequiredAttendees table variable.
It also sets the global variable isAttendeesListUpdated to true. This variable tracks whether the meeting attendees list needs to be confirmed by the User.
If no Users are found in the search results, we will rely on the orchestrator to tell the User the person could not be found. We will not add any message node to do it.

We can ensure the Topic is only passed user names by creating an input variable. Add a new input variable called varSearchUserName with a string data type and let the Agent decide how to fill it. Choose Identify As Person Name for the entity type.

The Modify items in a list node adds the top search result to the tblRequiredAttendees table.

Use this code in the Value property of the Modify items in a list node.
{
Name: First(Topic.SearchUserV2.value).DisplayName,
Email: First(Topic.SearchUserV2.value).UserPrincipalName
}
In the Copilot Studio test window, the User will enter one or more Agent names in any format they want. It does not have to be comma-separated.

Then the Resolve User Name To Valid Email topic will run once for each attendee name.

Confirm Meeting Attendees List Topic
Anytime attendees are added the User should confirm the list before the Agent asks when to search for meetings. The Agent sends a message to the user showing who is currently in the tblRequiredAttendees variable.
If the User answers Yes, the conversation is redirected to the find meeting times topic. The global variable isAttendeeListUpdated is reset to false.
Or if the User answers No, the orchestrator will continue the conversation by asking the User if they want to add or remove attendees. We do not need to manually add a message node for this.

Set the topic trigger condition to Global.isAttendeeListUpdated is equal to true. This prevents the topic from triggering when there are no updates. We could try to rely solely on the orchestrator and agent instructions, but this helps prevent any mistakes.

We want to convert tblRequiredAttendees to a bulleted list so the User will have an easy time reading it. To do this we will use a combination of HTML & PowerFx code.

Use this code to set the formattedAttendeesList variable.
"<ul>"&Concat(
Global.tblRequiredAttendees,
$"<li>{Name} [{Email}]</li>"
)&"</ul><br />"
The formatted list will show like this in the Copilot Studio test window.

And the orchestrator will choose to run Confirm Meeting Attendees List after any attendee is added or removed based on our Agent instructions.

Remove User From Attendees List Topic
The Remove User From Attendees List drops attendees from the tblRequiredAttendees table variable. Before confirming the attendees list, the User can ask the Agent to remove one or more attendees and this topic will run once for each person.
First, the Agent checks to see if the person requested to be removed is currently in tblRequiredAttendees table. Then if true, it drops them from the table. Otherwise, the Agent sends a message to communicate the person could not be found in the list.

Similar the the Resolve User Name To A Valid Email topic, we will use an input variable to ensure only Person Names are passed to the topic. Create an input variable named removeAttendee with a string data type and let the Agent decide how to fill the input. Choose Identify As Person Name.

Set the boolean variable isNameInAttendeesList to this code.
Not(
IsBlank(
LookUp(
Global.tblRequiredAttendees,
Name = Topic.removeAttendee
)
)
)
Then in the condition branch isNameInAttendeesList is equal to true remove the person from the attendees list.

Use this code to update the tblRequiredAttendees table.
Filter(
Global.tblRequiredAttendees,
Name <> Topic.removeAttendee
)
Try removing an attendee in the Copilot Studio test window. After dropping the attendee, the User will be asked to confirm the new attendees list.

Here we can see the orchestrator chose to run a Remove User From Attendees List topic and a Confirm Meeting Attendees List topic.

Find Meeting Times Topic
The Find Meeting Times topic asks the user to provide a date range and returns possible times when attendees can meet. It will only run after the attendees list has been confirmed.
It sends an Adaptive Card with inputs to capture the date range and and meeting duration. Then it calls the built-in Find Meeting Times (Outlook) action through a tool. The results are output to a global variable named meetingTimeSuggestions. And the agent finishes by determining the User’s time zone and storing it in another global variable named userTimeZone.

We want to prevent this topic from being called before the attendees list is confirmed. Set the topic’s trigger condition to isAttendeesListConfirmed is equal to Yes.

An adaptive card is ideal when asking the the User to provide multiple inputs at once. It also validate data types and suggest default values. Create an Adaptive Card to capture the Start Date, End Date and duration.

Open the adaptive card editor in formula mode and use this code:
{
type: "AdaptiveCard",
version: "1.5",
body: [
{
type: "TextBlock",
text: "Please specify when to look for available meeting times:",
wrap: true,
weight: "Bolder",
size: "Medium"
},
{
type: "Input.Date",
id: "startDate",
label: "Start Date",
isRequired: true,
errorMessage: "Start Date is required.",
value: Text(Today(),"yyyy-mm-dd")
},
{
type: "Input.Date",
id: "endDate",
label: "End Date",
isRequired: true,
errorMessage: "End Date is required.",
value: Text(Today(),"yyyy-mm-dd")
},
{
type: "Input.Number",
id: "meetingDuration",
label: "Meeting Duration (minutes)",
value: 30,
isRequired: true,
errorMessage: "Meeting Duration is required."
}
],
actions: [
{
type: "Action.Submit",
title: "OK",
id: "submit"
}
],
'$schema': "https://adaptivecards.io/schemas/adaptive-card.json"
}
Load input values for the RequiredAttendees, Start, End and MeetingDuration values into the Find Meeting Times (Outlook) tool. Change the output variable named MeetingTimeSuggestions to a global variable and provide the description “Meeting time suggestions in UTC timezone.”

Use this formula to format the Required Attendees as a semi-colon separated list.
Concat(
Global.tblRequiredAttendees,
$"{Email};"
)
Provide this formula as the Start value. It converts the selected start date to the UTC timezone.
DateTimeValue(
DateAdd(
Topic.startDate,
TimeZoneOffset(),
TimeUnit.Minutes
)
)
And use this formula as the End value. It converts the selected end date to the UTC timezone and adds time for end-of-day.
DateTimeValue(
DateAdd(
Topic.endDate,
TimeZoneOffset(),
TimeUnit.Minutes
)
) + Time (23,59,59)
Then add the meetingDuration topic variable as the Duration.
Topic.meetingDuration
After finding the meeting times, update the userTimeZone variable to global and use the description “Adjustment for User’s current timezone in hours.”

Use this code to format the UTC timecode. Example: UTC-5.
"UTC"&Text(-TimeZoneOffset(Now())/60)
In the Copilot Studio test window, the User confirms the attendees list and choose the meeting time search parameters.

The Agent sends a message with the available meeting times for attendees. Note that the meeting times were automatically converted to UTC-5 without writing any logic. The Agent did this because we provided it instructions to display any meeting times in the user’s local time zone.

Restart Agent Topic
The Restart Agent topic allows the User to start over and find meeting times for a new set of attendees. It resets the conversation by clearing all variables and ending all topics.

The topic can be triggered at anytime, however, the Agent will suggest it as the next topic after showing the User a list of available meeting times.

Who Am I Topic
The Who Am I topic can be called at any time. It provides a version number which is helpful for the developer debugging the Agent. And a list of sample commands which is helpful to the User.

Conversational Boosting System Topic
We decided to turn-off the Fallback system topic for the Agent, so we should add a few messages to communicate when the Agent could not respond to the User’s question. This is helpful for when the User tries to go off-topic and use the Agent for unintended purposes.

Extending The Agent Further
That’s all we will build for now. If you want to extend the Agent further, consider these ideas or come up with some of your own:
- Add a tool or create a topic to create an Event in Outlook.
- When multiple results are found for a person being added, display the list of results and ask the User to choose one.
- Check the attendees list for duplicates before adding a new person
Video: How I Built A Generative Orchestration Agent
Watch this video for a complete walkthrough of how to build the Agent outlined in the article.
Did You Enjoy This Article? 😺
Subscribe to get new Power Apps & Power Automate articles sent to your inbox each week for FREE
Questions?
If you have any questions or feedback about How To Build A Copilot Studio Agent With Generative Orchestration please leave a message in the comments section below. You can post using your email address and are not required to create an account to join the discussion.