The instinct is to rebuild it in Microsoft Foundry. Usually that's the wrong call β and it costs you the low-code authoring surface, the built-in Microsoft 365 channels, and the Power Platform governance you already have.
Keep the agent in Copilot Studio. Put your own front end on it using the Microsoft 365 Agents SDK.
Rebuild in Foundry only when you can name the specific capability Copilot Studio cannot deliver β and even then, prefer adding a Foundry agent as a connected agent over a full migration.
Most "migrate to Foundry" requests are really one of seven underlying asks. Four of them don't require migration at all. Find your row before scoping the work.
| What they say | What they mean | Migrate? |
|---|---|---|
| "Make it a web app" | Branded chat UI on our domain | No |
| "We need our own UI" | Control look, layout, placement | No |
| "Put it on our SharePoint sites" | Wide internal rollout | No |
| "We need to own chat history" | Compliance, analytics, export | No |
| "Copilot Studio can't do X" | Custom orchestration or tooling | Maybe |
| "We need model Y / cheaper tokens" | Model choice + cost control | Likely |
| "Our AI team works in Python" | Code-first agent engineering | Yes |
Copy an <iframe> into your page. Zero code.
Microsoft 365 Agents SDK + MSAL + Web Chat. Your UI, your auth.
botframework-webchatThe lowest-level transport. You build more of it yourself.
Option A's embed code is only visible when the agent's security setting is No authentication. Turn on Authenticate with Microsoft or Authenticate manually and it vanishes from the Copilot Studio UI β you must use B or C.
The Copilot Studio Client library supports standard harness agents only: "Currently, you can only use the Copilot Studio client library with Copilot Studio agents created by using the standard harness. Agents using the GitHub Copilot harness aren't yet officially supported."
Check your agent's harness before planning this integration. On the GitHub Copilot harness, Option B isn't available today.
Sources: Integrate with web or native apps Β· Integrate with Copilot Studio
| Signal | Why Copilot Studio hits a ceiling |
|---|---|
| Specific model, or model flexibility | Foundry lets you pick from the catalog and swap models without changing agent code |
| Custom orchestration / multi-agent | Hosted agents run your Agent Framework, LangGraph, or OpenAI Agents SDK code as a container |
| Per-token cost control at volume | Copilot Studio bills on credits/message packs; Foundry bills per-call inference |
| Hard network isolation | Private networking, BYO VNet, VM-isolated sandboxes per session |
| Own conversation state in your datastore | Bring-your-own Azure Cosmos DB |
| Code-first CI/CD | Define agents via SDK/REST in a pipeline with version control |
Use both. Keep Copilot Studio as the conversational front door β it owns channels, topics, escalation, and governance β and delegate only the hard reasoning to a Foundry agent via connected agents. Migration risk stays contained to one capability instead of the whole agent.
Microsoft restructured this product. Tutorials that tell you to create a thread and then a run are describing Foundry Agent Service (classic) β deprecated, with a retirement date of 31 March 2027.
| Current | Classic | |
|---|---|---|
| Docs path | /azure/foundry/agents/ | /azure/foundry-classic/agents/ |
| Agent model | Prompt agents, Hosted agents, Responses API | Assistants-style agents |
| Conversation state | conversations; BYO Cosmos DB | threads / runs / messages |
| Status | Current | Retires 2027-03-31 |
The Bot Framework SDK is archived β support tickets are no longer serviced as of 31 December 2025. The official successor is the Microsoft 365 Agents SDK. If a blog post tells you to use Bot Framework, it's out of date.
The Copilot Studio Client hands you an object that drops straight into the standard
botframework-webchat React component. You get a production chat UI without building one,
but you own the page, the styling, and the auth.
Browser (your React app)
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β βββββββββββββββββ 1. loginPopup / silent β
β β MSAL Browser β ββββββββββββββββββββββββββββββ β
β β public client β βββββ access token βββββββββββ€ β
β βββββββββ¬ββββββββ β β
β β 2. token β β
β βΌ β β
β ββββββββββββββββββββββββ β β
β β CopilotStudioClient β β β
β βββββββββ¬βββββββββββββββ β β
β β 3. createConnection() β β
β βΌ β β
β ββββββββββββββββββββββββ β β
β β botframework-webchat β β β
β ββββββββββββββββββββββββ β β
βββββββββββββββββββββββββββββββββββ¬ββββββββββββββββ΄βββββ
βΌ
Microsoft Entra ID βββΆ Power Platform API
scope: CopilotStudio.Copilots.Invoke (agent runtime)
Note what's not there: no Direct Line secret, and no custom token-minting backend. MSAL runs as a public client and the token is a delegated user token, so the browser never holds a long-lived shared secret.
import { PublicClientApplication, InteractionRequiredAuthError } from '@azure/msal-browser'
import { CopilotStudioClient } from '@microsoft/agents-copilotstudio-client'
export async function acquireToken (settings) {
const msalInstance = new PublicClientApplication({
auth: {
clientId: settings.appClientId,
authority: `${settings.authority}/${settings.tenantId}`,
},
})
await msalInstance.initialize()
const loginRequest = {
// the SDK resolves the correct Power Platform scope for your cloud
scopes: [CopilotStudioClient.scopeFromSettings(settings)],
redirectUri: window.location.origin,
}
try {
const accounts = await msalInstance.getAllAccounts()
if (accounts.length > 0) {
const r = await msalInstance.acquireTokenSilent({ ...loginRequest, account: accounts[0] })
return r.accessToken // silent path β no prompt
}
} catch (e) {
if (!(e instanceof InteractionRequiredAuthError)) throw e
}
const r = await msalInstance.loginPopup(loginRequest)
return r.accessToken
}
const token = await acquireToken(settings)
const client = new CopilotStudioClient(settings, token)
const connection = CopilotStudioWebChat.createConnection(client, { showTyping: true })
// `directLine` is just the prop name β this is NOT the Direct Line channel
<FluentThemeProvider>
<Composer directLine={connection}>
<BasicWebChat />
</Composer>
</FluentThemeProvider>
Search for Power Platform API.
CopilotStudio.Copilots.InvokeAdd it, then grant admin consent if your tenant requires it.
Must match window.location.origin for every environment.
An AADSTS65001 error or an empty token is almost always step 1 or 2.
| Platform | Package | Version |
|---|---|---|
| JavaScript | @microsoft/agents-copilotstudio-client | 1.7.2 GA |
| JavaScript | @azure/msal-browser | 4.13.1 |
| JavaScript | botframework-webchat | 4.19.1 |
| .NET | Microsoft.Agents.CopilotStudio.Client | 1.8.77 stable Β· 1.9.40 beta |
| Python | microsoft-agents-copilotstudio-client | 1.5.0 |
A stable .NET release exists (1.8.77) alongside a -beta prerelease channel. Search NuGet with prereleases enabled and you'll only see 1.9.40-beta β make sure you're pinning the stable line unless you specifically need prerelease features.
Start with Microsoft's copilotstudio-webchat-react sample β validate your config with the console sample first, then bring up the UI.
There are three independent "sessions" in play, and they expire on different clocks. Most production bugs here come from assuming they're the same thing.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 1. BROWSER SESSION owned by: your app β
β MSAL cache, your app cookie β
β Ends: tab close, cache clear, explicit sign-out β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β 2. IDENTITY SESSION owned by: Microsoft Entra ID β
β Access token for the Power Platform / Direct Line scope β
β Ends: token expiry β renew silently via MSAL β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β 3. CONVERSATION SESSION owned by: Copilot Studio β
β conversationId + agent-side variable state β
β Ends: ~30 min inactivity, or you start a new one β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
A signed-in user with a valid token can still be talking to a brand-new conversation with none of their earlier context. If you want continuity, persist and reuse the conversationId yourself.
| Option | Variables your topics get | Control who chats? | Embed code? |
|---|---|---|---|
| No authentication | β | No β "anyone with the link" | Yes |
| Authenticate with Microsoft | User.ID, User.DisplayName | Yes, via sharing | No |
| Authenticate manually | + User.AccessToken, User.IsLoggedIn | Yes (Entra ID) | No |
The decisive question: does the agent need to call a downstream API as the user? If yes, only Authenticate manually gives you User.AccessToken.
Auth changes only take effect after you publish. Switching manual β Microsoft turns existing User.AccessToken references into Unknown variables and breaks those topics at runtime. And agents using Entra ID auth can't be embedded in Power BI reports via iframe visuals.
Source: Configure user authentication
"Don't expose the secret in any code that runs in the browser, either hard-coded or transferred through a network call." Exchange it for a short-lived token server-side.
| Path | Endpoint | expires_in |
|---|---|---|
| Copilot Studio token endpoint | GET <BOT TOKEN ENDPOINT> | 3600 s |
| Direct Line secret exchange | POST /v3/directline/tokens/generate | 1800 s |
Refresh proactively at ~75% of expires_in. You can refresh an unlimited number of times β but you cannot refresh an expired token. Let it lapse and the user's conversation dies.
Toggling Require secured access takes up to two hours to propagate. You don't need to publish the agent β but you do need to wait. Plan the change window.
Microsoft: "For security reasons, don't reuse the same app registration for both your agent and your custom website."
An authentication app (exposes the custom scope, authorises the canvas app) and a canvas app (SPA platform, redirect URI, access + ID tokens). Confirm accessTokenAcceptedVersion is 2. The canvas intercepts the sign-in prompt, requests an on-behalf-of token, and the agent exchanges it to populate User.AccessToken.
| Channel | SSO | Channel | SSO |
|---|---|---|---|
| Custom website | Yes | Demo website | No |
| SharePoint | Yes | Mobile app | No |
| Microsoft Teams | Yes* | Azure Bot Service channels | No |
| Omnichannel (live chat) | Yes | Power Apps / Power Pages | No |
* Teams requires extra config β skipping it makes users always fail authentication. Source: Configure SSO
"Guest users can't access AI-generated answers from SharePoint and Graph Connector data sources in SSO-enabled apps." Test this explicitly if external collaborators are in scope β it fails quietly.
Copilot Studio stores transcripts in two places, with two different retentions β and there is one exclusion that invalidates most compliance reporting built on them.
ConversationTranscript30 days by default, via a recurring bulk-delete job.
28 days, then auto-deleted.
Microsoft: "Agent responses that use SharePoint as a knowledge source aren't included in conversation transcripts⦠conversation transcripts include the question and the content of the source documents used to generate the response. However, the answer isn't included, and is marked as REDACTED."
For a typical intranet agent, Dataverse gives you the question and the sources β but not what the agent actually told the user. Any QA, compliance, or hallucination-review process built on Dataverse transcripts will not work.
Content caps at 1 MB. Longer conversations split across records sharing Name + ConversationStartTime with different Metadata.BatchId. Group and sort by BatchId, or your analytics silently truncate.from.role is an integer: 0 = agent, 1 = user. Getting it backwards inverts every report.Source: Conversation transcript controls
"The audit logs in the Audit solution don't include the full text or transcript of the interactions between a user and the agent, only the transcript thread ID. The Data Security Posture Management (DSPM) for AI solution attempts to retrieve the chat text related to the event."
If you need discoverable chat text, Audit alone is not enough. To suppress retention entirely, use a Purview Data Lifecycle Management policy with 0-day retention for Copilot Experiences.
Given the redaction behaviour, the 28/30-day windows, and the 30-minute write lag, most enterprise web apps should mirror the transcript into their own store.
Browser (Web Chat)
β activities in / out
βΌ
βββββββββββββββββββββββββ ββββββββββββββββββββββββββββ
β Your BFF / API βββββββββΆβ Azure Cosmos DB β
β β’ mints tokens β β β’ partition: userId β
β β’ mirrors activities β β β’ doc: conversationId β
β β’ enforces retention β β β’ items: activities[] β
βββββββββββββ¬ββββββββββββ ββββββββββββββββββββββββββββ
βΌ
Copilot Studio agent
Mirror server-side, not in the browser β a client-side mirror can be tampered with and is lost on tab close. Microsoft's baseline chat architecture names three triggers for self-managing history: zero-data-retention obligations, custom context-window control, and multiple UX channels with independent session semantics.
Two working references with real server-side history you can port:
sample-app-aoai-chatGPT and
azure-search-openai-demo.
Both are OpenAI-centric, but the history layer is agent-agnostic.