A Teams IT helpdesk agent in Copilot Studio: what shipped in v1 and what the test panel never showed

Three outbound flows, a SharePoint knowledge base, and the behaviours that only appeared once the agent was running in the real Teams client.

Employees ask IT the same handful of questions. The answers already exist, written up and approved, sitting in SharePoint. The gap is not knowledge. It is that somebody has to read the question, find the right page, and paste it back. 

I built the Teams surface for that in Copilot Studio and shipped it as a personal app scoped to a single Entra ID security group. This covers the v1 architecture, the behaviours that only surfaced once it was running in the real Teams client rather than the test panel, and an honest list of what v1 does not do. 

There was already a help desk pipeline behind this. I built it on n8n and Microsoft 365, running three intake channels against the same approved SharePoint knowledge base, and wrote it up separately: An AI Help Desk Built on n8n and Microsoft 365, Locked to Your Approved KB. The Outlook path there is push-driven, firing on a Microsoft Graph change notification the moment mail arrives. The voice path runs in real time through an ElevenLabs agent. The Teams path is the one that does not fit the pattern: a scheduled workflow that wakes every 30 minutes, reads new messages posted to a Service Desk channel, and processes whatever it finds. 

A 30-minute cycle is reasonable for a ticket queue. It is a poor fit for an employee typing a question and waiting on an answer. Closing that gap is what this build is for, and Copilot Studio closes it by changing the shape of the channel rather than shortening the interval: a personal app with a conversational surface, triggered on the message, holding its own conversation state, rendering citations natively.

What the employee sees 

An employee opens the assistant in Teams and asks a question in plain English. One of three things happens. 

Figure 1. Published to the tenant app store under Built for your organisation. Access is scoped to an Entra ID security group, so it appears only for people in the pilot.

If the question matches the knowledge base, the answer comes back grounded in a SharePoint Site Page, with a citation the employee can open. No ticket, no card, no email, no log row.

If the employee asks for a person, or says the previous answer did not work, the agent routes into a topic that asks one question: how urgent is this. Low, Medium, or High. It then creates a ticket ID in the form HD-YYYYMMDD-XXXX and confirms it in the chat.

Figure 2. The follow-up path. One urgency question, then a ticket ID the employee can quote back.

If the question matches nothing in the knowledge base and contains no explicit request for a human, it still ends up as a ticket. That last case is the one that took a change to the system topics to get right, and it is covered further down. 

Every escalation fans out to three places. An adaptive card lands in the Service_Desk channel, orange for a follow-up and red for a fresh escalation. An email goes to the service desk mailbox with the ticket ID bracketed into the subject line. A row is written to an ExecutionLog list in SharePoint with the channel recorded as teams-copilot. 

Figure 3. Follow-up cards are orange and carry a different subtitle, so the service desk can tell a repeat from a first report without opening it.

Figure 4. A question with no knowledge base match becomes a red card rather than a dead end.

Figure 5. The same escalation reaches the mailbox with the ticket ID in the subject, so it threads and searches without anyone opening Teams.

The architecture 

Outlook and voice stay where they are. Nothing in the n8n pipeline changed to make this work, and the knowledge base is shared across both systems while being consumed two different ways. The n8n workflows read the HelpDeskKB list directly and send the resolution text out of a column. The Copilot Studio agent grounds on the SharePoint Site Pages that the same list's KBArticleURL column points at. One set of approved content, two retrieval paths, and a single place for the IT team to edit either. 

Within the Teams surface, eight components, and each one owns exactly one thing.

Components of the Teams surface and what each one owns.
ComponentWhat it owns
Copilot Studio agent (generative orchestration)Routing, knowledge retrieval, response composition
SharePoint Site Pages, knowledge source HelpDeskKBThe approved answers themselves
Escalate topicUrgency capture and ticket creation for a fresh escalation
Follow-up topicThe same, with a follow-up flag set so downstream artefacts differ
Conversational boosting (system topic, modified)The catch-all for anything the other two do not claim
HD_WriteLog (Power Automate, Graph HTTP, app-only)Ticket ID generation and the ExecutionLog row
HD_EscalateEmail (Power Automate, Graph HTTP, app-only)The escalation email to the service desk mailbox
HD_EscalateAgentCard (Power Automate, Teams connector, delegated)The adaptive card in the Service_Desk channel
IT Helpdesk Assistant request flow An employee message in Teams reaches the Copilot Studio agent. Matched questions are answered from the HelpDeskKB knowledge source. Unmatched questions and follow-ups route to the Escalate or Follow-up topic, which calls three Power Automate flows. HD_WriteLog and HD_EscalateEmail authenticate app-only against Microsoft Graph. HD_EscalateAgentCard posts under the employee's delegated identity and requires a one-time consent. Employee message in Teams Copilot Studio agent generative orchestration match found no match, or follow-up HelpDeskKB SharePoint Site Pages Grounded answer no ticket, no card, no log row Escalate / Follow-up topic urgency captured, ticket ID issued HD_WriteLog app-only Graph HD_EscalateEmail app-only Graph HD_EscalateAgentCard delegated identity ExecutionLog SharePoint list ServiceDesk mailbox ticket ID in subject Service_Desk channel adaptive card One-time consent prompt on first use App-only. Employee identity not used. Delegated. Runs as the employee. Answer path. Nothing is written.

Answering costs nothing downstream. Escalating writes to three places, and one of them runs as the employee rather than as the application.

The identity story is not uniform across those three flows, and that is worth being direct about. HD_WriteLog and HD_EscalateEmail authenticate app-only against Microsoft Graph using an app registration and its client secret. The employee's identity is irrelevant to those two calls. HD_EscalateAgentCard is different: it posts through the Teams connector under the invoking employee's delegated identity, which gives the service desk an authoritative record of who escalated, and which is also the reason the employee sees a consent prompt the first time the agent creates a ticket for them. 

Figure 6. The delegated posting path costs one consent click per employee. This is the most visible tradeoff in v1.

The model driving orchestration is an Anthropic Claude model rather than the OpenAI default. That choice has a governance consequence that is not visible anywhere in the Copilot Studio interface. Anthropic is onboarded as a Microsoft subprocessor, so the Microsoft Product Terms, the Data Protection Addendum, and Enterprise Data Protection apply, and the Customer Copyright Commitment covers Copilot Studio. What does not apply is data boundary scope: Microsoft Learn states that Anthropic models in Copilot Studio are excluded from the EU Data Boundary and, where applicable, from in-country processing commitments. Anthropic models are on by default in commercial cloud outside the EU, EFTA, and the UK, and access can be scoped to specific users or security groups from the Microsoft 365 admin center. For a tenant with residency obligations, that is a decision to make deliberately rather than inherit from a dropdown. 

What the three flows do 

All three are instant cloud flows in the G2C Helpdesk Automation solution, called as custom actions from the topics rather than triggered on a schedule or an event. The agent decides when they run. 

Figure 7. Three instant flows, each called as a custom action from a topic. Two carry the premium connector marker. 

HD_WriteLog generates the ticket ID and writes the row to the ExecutionLog list in SharePoint, carrying the channel, the outcome, and the ticket reference. It authenticates app-only against Microsoft Graph using an app registration and its client secret, so the row is written by the application rather than by whoever was talking to the agent. The ID it returns is what the other two artefacts key off, which makes this the first flow to run in an escalation and the one whose failure would leave the other two without a reference.

HD_EscalateEmail sends the escalation from the IT support mailbox to the service desk mailbox, with the ticket ID bracketed into the subject so it threads and searches cleanly. Same app registration, same app-only Graph path. Employee identity is irrelevant to the call, which is the point: the mail has to arrive whether or not the employee has ever consented to anything.

HD_EscalateAgentCard is the one that breaks the pattern. It posts the adaptive card to the Service_Desk channel through the Teams connector under the employee's delegated identity, not app-only. That gives the service desk an authoritative record of who escalated, and it is the reason for the consent prompt. Two flows that do not care who the employee is, one that does, and the whole consent tradeoff sits in that third line.

What the agent is not allowed to do 

Tool scoping is a governance decision rather than a feature list. The useful question about an agent like this is not what it can reach. It is what it was deliberately prevented from reaching, and why. 

Five things are on. The three flows above, the HelpDeskKB knowledge source grounding on the ITHelpdeskAutomation SharePoint site, and generative answers inside the modified Conversational boosting topic. File upload is also on, which is a considered yes rather than an oversight: the file comes from the employee, it is not retained in the agent, and the model's use of it is scoped to that one message. 

Figure 8. Three custom actions on, both Work IQ tools off. The off switches are the governance decision. 

Work IQ is off, and that one took the most thought. Turning it on brings tenant-wide Graph retrieval into play, which blends knowledge base content with unrelated Microsoft 365 content under unified citations. The knowledge base scope stops being enforceable at that point, and the claim that answers come from approved content stops being checkable. It is a capable feature and it is the wrong feature for a helpdesk agent whose entire argument is bounded retrieval. 

Web search is not enabled, and general knowledge fallback is off, for the same reason stated once: if the answer is not in the knowledge base, the agent escalates rather than composes. An IT helpdesk that answers from the open web will produce fluent, plausible, wrong instructions about a tenant it knows nothing about. 

The ungrounded responses setting is off. That is the toggle that lets the model compose an answer when grounded retrieval comes back empty, and leaving it on would make the design principle unenforceable rather than merely leaky. Given what the next section covers about composed text, it is worth being clear that this control governs whether the model answers ungrounded, not whether it writes ungrounded sentences around a grounded answer. 

No third-party connectors are attached. Not Salesforce, not ServiceNow, not Jira, not Slack. Each one expands the surface the model can reach and each one is its own governance conversation. None of them are needed to answer a password reset question. 

The principle, and where it bends 

The design rule for this build was narrow on purpose. The AI classifies. The knowledge base answers. Nothing employee-facing is authored by a model at runtime. 

That rule holds at the retrieval layer. It does not hold as cleanly at the rendering layer, and this is the part most Copilot Studio coverage skips. 

Citations in Copilot Studio attach to a message, not to a sentence. When the model wraps grounded content in a conversational opener, a sign-off, or a line about what happens next, that composed text sits inside the same message and under the same citation marker as the retrieved content. Nothing in the rendering distinguishes the two. A response can read as more sourced than it is. 

Tightening the agent instructions helped more than I expected. A strict response-style block removed openers, sign-offs, emoji, and references to earlier turns on a fresh account. It did not remove everything. On an account with a prior escalation in its transcript, the agent still produced a line describing what would happen next and attributed it to the knowledge base process, phrasing that appears nowhere in the knowledge base. 

A later re-test produced the worst version of this. The same account, the same password reset question, and the grounded steps came back correctly with their citation. Underneath them the model composed a sentence saying that because the employee had mentioned ongoing issues, the ticket would be routed to the Follow-up topic so a human agent could pick it up with context. 

None of that happened. No urgency picker appeared. No ticket was created, no card reached the Service_Desk channel, no email went to the mailbox, and no row was written to the log. The sentence named a real topic in the agent and described a real routing path, which is what makes it convincing, and it sat under the same message-level citation marker as the knowledge base content above it. 

Figure 9. The grounded steps are correct. The closing sentence refers to the employee's earlier ticket and reports a routing action that never took place, both under the same citation.

The three examples escalate. First the model refers to an earlier ticket. Then it rewords that reference differently on each run. Then it narrates system behaviour that did not occur. The first two add unsourced context to a sourced answer. The third tells the employee something false about the state of their request, in the register the system uses for its own status messages. 

Grounding is a spectrum rather than a switch. Retrieval was correct in every one of those responses. What varied was everything the model wrapped around it, and the citation marker covered all of it equally. 

The rule this suggests is simple enough. Grounded retrieval is for answers, authored topic messages are for anything describing what the system did, and model prose about system behaviour is text rather than status. 

Instructions in generative orchestration are advisory. They shape output, they do not constrain it. Treat them as a strong nudge with a measurable hit rate, not as a control, and test them against an account that has history rather than a clean one.

Three things the test panel did not show 

1. Conversational boosting is the catch-all, not an inert topic 

The Learn page on generative orchestration lists a known limitation: with generative orchestration turned on, an agent does not use the Conversational boosting system topic when it searches knowledge sources. It is easy to read that as the topic never running at all. The sentence is scoped to knowledge search. 

In the real Teams client, that topic is exactly what catches a query when no custom topic claims it and no knowledge retrieval succeeds. Two out-of-scope test questions, one asking about a restaurant near the office and one asking how to install Zoom, both landed on the topic's “All other conditions” branch and stopped there, showing a polite fallback message. No routing, no ticket, no card, no log row. From the employee's side it looked like a promise to connect them to a human that never happened. 

The fix was one node. On that branch, replace the message and the End current topic node with a redirect to the Escalate topic. Both questions then produced tickets, cards, and log rows, and the original query text was carried into the ticket rather than being lost.

Figure 10. The branch now redirects to Escalate, which is what turns an unanswerable question into a ticket. 

The design lesson is not that the documentation is wrong. It is that a well-written trigger description on a custom topic is not the only routing surface in the agent, and the system topics are still doing work underneath.

2. Teams keeps the transcript, and the model reads it 

The same question, asked by two different accounts, produced two different answers. 

A fresh account asking how to reset a password got the grounded answer and nothing else. An account with an earlier escalation still sitting in its Teams chat history got the grounded answer plus a composed reference back to that ticket, which is the response shown earlier. Re-running the query produced a differently worded version of the same reference, so it is not a fixed string the model appends. 

Microsoft documents the mechanism. Agents use recent conversation history when deciding how to respond, some channels including Teams maintain extensive conversation history by design, and Learn explicitly notes that this is why a fresh test panel conversation and a longer running Teams conversation can answer the same question differently. The behaviour is expected. What is not obvious is the consequence. 

An IT director who has been told the agent is grounded to an approved knowledge base will reasonably hear that as the model only sees the knowledge base. In a Teams personal chat, the model also sees whatever the employee said earlier, including previous tickets and whatever context they pasted in while describing a problem. That is a different conversation to have with a security reviewer than the one about knowledge sources. 

There is a lever, and it works. The Reset Conversation system topic fires on natural phrasing, so an employee typing new conversation or start over gets a confirmation prompt and a fresh start. Run on an account carrying prior escalation history, the same knowledge base query afterwards came back clean, with none of the composed cross-turn text described above. 

Figure 11. Reset Conversation fires on natural phrasing. Typing "new conversation" produces a confirmation prompt and a fresh start.

Figure 12. The same question after the reset. The response ends at the citation, with no reference to the earlier ticket. 

Worth knowing that Microsoft Learn describes this topic as clearing session variables rather than conversation history. What is observable from the Teams client is the behaviour, not the mechanism, and the behaviour after a reset was clean. If you are relying on this, test it in your own tenant rather than taking either the documentation or this article at face value. 

The real problem is that none of it is obvious. An agent sitting in Teams gets used the way employees already use Teams chats, which is not by triggering a reset topic between unrelated questions. The transcript accumulates by default and the model reads it by default. The lever exists and it sits behind user awareness that a pilot deployment will not have unless someone puts it in the onboarding message. 

3. Instructions hold on style, not on composition 

Covered above in principle, and worth separating as a finding because the two halves failed differently. The prohibitions that describe a visible surface feature, no openers, no sign-offs, no emoji, no references to earlier turns, held on a fresh account. The prohibition that requires the model to judge whether a sentence is grounded did not hold on an account with history, and the routing sentence shows how far that can go: an instruction not to invent content did not stop the model inventing a system action. 

If you are writing acceptance criteria for an agent like this, that split is the useful shape. Style rules are testable and they mostly pass. Content-composition rules are testable and they partly pass. Do not write the second kind into acceptance criteria you have to sign off against. 

What it consumes 

An agent built in the standalone Copilot Studio and deployed to Teams consumes Copilot Credits. The exception is worth knowing: when the person using it holds a Microsoft 365 Copilot license and the agent runs under that authenticated identity, the usage is not billed, subject to fair use limits. For a helpdesk agent aimed at the part of the workforce least likely to be sitting on a Copilot license, plan on the metered path. 

The published rates, per Microsoft Learn: 

Copilot Credits billing rates, per Microsoft Learn.
Agent featureRateM365 Copilot licensed user
Classic answer1 Copilot CreditNo charge
Generative answer2 Copilot CreditsNo charge
Agent action5 Copilot CreditsNo charge
Tenant graph grounding for messages10 Copilot CreditsNo charge
Agent flow actions, per 100 actions13 Copilot CreditsNo charge

No charge applies to employee-facing use when the agent runs under the authenticated Microsoft 365 Copilot user's identity, subject to fair use limits.

Agent actions cover triggers, deep reasoning, and topic transitions. That last one is relevant to the redirect described above: sending every unmatched question through Conversational boosting into Escalate adds a topic transition to a path that previously ended in a message. It is a correct change and a cheap one, but it is not free, and an agent whose architecture leans on redirects will show it in the meter. 

One boundary makes the number smaller than a first read suggests. Power Automate cloud flows run on Power Automate licensing rather than Copilot Credits and are not subject to Copilot Studio billing or enforcement, which takes all three outbound flows in this build off the credit meter. Note that this covers Power Automate cloud flows specifically, not the agent flows in the rate table above, which are a different object and are metered. 

Do not assume the test panel is free. Learn is explicit that test runs do not consume capacity for agent flow actions, and that exemption is scoped to agent flow actions. Microsoft has answered elsewhere that testing an unpublished agent can still consume credits, because consumption follows what the agent does rather than whether it has been published. 

There are two ways to hold capacity, and the choice matters more than the rates do. The pay-as-you-go meter bills in arrears at $0.01 per Copilot Credit with no upfront commitment. A capacity pack is $200 a month for 25,000 credits, and unused credits do not carry into the next month. This build runs on the meter, which is the sane default for anything that has not been measured yet: a grounded answer at 2 credits costs two cents, and nothing is committed before there is usage data to commit against. 

The distinction has an operational edge as well as a financial one. A tenant that reaches 125 percent of prepaid capacity has its custom agents disabled, with in-flight conversations allowed to finish and every subsequent invocation rejected. On the pay-as-you-go meter that enforcement does not apply, because the overage bills to the Azure subscription instead. Running a pilot on prepaid capacity means an agent that can stop answering employees mid-pilot for a billing reason. 

One licensing detail is easy to trip over when starting from nothing. The Copilot Studio user license costs nothing, but it cannot be assigned until the tenant holds a prepaid credit pack subscription. The way in without that is the Copilot Studio authors role, granted by creating a security group and assigning it to the Copilot Studio author setting in the Power Platform admin center. A Microsoft 365 Copilot license also grants access.

What v1 does not do 

Every one of these was a decision, not an oversight, and each is on the list for the next pass. 

The consent card stays. An employee sees a Microsoft consent prompt the first time the agent posts an escalation card on their behalf, because that path runs under their delegated identity. It is one click and it is Microsoft standard behaviour, but it is a first-use event that has to be explained to pilot users rather than discovered by them. 

Follow-up tickets carry the wrong description. The Follow-up topic captures the text of the message that triggered it, which is usually a phrase like "that didn't work." The service desk gets a ticket whose description is the complaint rather than the original problem. Fixing it means capturing the preceding turn rather than the one that triggered the topic. 

The client secret lives in the flow rather than in Key Vault. Two of the three flows authenticate app-only against Graph, which means the secret sits in the flow definition, and a flow definition travels. It appears in exports, in solution packages, and in version history, which is three places a secret should never be. 

The production shape is not complicated. Store the secret as a versioned secret in an Azure Key Vault in the same resource group as the app registration. Have the flow retrieve it at runtime with the Key Vault Get Secret action and pipe it into the token acquisition call. Grant the Power Automate service principal access through a vault access policy or RBAC. Rotation then happens in Key Vault and no flow gets edited. 

What that buys is worth naming precisely, because it is more than tidiness. The secret stops appearing in anything exportable. Revocation and audit move to one place. Rotation becomes an operation rather than a change to three flows, and the whole thing clears the security review line item about secrets not living in application code. 

It was deferred rather than missed. Wiring it costs somewhere around three to four hours, and at v1 that time was better spent on the surface the employee actually touches. It also belongs with the rest of the production-readiness work rather than being done alone, since a tenant that needs Key Vault also needs the logging and the pilot scoping sorted at the same time. In a sandbox with two test accounts the exposure is theoretical. In a tenant with real employees it is not, and that is the line where this stops being a deferral and starts being a defect. 

Only escalations write to the log. A question answered straight from the knowledge base produces no row, which means the metric an IT director would most want, the proportion of questions resolved without a human, is the one the log cannot currently produce.

Figure 13. Escalations land in the log with the channel and outcome recorded. Answered questions currently do not.

Where this leaves it 

The part of this build I would defend to a security reviewer is the narrow one: the knowledge base is the only thing that answers, and the model chooses which approved page applies. The part I would flag before anyone else did is that grounding is a property of retrieval, not of rendering, and that a Teams personal chat hands the model more context than the knowledge source alone. 

Neither of those showed up in the test panel. Both showed up as soon as two accounts started putting real questions to the agent in the Teams client, which is the argument for shipping a scoped pilot early rather than testing longer. 

Removing that consent prompt turns out to cost more than it saves at this scale. Graph has no application permission for posting channel messages outside migration scenarios, so the alternatives are an incoming webhook, a service account holding a stored refresh token, or a Bot Framework registration, none of which are a small change. For a pilot measured in single-digit users, that is a handful of clicks against real infrastructure work. The next piece covers applying Purview to an agent that is already shipped. 

Questions about which path fits your environment are welcome. Please feel free to reach out.

Next
Next

A Grounded IT Knowledge Agent on Copilot Studio and SharePoint, Locked to Your Runbooks