Building a Generative Orchestration Agent with Copilot Studio: A Step-by-Step Guide
Discover how to create a dynamic, multi-turn Copilot Studio agent that uses generative orchestration to manage attendee lists, confirmations, and availability searches—without rigid conversational flows.
Imagine an agent that can understand free-form user input, decide which topics to invoke, and adapt its behavior based on conversation context – all without you writing explicit branching logic. That’s the promise of generative orchestration in Microsoft Copilot Studio. Instead of painstakingly chaining topics together with conditions and flows, you provide high-level instructions and let the AI orchestrate the conversation. In this guide, I’ll walk you through building a fully functional Attendee Coordinator Agent that collects meeting attendees, resolves their email addresses, confirms the list, searches for availability, and presents the results – all driven by generative orchestration.
The Scenario: Attendee Coordinator Agent
The agent’s job is to help a user schedule a meeting with a group of colleagues. The flow feels natural:
- The user provides one or more attendee names (any format – “John and Sarah” or “John, Sarah, Bob”).
- The agent resolves each name to a valid email address using the Microsoft 365 Users connector.
- The agent asks the user to confirm the list, showing names and emails.
- After confirmation, the agent gathers a date range and meeting duration.
- The agent finds available time slots and displays them in a clear, grouped list.
- The user can add or remove attendees, change the date range, or start over.
Throughout the conversation, the agent avoids hardcoded messages – it relies on the orchestrator to generate responses based on the instructions and user input.
1. Creating the Agent and Writing Instructions
Open Copilot Studio and create a new agent. Give it a descriptive name like Attendee Coordinator and a description that clearly states its purpose. The real magic lies in the Instructions field, where you define both general rules and a step‑by‑step process.
Instructions are the backbone of generative orchestration. They must be specific, ordered, and cover edge cases. Avoid ambiguous language or contradictory commands.
Below is an example instruction set that balances structure with flexibility. Paste it into the agent’s instructions area.
General Instructions
- Assume the current year for any dates unless the user explicitly provides a year.
- Convert all UTC times to the user's time zone specified by {Global.userTimeZone}.
- Do not engage in topics outside of meeting scheduling. Politely redirect if asked.
Steps to Coordinate Attendees
1. Collect Attendee Names: Ask the user for the names of all meeting participants.
2. Resolve to Emails: For each name, call the "Resolve Person" topic. Include the user’s own name/email as the meeting organizer.
3. Confirm List: After any addition or removal, invoke the "Confirm Attendees" topic.
4. Find Available Times: Once the list is confirmed, run the "Find Slots" topic. Do not run this in the same turn as step 3.
5. Display Results: Present available times in a bulleted list, grouped by date, sorted oldest to newest, in the user’s time zone.
6. Next Actions: Ask the user if they want to modify the list, change the time range, or start over. Always reconfirm the list after a change.Global Variable Setup
Before building topics, you need a global variable to hold the attendee list. I’ll use a table variable named colAttendees with columns DisplayName (Text) and Email (Text). Initialize it with an empty schema during the conversation start.
FirstN(
Table(
{DisplayName: "Placeholder", Email: "placeholder@example.com"}
),
0
)This returns an empty table that preserves the required columns. Also create a Boolean global isListUpdated and set it to false.
2. Building the Custom Topics
You will build three main custom topics. The system topic Conversation Start will also be modified.
2.1 Conversation Start (System Topic)
In the Conversation Start system topic, add the initialization formulas above. Then send a welcome message and an opening question, such as “Who would you like to invite to the meeting?”
2.2 Resolve Person Topic
The orchestrator invokes this topic when it detects person names in the user’s message. Add a trigger phrase such as "add [personName] to the list" and create an input variable varPersonName of type String with its Identify as setting set to the Person Name prebuilt entity. The orchestrator will pass each name it detects.
Inside the topic, add an Office 365 Users – Search for users (V2) action. Use varPersonName as the search term. Then, using a Modify items in a list node, append the top result to Global.colAttendees:
{
DisplayName: First(Topic.SearchUserV2.value).DisplayName,
Email: First(Topic.SearchUserV2.value).UserPrincipalName
}After the append, set Global.isListUpdated to true. If no user is found, do not add a message node – let the orchestrator inform the user and ask for clarification. This keeps the agent responsive without hardcoded text.
2.3 Confirm Attendees Topic
Create a new topic named Confirm Attendees. At the start of this topic, add a Condition node checking that Global.isListUpdated equals true, and redirect to the previous topic if it is false. The orchestrator decides when to invoke this topic based on your instructions, but the flag gives you a runtime guard to prevent it running prematurely.
Generate a readable HTML list from the colAttendees table:
"<ul>" &
Concat(
Global.colAttendees,
$"<li>{DisplayName} ({Email})</li>"
) &
"</ul>"Assign the result to a string variable. Then send a message like:
Here are the attendees I found: [formattedList] Is this correct?
If the user answers Yes, set Global.isListUpdated to false and redirect the conversation to the Find Slots topic. If No, let the orchestrator handle the follow‑up – it will ask the user to add or remove names.
Always use a flag like isListUpdated to control when this topic runs. Without it, the topic could loop unnecessarily after every small change.
2.4 Find Slots Topic (High‑Level)
The Find Slots topic collects the date range and meeting duration. The simplest approach is to ask two questions:
- “How many minutes should the meeting last?”
- “What are the start and end dates for the search?”
Then use a connector (e.g., Microsoft Graph via HTTP or a custom connector) to query free/busy information for all attendees. For demonstration, you can simulate the results.
After obtaining the available slots, present them in a bulleted list grouped by date and sorted chronologically.
SortByColumns( GroupBy(colAvailableSlots, "Date", "Slots"), "Date", SortOrder.Ascending )
Feed this list into a message node.
3. System Topic Adjustments
3.1 Modifying the Conversation Start Topic
As mentioned, add the initialization and welcome message here. Also, ensure the Conversational Boosting system topic remains active and well‑configured.
3.2 Disabling Unnecessary System Topics
Turn off the following system topics to prevent interference with generative orchestration:
- Escalate
- Goodbye
- Next Steps
- Sign‑out (if present)
Keep On Error and Sign-In as‑is. The orchestrator will handle off‑topic conversations using the instructions you provided.
4. Security and Performance Considerations
- Microsoft 365 Permissions: The Office 365 Users connector requires admin consent and appropriate Graph API permissions. Ensure the agent is configured to use a connection that can read user profiles.
- Variable Scoping: Use global variables only for state that must persist across topics. All other data should remain local to a topic.
- Delegation: If you later switch to Dataverse for storing attendee lists, remember that functions like
ConcatandGroupBymay not fully delegate. For typical attendee lists (fewer than 100 people), this is rarely a concern. - Error Handling: Let generative orchestration handle unexpected responses. Test edge cases: multiple names in one message, misspelled names, empty lists.
5. Common Mistakes and Troubleshooting
| Mistake | Solution |
|---|---|
| Variable not initialized before use | Ensure the global table is created in the Conversation Start topic before any other topic runs. |
| Topic fails to trigger | Double‑check trigger conditions and entity types. The input variable must have the correct Identify as setting (e.g., Person Name) so the orchestrator knows what to pass. |
| Infinite loops or repeated confirmations | Review the isListUpdated flag logic. It should be reset to false only after the list is confirmed. |
| HTML list not rendering | Use a Text variable to store the HTML string, then output it directly in a message node. Copilot Studio renders simple HTML like <ul> and <li>. |
Testing tip: Run each topic individually in the test pane before testing the full orchestrated flow. Use the conversation map after a test run to see which topics were invoked and in what order.
6. Final Recommendation
Generative orchestration enables a new class of conversational agents that feel proactive and natural. The key is investing time in clear, structured instructions and letting the AI handle the rest. Start with a well‑scoped scenario like attendee coordination, use global variables to maintain state, and test iteratively. You will be surprised how much you can accomplish with minimal hardcoded logic.
References
- Original article: How To Build A Copilot Studio Agent With Generative Orchestration by Matthew Devaney
- Microsoft Learn: Generative orchestration in Copilot Studio
- Microsoft Learn: System topics in Copilot Studio
- Microsoft Learn: Use the Office 365 Users connector