Project Assist Agent is a production-ready pattern for building a focused, LLM-driven assistant for project and task management inside Microsoft 365. Built with the Microsoft 365 Agents SDK, Semantic Kernel, and Azure AI, it integrates with Microsoft Graph (Planner, Teams, Outlook), exposes safe Kernel plugins for automation, and returns structured responses (text or Adaptive Cards) so users can view, create and act on project items from Teams, Copilot, Web Chat or custom apps.
This post guides you through clear architecture overview, step-by-step build instructions, security and deployment notes, debugging tips for local dev, and a short checklist for packaging and Copilot/Teams validation.
What you’ll learn
A concise, high-level view of the outcomes this guide delivers:
- When to build a custom Project Assist Agent versus using Copilot Studio
- The agent architecture and runtime concepts (turns, activities, channels)
- How to design safe Graph integrations and Kernel plugins
- Core implementation and deployment checkpoints (auth, secrets, packaging)
- Practical local debugging and Teams/Copilot validation steps
Introduction — Why build a custom Project Assist Agent?
Generic copilots are useful, but teams gain the most when assistants are specialised, contextual and tightly integrated with the systems people use daily. A Project Assist Agent goes beyond answering questions: it automates workflows, surfaces overdue work, creates and assigns Planner tasks, and can send or schedule messages and emails on behalf of users — all while respecting tenant governance and permission boundaries.
Building a focused agent lets you:
- Enforce least-privilege access and explicit consent for Graph operations
- Produce structured outputs (Adaptive Cards, JSON) that drive UI actions instead of free-form text
- Integrate deterministic, testable plugins (Planner queries, task creation, email send) that the LLM can safely call
- Own the DevOps lifecycle: source control, automated tests, CI/CD, and observability
This article walks you through the architectural choices and the step-by-step implementation needed to build, debug and validate a Project Assist Agent using the Microsoft 365 Agents SDK and Semantic Kernel.
Deep Dive: The Microsoft 365 Agents SDK
The Microsoft 365 Agents SDK is a comprehensive, developer-centric framework designed for building intelligent, full-stack, multi-channel agents. It represents the evolution of the Azure Bot Framework, shifting the focus from a rigid, dialog-tree conversation model to a flexible, generative AI model centered on orchestrating complex actions.
At its core, the SDK provides a flexible “container” or “glue” for your agent logic. Its primary responsibilities are:
State and Storage Management: Persisting conversation context, user data, and other information across sessions using built-in support for Memory, Azure Blob Storage, or Azure Cosmos DB.
Activity and Event Handling: Managing the flow of incoming and outgoing communications based on the standardized Activity Protocol.
Channel Integration: Providing the scaffolding to deploy the same agent logic across different channels like Microsoft Teams, Microsoft 365 Copilot, Web Chat, and custom apps via Channel Adapters.
A key philosophical pillar of the SDK is its AI-agnostic nature. It doesn’t force you to use a specific AI service. You have the freedom to integrate your chosen models and orchestration layers—be it Azure OpenAI, Semantic Kernel, LangChain, or even a custom orchestrator.
Core Concepts: The Language of Agents
It’s worth elaborating a bit more on the core concepts: turns, activities, and messages.
Turns: A turn is the basic unit of work for an agent. It encompasses the entire processing of one incoming activity from the user and any number of outgoing activities the agent sends in response. For example, if a user asks, “What’s the status of Project X?”, that inbound message triggers a turn in which the agent might perform several actions (look up data, compose a reply, maybe send multiple messages) before the turn is complete.
Activities: Activities represent the events exchanged between user and agent (or between agents). The Agents SDK defines a schema for activities that includes types like Message, ConversationUpdate, Typing, EndOfConversation, Event, etc.. When your agent is running in Teams or a web chat, the platform-specific payloads are translated into these activity types by the SDK’s channel adapter. For instance, when a new user joins a Teams channel, the agent can receive a ConversationUpdate activity with membersAdded info. Similarly, if the agent needs to send a typing indicator, you can send an activity of type Typing to the user.
Messages: The most common activity is a message activity – carrying text, images, or cards. The SDK offers the MessageFactory helper to easily create different message types (text, attachments, suggested actions, etc.). This ensures your agent’s responses are correctly formatted for the channel. For example, to send an adaptive card with buttons, you can create an attachment and use MessageFactory.Attachment(), and the SDK will handle the rest. In our echo bot example above, we used MessageFactory.Text to create a simple text reply.
Under the hood: If you’ve used the older Bot Framework SDK, you might be wondering how this compares. The Agents SDK has a similar foundation (it even uses Azure Bot Service behind the scenes for routing), but rather than using a dialog stack or declarative conversational flows, it leans into a generative AI model of interaction. Your agent logic can be simpler (just react to each turn, possibly using AI to decide the action), and you can integrate powerful AI planning via Semantic Kernel (more on that shortly). The stateless-by-default approach means you think in terms of the current user request and how to fulfill it, which is natural for generative AI patterns. You can always enable state persistence if your scenario requires remembering info across turns or sessions.
Code Insight: Basic Echo Agent (C#)
// This code sets up a basic agent that listens for message activities and echoes the text back. |
This simple agent illustrates the turn-based model and activity handling. Real-world agents add AI services and orchestration here.
Strategic Choice: Agents SDK vs. Copilot Studio
Microsoft offers two primary paths to build AI agents in the M365 ecosystem: the Microsoft 365 Agents SDK (pro-code) and Copilot Studio (low-code). Both aim to empower the creation of intelligent assistants, but they target different audiences and needs. Let’s compare them:
Aspect | Microsoft 365 Agents SDK (Custom Pro-Code) | Copilot Studio (Low-Code) |
---|---|---|
Target Audience | Professional developers, software engineers. | Fusion teams, power users, citizen developers. |
Development Model | Code-first: Use C#/JS/Python with full IDE, source control, CI/CD. Build custom logic and integrations. | Low-code: Graphical interface, pre-built connectors (via Power Automate), and prompts. Minimal coding required. |
Customization | Complete control – you design every aspect of the agent’s behavior, choose any AI model, and call any API. Ideal for complex or unique requirements. | Platform-guided – faster to build with templates and managed components, but constrained to provided features and connectors. |
Integration | Can integrate with any external or internal API (Microsoft Graph, third-party services, databases, etc.) by writing code or plugins. No inherent limits on integrations. | Limited to supported connectors and actions in the Power Platform. Great for standard workflows (e.g. CRM, SharePoint), but may require workarounds for unsupported APIs. |
DevOps & Lifecycle | Treated like a normal software project – you maintain it in code repos, run tests, deploy to Azure, etc. Supports dev best practices and robust DevOps. | Managed within the Power Platform environment. Less setup, but also less flexibility for version control and automated testing (changes are often manual). |
Pricing Model | Pay-as-you-go based on Azure consumption. The SDK itself is free, but you incur costs for underlying services: e.g. Azure Bot Service (for messaging infrastructure) and any AI calls (Azure OpenAI, etc.) Example: Azure Bot Service’s standard tier includes 10k free messages per month, then ~$0.50 per additional 1k messages; Azure OpenAI (GPT-4) calls are billed per token. |
Subscription or per-message pricing. Copilot Studio (part of Power Virtual Agents licensing) offers a certain quota of messages per month. Example: A Messaging Pack might provide 25,000 messages/month for a fixed fee (around $200/tenant/month), or a pay-as-you-go rate. Additionally, using Copilot features requires a Microsoft 365 Copilot licence (~$30/user/month). |
Agents SDK Pricing: The Agents SDK itself doesn’t cost anything to use – you can run your agent locally or on any hosting. When you deploy to Azure, the primary service is the Azure Bot Service (which handles message routing to your agent). Azure Bot Service has a free tier (standard channels like Teams are free and unlimited), and a paid tier for premium channels or high volume, with 10,000 free messages per month and $0.50 per 1,000 messages beyond that. In addition, you’ll pay for whatever AI services you use – for example, if your agent uses Azure OpenAI GPT-4, you’ll be billed per 1,000 tokens processed (input ~£2.00 and output ~£8.00 per million tokens as of writing, depending on model). Hosting costs (e.g. an Azure App Service or Azure Functions to run the agent code) are also a factor (for instance, a Basic B1 App Service is about £55/month).
Copilot Studio Pricing: Copilot Studio falls under Power Platform licensing. Microsoft has announced a per-tenant monthly fee that includes a generous allotment of messages (for example, 25k messages per month for around £160 if paid annually, equivalent to $200) for the bot, which covers the runtime AI costs. Beyond that, additional messages may incur overage charges. Note that each user of a Copilot (including custom ones) also needs a Microsoft 365 Copilot licence (£23 per user per month, or $30) to access Copilot features. In summary, Copilot Studio simplifies the tech overhead but comes with a predictable subscription cost, whereas Agents SDK might be cheaper for low usage but can scale in cost with heavy use (while offering more flexibility).
When to Choose Which? It often comes down to the trade-off between speed vs. flexibility:
Choose Agents SDK if you need deep customisation and integration. For example, building an agent that connects to proprietary databases or implements custom AI logic. It’s ideal if you have development capability and want full control over the agent’s behavior and data handling.
Choose Copilot Studio if you need to get something working quickly without code, or to empower business domain experts to create their own agents. It’s great for standard scenarios (approvals, Q&A bots, simple task automation) and when you want to leverage the governed environment of the Power Platform.
What is the Project Assist Agent?
Project Assist Agent is an AI-powered assistant built with the Microsoft 365 Agents SDK, Semantic Kernel, and Azure AI to streamline project and task management inside Microsoft 365. It integrates with Microsoft Graph (Planner, Teams, Outlook), exposes Kernel functions (plugins) for automation, and returns structured responses (text or adaptive cards) so users can view, create, and act on project items directly from Teams or other Microsoft 365 surfaces.
Main features
- Planner integration: list, filter, and query tasks (by user, plan, priority, progress).
- Overdue task reporting: fetch overdue tasks globally or per user/plan.
- Task creation: create tasks in a specific plan and bucket, assign users, set due dates.
- User lookup: resolve users by email via Microsoft Graph.
- Email/send capability: send HTML emails via Outlook on behalf of a user (separated into
EmailService
). - Adaptive card responses: produce rich visual summaries using adaptive cards (v1.5+).
- Kernel-exposed plugins: PlannerPlugin, TeamsOutlookPlugin, AdaptiveCardPlugin for LLM tooling and safe function calls.
- Secure user auth: per-turn user tokens and MSAL-based sign-in flows (configured in appsettings.json).
- Extensible DI design: services wired via dependency injection (
IPlannerService
,IEmailService
). - Teams manifest commands: built-in commands (e.g., “Fetch my tasks”, “Fetch overdue tasks”) to surface capabilities in Teams.
- Local dev & playground support: debug and iterate using Microsoft 365 Agents Playground / dev tunnels.
Architecture Overview
Below is a high-level architecture diagram illustrating the flow of a user request through the Project Assist Agent’s components:
Project Assist Agent in Microsoft 365 Copilot
Step by Step Instruction to build Project Assist Agent
In project assist agent, we are going to use delegated permissions to access planner tasks as well sending emails via Microsoft Graph.
Step1: Prerequisites:
- Create Azure Bot Resource. Follow this article for step by step instruction to create Azure Bot Resource Use the Azure portal to Create an Azure Bot resource
- Register Azure Active Directory Identity Provider with the Bot. Follow this article for step by step instruction.
- Add Graph API permissions for Planner tasks as well sending emails via Microsoft Graph API
Step2: Create .NET agents in Visual Studio using the Microsoft 365 Agents Toolkit
Follow the following article to create a new Agents SDK .NET project in Visual Studio, using the Microsoft 365 Agents Toolkit. Use weather agent template as a starting point.
Create .NET agents in Visual Studio using the Microsoft 365 Agents Toolkit
Note
Before you begin, you need to install the Agents Toolkit extension for Visual Studio.
Step3: Update appsettings.json
The appsettings.json
file contains runtime configuration for the ProjectAssist application, including AgentApplication user-authorization settings and UI prompts, token validation audiences, the BotServiceConnection MSAL connection (client id, tenant, client secret and scopes) and connections map, logging levels, Azure OpenAI configuration (API key, endpoint, deployment name), and Microsoft Graph credentials (tenant id, client id, client secret). These settings wire authentication, Graph/OpenAI integrations, and logging for the app;
Note
Note that sensitive values (client secrets and API keys) are currently stored in this file and should be moved to secure storage (environment variables, user secrets, or Azure Key Vault) for production.
{ | |
"AgentApplication": { | |
"StartTypingTimer": true, | |
"RemoveRecipientMention": false, | |
"NormalizeMentions": false, | |
"UserAuthorization": { | |
"DefaultHandlerName": "graph", | |
"AutoSignin": true, | |
"Handlers": { | |
"graph": { | |
"Settings": { | |
"AzureBotOAuthConnectionName": "Graph", | |
"Title": "Sign in to Continue", | |
"Text": "Please sign in and send the 6-digit code" | |
} | |
} | |
} | |
} | |
}, | |
"TokenValidation": { | |
"Audiences": [ | |
"eb8c1f53-5cf4-4e1c-xxxx-xxxxxxxxx" // this is the Client ID used for the Azure Bot | |
] | |
}, | |
"Connections": { | |
"BotServiceConnection": { | |
"Assembly": "Microsoft.Agents.Authentication.Msal", | |
"Type": "MsalAuth", | |
"Settings": { | |
"AuthType": "ClientSecret", | |
"ClientId": "eb8c1f53-5cf4-4e1c-xxxx-xxxxxxxxx", | |
"ClientSecret": "drt8Q~kA-xxxxxxxxxxxxxxxxxxxxxxxxxxx", | |
"TenantId": "3f4d536c-9ebc-4eb1-xxxx-xxxxxxxxx", | |
"AuthorityEndpoint": "https://login.microsoftonline.com/3f4d536c-9ebc-xxxx-xxxx-xxxxxxxxx", | |
"Scopes": [ | |
"https://api.botframework.com/.default" | |
] | |
} | |
} | |
}, | |
"ConnectionsMap": [ | |
{ | |
"ServiceUrl": "*", | |
"Connection": "BotServiceConnection" | |
} | |
], | |
"Logging": { | |
"LogLevel": { | |
"Default": "Information", | |
"Microsoft.AspNetCore": "Warning", | |
"Microsoft.Agents": "Warning", | |
"Microsoft.Hosting.Lifetime": "Information" | |
} | |
}, | |
"Azure": { | |
"OpenAIApiKey": "[Open API Key]", | |
"OpenAIEndpoint": "[Azrue Open API Endpoint]", | |
"OpenAIDeploymentName": "gpt-4o" | |
}, | |
"MicrosoftGraph": { | |
"TenantId": "3f4d536c-9ebc-4eb1-xxxx-xxxxxxxxx", | |
"ClientId": "eb8c1f53-5cf4-4e1c-xxxx-xxxxxxxxx", | |
"ClientSecret": "drt8Q~kA-5ig9wxxxx-xxxxxxxxxx" | |
} | |
} |
Step4: Create ProjectAssistBot.cs
class
This file implements the bot application that hosts the ProjectAssist conversational agent. It derives from AgentApplication, wires required dependencies (Kernel, HTTP client factory, Planner and Email services, configuration), and registers handlers for conversation events and incoming message activities.
On each message activity the bot builds a per-turn service provider, creates a ProjectAssistAgent with the current Kernel and services, and forwards the user message and chat history to the agent. It streams an informative update while the agent runs, awaits the agent response, and maps the returned ProjectAssistAgentResponse into the bot’s streaming response—either plain text chunks or an Adaptive Card attachment.
The class also contains a simple welcome handler that sends a greeting to newly added conversation members. Overall, this file is responsible for receiving Teams messages, coordinating per-turn services and context, invoking the LLM-driven agent, and formatting the agent’s structured responses for delivery to users.
using Microsoft.Agents.Builder; | |
using Microsoft.Agents.Builder.App; | |
using Microsoft.Agents.Builder.State; | |
using Microsoft.Agents.Core.Models; | |
using Microsoft.Extensions.DependencyInjection.Extensions; | |
using Microsoft.SemanticKernel; | |
using Microsoft.SemanticKernel.ChatCompletion; | |
using O365C.Agent.ProjectAssist.Bot.Agents; | |
using O365C.Agent.ProjectAssist.Bot.Models; | |
using O365C.Agent.ProjectAssist.Bot.Services; | |
using System.Net.Http; | |
using System.Net.Http.Headers; | |
using System.Text.Json.Nodes; | |
using System.Threading; | |
using System.Threading.Tasks; | |
namespace O365C.Agent.ProjectAssist.Bot; | |
public class ProjectAssistBot : AgentApplication | |
{ | |
private ProjectAssistAgent _projectAssistAgent; | |
private Kernel _kernel; | |
private readonly IHttpClientFactory _httpClientFactory; // Add this field | |
private readonly IPlannerService _plannerService; | |
private readonly IEmailService _emailService; | |
public ConfigOptions _configurations; | |
public ProjectAssistBot(AgentApplicationOptions options, Kernel kernel, IHttpClientFactory httpClientFactory, IPlannerService graphService, ConfigOptions configurations, IEmailService emailService) : base(options) | |
{ | |
_kernel = kernel ?? throw new ArgumentNullException(nameof(kernel)); | |
_httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory)); | |
OnConversationUpdate(ConversationUpdateEvents.MembersAdded, WelcomeMessageAsync); | |
OnActivity(ActivityTypes.Message, MessageActivityAsync, rank: RouteRank.Last); | |
_plannerService = graphService; | |
_configurations = configurations; | |
_emailService = emailService; | |
} | |
protected async Task MessageActivityAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) | |
{ | |
// Setup local service connection | |
ServiceCollection serviceCollection = [ | |
new ServiceDescriptor(typeof(ITurnState), turnState), | |
new ServiceDescriptor(typeof(ITurnContext), turnContext), | |
new ServiceDescriptor(typeof(Kernel), _kernel), | |
new ServiceDescriptor(typeof(AgentApplication), this), | |
new ServiceDescriptor(typeof(IHttpClientFactory), _httpClientFactory), | |
new ServiceDescriptor(typeof(IPlannerService), _plannerService), | |
new ServiceDescriptor(typeof(IEmailService), _emailService), | |
new ServiceDescriptor(typeof(ConfigOptions), _configurations), | |
]; | |
await turnContext.StreamingResponse.QueueInformativeUpdateAsync("Working on a response for you"); | |
ChatHistory chatHistory = turnState.GetValue("conversation.chatHistory", () => new ChatHistory()); | |
_projectAssistAgent = new ProjectAssistAgent(_kernel, serviceCollection.BuildServiceProvider()); | |
var response = await _projectAssistAgent.InvokeAgentAsync(turnContext.Activity.Text, chatHistory); | |
if (response == null) | |
{ | |
turnContext.StreamingResponse.QueueTextChunk("Sorry, I couldn't get a project management response at the moment."); | |
await turnContext.StreamingResponse.EndStreamAsync(cancellationToken); | |
return; | |
} | |
switch (response.ContentType) | |
{ | |
case ProjectAssistAgentResponseContentType.Text: | |
turnContext.StreamingResponse.QueueTextChunk(response.Content); | |
break; | |
case ProjectAssistAgentResponseContentType.AdaptiveCard: | |
turnContext.StreamingResponse.FinalMessage = MessageFactory.Attachment(new Attachment() | |
{ | |
ContentType = "application/vnd.microsoft.card.adaptive", | |
Content = response.Content, | |
}); | |
break; | |
default: | |
break; | |
} | |
await turnContext.StreamingResponse.EndStreamAsync(cancellationToken); | |
} | |
protected async Task WelcomeMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken) | |
{ | |
foreach (ChannelAccount member in turnContext.Activity.MembersAdded) | |
{ | |
if (member.Id != turnContext.Activity.Recipient.Id) | |
{ | |
await turnContext.SendActivityAsync(MessageFactory.Text("Hello and Welcome! I'm here to help with your project management needs!"), cancellationToken); | |
} | |
} | |
} | |
} |
Create Project Assist Agent ProjectAssistAgent.cs
The ProjectAssistAgent.cs
implements an LLM-driven coordinator for project-management workflows, orchestrating Planner, Teams, and Outlook capabilities into conversational actions. It defines the agent’s instructions and expected behavior, including a strict JSON response schema with the fields contentType and content that the model must return.
The class configures a Semantic Kernel ChatCompletionAgent, setting prompt execution options and response formatting, and registers the Kernel plugins the agent uses: PlannerPlugin
, AdaptiveCardPlugin
, and TeamsOutlookPlugin
. These plugins provide the Planner queries/creation, adaptive card rendering, and email/Teams actions the agent can invoke.
InvokeAgentAsync
is exposed to run the agent against a user message and the current ChatHistory: it streams the model’s responses, aggregates the streamed content, parses the aggregated JSON into a typed ProjectAssistAgentResponse, and returns that structured result. If the agent output is malformed or cannot be parsed, the method retries by issuing a corrective prompt to obtain a properly formatted response.
using Microsoft.SemanticKernel.Connectors.OpenAI; | |
using Microsoft.SemanticKernel; | |
using Microsoft.SemanticKernel.Agents; | |
using Microsoft.SemanticKernel.ChatCompletion; | |
using System.Text; | |
using System.Text.Json.Nodes; | |
using O365C.Agent.ProjectAssist.Bot.Plugins; | |
using O365C.Agent.ProjectAssist.Bot.Models; | |
namespace O365C.Agent.ProjectAssist.Bot.Agents | |
{ | |
public class ProjectAssistAgent | |
{ | |
private readonly Kernel _kernel; | |
private readonly ChatCompletionAgent _agent; | |
private const string AgentName = "ProjectAssistAgent"; | |
private const string AgentInstructions = """ | |
You are a helpful assistant for project management tasks. You help users manage tasks, projects, and collaborate using Microsoft 365 tools like Planner, SharePoint, Teams, and Outlook. | |
You can also send emails, schedule meetings, and manage calendar events using Teams and Outlook via the TeamsOutlookPlugin. | |
Ask follow-up questions to clarify requirements. When you have enough information, respond with a summary or actionable steps, formatted as an adaptive card if appropriate. | |
Use adaptive cards version 1.5 or later for visual responses. | |
Respond in JSON format with the following schema: | |
{ | |
\"contentType\": "'Text' or 'AdaptiveCard' only", | |
\"content\": "{The content of the response, may be plain text, or JSON based adaptive card}" | |
} | |
"""; | |
public ProjectAssistAgent(Kernel kernel, IServiceProvider service) | |
{ | |
_kernel = kernel; | |
_agent = new() | |
{ | |
Instructions = AgentInstructions, | |
Name = AgentName, | |
Kernel = _kernel, | |
Arguments = new KernelArguments(new OpenAIPromptExecutionSettings() | |
{ | |
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(), | |
ResponseFormat = "json_object" | |
}), | |
}; | |
_agent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromType<PlannerPlugin>(serviceProvider: service)); | |
_agent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromType<AdaptiveCardPlugin>(serviceProvider: service)); | |
_agent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromType<TeamsOutlookPlugin>(serviceProvider: service)); | |
} | |
public async Task<ProjectAssistAgentResponse> InvokeAgentAsync(string input, ChatHistory chatHistory) | |
{ | |
ArgumentNullException.ThrowIfNull(chatHistory); | |
AgentThread thread = new ChatHistoryAgentThread(); | |
ChatMessageContent message = new(AuthorRole.User, input); | |
chatHistory.Add(message); | |
StringBuilder sb = new(); | |
await foreach (ChatMessageContent response in this._agent.InvokeAsync(chatHistory, thread: thread)) | |
{ | |
chatHistory.Add(response); | |
sb.Append(response.Content); | |
} | |
try | |
{ | |
string resultContent = sb.ToString(); | |
var jsonNode = JsonNode.Parse(resultContent); | |
ProjectAssistAgentResponse result = new ProjectAssistAgentResponse() | |
{ | |
Content = jsonNode["content"].ToString(), | |
ContentType = Enum.Parse<ProjectAssistAgentResponseContentType>(jsonNode["contentType"].ToString(), true) | |
}; | |
return result; | |
} | |
catch (Exception je) | |
{ | |
return await InvokeAgentAsync($"That response did not match the expected format. Please try again. Error: {je.Message}", chatHistory); | |
} | |
} | |
} | |
} |
Semantic Kernel plugins and Microsoft Graph service
Implement the Kernel plugins that the agent uses: PlannerPlugin
, TeamsOutlookPlugin
, and AdaptiveCardPlugin
. These plugins encapsulate calls to Microsoft Graph and other services (task queries/creation, calendar/meeting/email actions, and adaptive-card rendering). See the project source or the code snippets earlier in this post for full implementations and registration details.
Debugging the Project Assist Agent in the Web Chat channel
Follow these steps to run and test the agent locally using a dev tunnel (or deploy to Azure if you prefer):
- Build and run the agent project in Visual Studio. Note the local listening port (for example,
http://localhost:5130
). - Expose your local endpoint with a dev tunnel. See the official guide to create and host a dev tunnel: https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/get-started?tabs=windows
- Start the dev tunnel (replace the port with your local port if different):
devtunnel host -p 5130 --allow-anonymous |
- Copy the public dev tunnel URL and update your Azure Bot resource’s messaging endpoint to point to the tunneled URL (for example,
https://<your-tunnel>.devtunnels.ms/api/messages
).
- In the Azure Portal, ensure the Web Chat and Microsoft Teams channels are enabled for your Azure Bot resource.
- Open Test in Web Chat (under the Bot resource’s Settings). If authentication is configured correctly you should see a Sign in button in the Web Chat pane.
Debugging the Project Assist Agent in Microsoft Copilot
Follow these steps to package your Teams app (manifest + icons), upload it to the Teams Developer Portal, and expose the Project Assist Agent as a custom Copilot agent. Use the demo screenshot below to confirm the agent is reachable inside Copilot.
Prerequisites
- Ensure the Azure AD App registration exists and the
webApplicationInfo.id
in the manifest matches the AAD app (client) ID. - Verify the bot registration (Azure Bot or Bot Channels Registration) uses the same Bot ID as
bots[0].botId
and that the bot’s messaging endpoint is reachable via HTTPS. - Add and grant admin consent for required Microsoft Graph permissions (Planner, Mail.Send, etc.).
- Include any dev-tunnel or hosting domains in
validDomains
and ensure they are resolvable from the internet. - Prepare
color.png
andoutline.png
icon files (recommended sizes per Teams docs).
- Ensure the Azure AD App registration exists and the
Validate the manifest
- Confirm
id
is a unique GUID for the Teams app package. - Confirm
webApplicationInfo.id
,bots[].botId
, andcopilotAgents.customEngineAgents[].id
all reference the correct AAD/bot App IDs. - Verify
manifestVersion
(1.22) is supported by your Teams environment.
- Confirm
Create the app package (zip)
- Place these files in one folder:
manifest.json
(the JSON you provided)color.png
andoutline.png
- Zip the files (do not zip the parent folder). Example PowerShell (run in the folder with the files):
- Place these files in one folder:
Compress-Archive -Path .\manifest.json, .\color.png, .\outline.png -DestinationPath .\ProjectAssistAgent-1.2.1.zip |
Import the app into the Teams Developer Portal
- Open Teams -> Apps -> Developer Portal (or visit the Developer Portal app in Teams).
- Choose Apps → Import app → Upload existing app and select the zip file.
- Inspect the imported app in the Developer Portal and confirm metadata, icons, and bot configuration.
Configure Copilot & tenant settings
- In the Developer Portal app settings confirm the bot is enabled for the
personal
scope (Copilot typically invokes personal-scope bots). - If your tenant requires app approval, publish the app to the tenant app catalog or request admin approval in the Microsoft 365 admin center so Copilot can access it.
- In the Developer Portal app settings confirm the bot is enabled for the
Install and test
- Install the app for a test user (Add to personal apps or side-load into a Team).
- Test the bot in personal chat and verify the manifest command (e.g., “Fetch overdue tasks”) appears.
Test inside Microsoft Copilot
- Open the Copilot pane in Teams and invoke the Project Assist Agent (by name or command), for example: “Ask Project Assist Agent to show my overdue tasks.”
- Verify responses, adaptive cards, and any RAG-backed answers appear as expected.
Troubleshooting checklist
- Bot not responding: confirm
botId
matches the Bot registration and the messaging endpoint is reachable and configured in the bot resource. - Authentication/SSO issues: verify
webApplicationInfo
is correct, AAD redirect URIs are configured, and the token-exchange/SSO flow is implemented. - Copilot agent not visible: ensure tenant-level policies allow custom Copilot agents and the app is approved/published for the tenant.
- Dev tunnel problems: ensure the dev tunnel URL is included in
validDomains
and uses HTTPS.
- Bot not responding: confirm
Promote to production
- Replace dev-tunnel or temporary endpoints with production HTTPS endpoints.
- Move secrets to secure storage (Key Vault or environment variables).
- Verify compliance, logging, and monitoring before broad rollout.
Why Build a Custom AI Agent?
Organizations build custom AI agents when off-the-shelf copilots do not meet specific business, data, or workflow requirements. A custom agent lets you tailor behavior, integrations, security, and governance to your needs. Key benefits:
Tailored workflows — Model your agent around your team’s processes and terminology. For example, encode approval gates, custom validation rules, or company-specific task lifecycles so the agent enforces business policy rather than offering generic advice.
Deep integration with proprietary systems — Connect directly to internal APIs, legacy databases, or specialized tools that are not exposed via standard connectors. This enables multi-system actions (e.g., create a Planner task, update an on-premises ERP record, and notify external stakeholders) in a single conversational flow.
Stronger control & governance — Control which data the agent can access, where prompts and retrieval calls go, and how logs and telemetry are stored. This is important for compliance-sensitive industries where data residency, audit trails, and strict access controls are required.
Solve unique or complex problems — Implement domain-specific logic, multi-step orchestration, or specialized calculations that a general-purpose Copilot cannot perform out-of-the-box. Custom agents let you automate complex scenarios and optimize operational workflows.
Ownership of intellectual property — The knowledge, prompts, plugins, and integrations you develop are your organization’s IP. You can refine, version, and license those capabilities independently of vendor platforms.
In short, build a custom agent when you need predictability, tight integration, and governance that align with your organization’s processes and risk profile. The Microsoft 365 Agents SDK, Semantic Kernel, and Azure AI provide the building blocks to implement these capabilities while retaining full control over behaviour and data.
Seamless Deployment Across Microsoft 365
One of the advantages of using the Microsoft 365 Agents SDK is that your agent can be deployed to multiple channels without changing your core logic. This multi-channel capability means users can interact with the Project Assist Agent wherever it’s most convenient. Some deployment options include:
- WebChat: Broad reach via web interface.
- Microsoft Teams: Embedded in team channels for collaboration.
- Microsoft 365 Copilot: Extend or act as a custom skill within Copilot.
- SPFx (SharePoint Framework): Contextual assistance in SharePoint pages.
- Third-Party Platforms: Integrate with Facebook Messenger, Slack, Twilio, and more.
Conclusion: The Future is Powered by AI Agents
The combination of the Microsoft 365 Agents SDK, Semantic Kernel’s orchestration capabilities, and powerful cloud AI services is ushering in a new era of intelligent automation. The custom Project Assist Agent is a prime example of a solution that goes beyond generic copilots by leveraging these tools to create a deeply integrated, highly tailored, and proactive assistant.
By using the Agents SDK as a flexible foundation, Semantic Kernel to orchestrate complex, multi-step actions, and Retrieval-Augmented Generation (RAG) to power intelligent search, organizations can build custom agents that are not only powerful but also grounded in their unique business knowledge. The future of work is intelligent and adaptive, and with tools like these, developers are empowered to build the solutions that will drive it.
Ready to unlock new capabilities? Dive into modern agent frameworks and start building today!
Useful Resources
- Microsoft 365 Agents SDK documentation — Official documentation for the Microsoft 365 Agents SDK.
- Create .NET agents in Visual Studio using the Microsoft 365 Agents Toolkit — Quickstart and toolkit instructions for creating .NET agents.
- Microsoft 365 .NET Agents SDK samples (GitHub) — Sample agents and reference implementations.
- Teams Toolkit for Visual Studio (installation guide) — Install the Agents/Teams Toolkit extension for Visual Studio.
Source code
Note
You can find the complete source code from GitHub.