Showing Agent Reasoning in Custom UIs
IMPORTANT! This is the sample repository for https://microsoft.github.io/mcscatblog/posts/show-reasoning-agents-sdk/. Follow the article for a more detailed explanation.
Render Anthropic reasoning traces from Microsoft 365 Copilot Studio agents inside your own UI. This README distills the companion article posted on MCS CAT blog.
Why Bubble Up Reasoning?
- Strengthen trust in automated or assisted decisions.
- Give operators visibility into multi-step, decision-heavy workflows.
- Help end users judge the suitability of an answer before acting on it.
Demo Scenario
The reference sample (static HTML + JS) simulates an organization triaging monday.com tickets with an Anthropic-enabled Copilot Studio agent. Submitting a new ticket shows incremental reasoning as typing activities, and near-duplicate tickets are merged automatically instead of duplicated.
Prerequisites
- Copilot Studio agent configured with an Anthropic model (Settings -> Agent model).
- Custom UI wired to the Microsoft 365 Agents SDK.
- Optional backend summarization endpoint to shorten verbose reasoning (recommended for UX). GPT-family models do not yet emit reasoning traces.
Core Flow
- Initialize the client
import { CopilotStudioClient } from '@microsoft/agents-copilotstudio-client'; import { acquireToken } from './acquireToken.js'; import { settings } from './settings.js'; export const createCopilotClient = async () => { const token = await acquireToken(settings); return new CopilotStudioClient(settings, token); }; - Start a conversation
const copilotClient = await createCopilotClient(); let conversationId; for await (const act of copilotClient.startConversationAsync(true)) { conversationId = act.conversation?.id ?? conversationId; if (conversationId) break; } - Send a prompt
const prompt = `Create the following ticket:\n\nTitle: ${shortTitle}\nDescription: ${longDescription}`; const activityStream = copilotClient.askQuestionAsync(prompt, conversationId); - Capture reasoning and answers
for await (const activity of activityStream) { if (!activity) continue; const activityType = activity.type?.toLowerCase(); if (activityType === 'typing' && activity.channelData?.streamType === 'informative') { const streamKey = resolveStreamKey(activity); const previousActivity = streamLastActivity.get(streamKey); if (previousActivity && isContinuationOfPrevious(previousActivity, activity)) { streamLastActivity.set(streamKey, activity); continue; } await flushActivity(previousActivity, false); streamLastActivity.set(streamKey, activity); continue; } if (activityType === 'message') { agentMessages.push(activity.text); continue; } }
Detect Informative Typing
const isReasoningTyping = (activity) =>
(activity?.type || '').toLowerCase() === 'typing' &&
activity?.channelData?.streamType === 'informative';
Summarize Long Thoughts
Reasoning chunks can be lengthy. Capture completed thoughts and optionally POST them to a backend summarizer:
async function summarizeSafely(text) {
try {
const res = await fetch('/api/summarize', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text })
});
const { summary } = await res.json();
return summary.trim();
} catch {
return null;
}
}
Keep API keys server-side and apply rate limiting. The sample UI exposes an input control so end users can supply their summarizer key during demos.
UI Pattern Tips
- Show a calm, rotating status label once users submit a ticket (for example, “Reviewing possible options…”).
- Keep the spinner visible while informative typing events are active.
- Prepend the newest summarized reasoning to the top, keeping the latest five items.
- Add a subtle entrance animation and collapse the panel once the final answer arrives, with an optional “Show details” toggle.
Summary Checklist
- Switch your agent to an Anthropic model.
- Iterate the Microsoft 365 Agents SDK activity stream.
- Detect reasoning via
type === 'typing'andchannelData.streamType === 'informative'. - Group reasoning chunks by
channelData.streamId. - Flush pending reasoning when you receive the final message.
- Optionally summarize server-side to protect secrets.
- Render a compact Thinking panel with recent updates.
Try It Yourself
- Clone the reference implementation.
- Configure Copilot Studio with an Anthropic model.
- Run the sample locally, observe informative typing streams, and integrate your summarizer.
Questions or feedback on the pattern are welcome.