Copilot Studio botcomponent or conversation transcript analyser
Python
17
51 commits
updated Sep 7, 2026
Peek under the hood of your Copilot Studio agents. Upload a bot export, drop a conversation transcript, or connect straight to Dataverse — instantly see what your agent is actually doing under the hood: how the orchestrator routes decisions, which topics/tools/agents fire and why, where knowledge searches hit or miss, how long each step takes, and what falls through the cracks. Architecture reports, best-practice rules, trigger overlap detection, execution timelines, credit estimates, response quality scoring, and instruction compliance checking.
Everything you need to build with confidence and debug without guessing. If you're serious about Copilot Studio development, this belongs in your toolkit.

.zip export, or connect directly to your environment and auto-analyse on login| Feature | Description |
|---|---|
| Upload bot export | Drop a .zip, or botContent.yml + dialog.json — get a full architecture report with quick wins |
| Connect to Dataverse | Device-code auth to your environment, auto-analyses your bot the moment you connect |
| Routing analysis | Orchestrator decision timeline with routing scores, topic lifecycles with redirect tracking, trigger phrase similarity, plan evolution diffs with thrashing detection |
| Conversation transcripts | Upload or fetch transcripts from Dataverse — sequence diagrams, Gantt charts, event logs, per-turn efficiency and latency breakdown |
| Single conversation lookup | Fetch and analyse a specific conversation by ID directly from Dataverse |
| Response quality scoring | Groundedness assessment for every bot response — detects ungrounded answers, hallucination risk from zero-result searches, and silently swallowed tool errors |
| Instruction alignment | Checks if the bot's runtime behavior matches its system instructions — language compliance, escalation triggers, scope restrictions |
| Dead code detection | Cross-references bot components against runtime evidence to find topics, tools, and knowledge sources that are never used |
| Knowledge effectiveness | Per-source hit rate, contribution rate, and error tracking — identifies knowledge sources that never contribute to grounded answers |
| Multi-agent delegation | Traces orchestrator-to-agent delegation chains — detects dead agents, always-failing agents, and shows orchestrator reasoning per delegation |
| Latency bottlenecks | Per-turn time breakdown showing where time is spent (thinking, tools, knowledge, delivery) with bottleneck flagging |
| Plan evolution diffs | Structured diffs between consecutive orchestrator plans within a turn — detects thrashing, scope creep, and re-planning patterns |
| Batch analytics | Aggregate multiple Dataverse transcripts — success/failure/escalation rates, topic usage, error patterns, credit estimates |
| Custom rules | 18 default best-practice rules + user-defined YAML rules, evaluated during analysis |
| Tool call analysis | Runtime tool call tracing — per-tool statistics, async chain detection, orchestrator reasoning, Mermaid flow diagrams. Supports MCP servers, connectors, child/connected agents, A2A, flows, CUA |
| Component Explorer | Inline searchable picker over every topic and tool (User / System / Automation topics, MCP servers, connectors, flows, child / connected / A2A agents) with KB-sourced explanations per setting |
| LLM Audit Runner | Multi-mode audit (default + opt-in: conversation summary / sentiment / PII / answer accuracy / topic routing / custom prompts) — runs in parallel via OpenAI or Anthropic |
| Failure Diagnosis (AgentRX) | AgentRx-style root-cause diagnosis: 10-category taxonomy, 6 heuristic rules, LLM judge with confidence + secondary findings, streaming chat-with-judge, HITL exchange surfacing in the Conversation Flow |
| Exports | Markdown / HTML / Print → PDF / Audit-bundle downloads — every dynamic-page surface (Variable Tracker, Performance Waterfall, Citation Verification, etc.) is reflected in the exports |
| Dark / Light mode | Respects your OS preference, green accent theme throughout |
| Analysis counter | Tracks how many analyses you've run, with cat-themed gamification milestones |
Five ways to get data in, two channels of data, one parsing pipeline, optional LLM enrichment, four export formats. Nothing leaves your machine unless you explicitly click an LLM action — the dashed box below is the only network boundary.
flowchart TD
subgraph Inputs["📥 Input sources"]
direction TB
I1[Upload ZIP<br/>full Copilot Studio export]
I2[Upload loose files<br/>botContent.yml + dialog.json]
I3[Upload transcript JSON]
I4[Loose files + transcript<br/>combined upload]
I5[Live Dataverse fetch<br/>OAuth device code]
end
I1 --> BC
I2 --> BC
I4 --> BC
I4 --> TR
I3 --> TR
I5 --> BC
I5 --> TR
BC[Bot content channel<br/>static structure: topics, tools,<br/>knowledge, routing] --> PB[Parse YAML / JSON]
TR[Transcript channel<br/>runtime trace: events,<br/>tool calls, decisions] --> PT[Parse activity log<br/>→ timeline events]
PB --> M[Pydantic models<br/>BotProfile + ConversationTimeline]
PT --> M
M --> R[Renderer<br/>markdown + mermaid]
R --> T1[Profile / Tools /<br/>Knowledge / Routing tabs]
R --> T2[Conversation / Quality tabs]
T1 -.->|opt-in| LLM
T2 -.->|opt-in| LLM
subgraph LLM["🔒 Opt-in LLM enrichment — leaves your machine ONLY when you click Run / Diagnose"]
L1[LLM Audit Runner<br/>instruction lint]
L2[AgentRX judge<br/>10-category verdict]
L3[Ask the judge<br/>streaming chat]
L4[AI recs<br/>augment canned recs]
end
T1 --> Out
T2 --> Out
LLM --> Out
subgraph Out["📤 Outputs"]
O1[Markdown .md]
O2[HTML .html]
O3[PDF print]
O4[Audit bundle .md]
end
classDef input fill:#dbeafe,stroke:#1e40af,color:#1e3a8a
classDef channel fill:#ede9fe,stroke:#6d28d9,color:#4c1d95
classDef process fill:#fef3c7,stroke:#a16207,color:#713f12
classDef tabs fill:#d1fae5,stroke:#047857,color:#064e3b
classDef llm fill:#fecaca,stroke:#b91c1c,color:#7f1d1d
classDef output fill:#d1fae5,stroke:#047857,color:#064e3b
class I1,I2,I3,I4,I5 input
class BC,TR channel
class PB,PT,M,R process
class T1,T2 tabs
class L1,L2,L3,L4 llm
class O1,O2,O3,O4 output
style LLM fill:#fee2e2,stroke:#b91c1c,stroke-width:2px,color:#7f1d1d,stroke-dasharray:5 5
Two data channels, not one. Bot content is the static structure of your agent (topics, tools, knowledge sources, routing config). Transcript is a runtime trace of what actually happened in a conversation. Either alone gets you partial coverage:
A live Dataverse fetch always pulls both — it grabs bot + botcomponent (bot content) and conversationtranscript (transcripts) in one go.
Opt-in LLM enrichment. The dashed cyan box is the only place your data hits an external API. Parsing, heuristic rules, rendering, exports — all local. The LLM Audit Runner needs your own OpenAI / Anthropic key; the AgentRX judge + chat reuse it. Off by default. When you do turn them on, PII redaction (emails, phones, IBAN, BSN, credit-card numbers, names) is on by default for the AgentRX judge so transcripts get scrubbed before they leave the box.
git clone https://github.com/Roelzz/mcs-agent-analyser.git
cd mcs-agent-analyser
cp .env.example .env # default login: inspector / underthehood
uv sync
uv run reflex run
git clone https://github.com/Roelzz/mcs-agent-analyser.git
cd mcs-agent-analyser
Copy-Item .env.example .env # default login: inspector / underthehood
uv sync
uv run reflex run
git clone https://github.com/Roelzz/mcs-agent-analyser.git
cd mcs-agent-analyser
copy .env.example .env REM default login: inspector / underthehood
uv sync
uv run reflex run
Open http://localhost:3000, sign in with inspector / underthehood, and upload a .zip bot export or connect to Dataverse.
Privacy note: Deploy this locally or self-host in your own Azure tenant. Bot exports and Dataverse data never leave your machine. External API calls are only made when you opt into the LLM Audit Runner (uses OpenAI or Anthropic).
uv run python main.py path/to/botContent --all
Scans all subfolders containing botContent.yml + dialog.json and writes a report.md into each. If a Transcripts/ subfolder exists, every .json transcript gets a matching .md report.
Single folder:
uv run python main.py path/to/botContent_folder
uv run python main.py path/to/botContent_folder -o custom_report.md
The dynamic analysis page presents bot and conversation data across 6 purpose-driven tabs:
| Tab | Icon | What it answers |
|---|---|---|
| Profile | user-round | What is this bot? Architecture, AI config, model, security, metadata, custom findings |
| Tools | wrench | What can it do, and did it work? Component Explorer (topics + tools + agents), inventory, runtime stats, agent delegation, topic graph |
| Knowledge | database | Is the knowledge useful? Sources, search results, source effectiveness, citation verification |
| Routing | route | How did orchestration work? Decision timeline, plan evolution diffs, topic lifecycles, trigger analysis, topic coverage |
| Conversation | message-square | What happened? Visual dashboard, chat replay, sequence/Gantt diagrams, performance waterfall, variable tracker, turn efficiency, latency bottlenecks |
| Quality | shield-check | How can I improve? LLM Audit Runner, AgentRX failure diagnosis, credits estimate, quick wins, response quality, dead code, instruction alignment |
When uploading a transcript without a bot export, a reduced tab bar shows: Conversation, Tools, Routing, Quality.

tool_type

Deep-linking: every entity-naming visualization on this tab is clickable. Variable Tracker cards, Waterfall rows, Phase Breakdown rows, Reasoning rows, and Conversation Flow rows jump to the canonical destination (Tools tab Component Explorer for tools / topics / agents; Knowledge tab for knowledge calls). An Expand all / Collapse all toolbar at the top toggles every accordion in one click.

From botContent.yml:
BeginDialog)From dialog.json:
From Dataverse (live connection):
From transcript .json files:
Quick Wins (custom rules):
BotProfile with emoji severity indicators (🔴 🟡 🔵)Instruction Lint (AI-powered):
OPENAI_API_KEY and/or ANTHROPIC_API_KEY in .env (depending on the bot's configured model)When a conversation includes orchestrator-driven tool invocations (MCP servers, connectors, child agents, etc.), Agent Analyser traces every call from trigger to finish and presents runtime analysis in the Tools tab.
Supported tool types: MCP Server, Connector Tool, Child Agent, Connected Agent, A2A Agent, Flow Tool, CUA Tool.
What it shows:
thought for each tool selectionbotContent.yml against tools actually invoked in dialog.jsonTool call data is captured from DynamicPlanStepTriggered, DynamicPlanStepBindUpdate, and DynamicPlanStepFinished events in the conversation trace. Works with both full bot exports (ZIP) and transcript-only uploads.
Two layers of rule evaluation, both surfacing in the analysis report's Quick Wins section with emoji severity indicators (🔴 fail, 🟡 warning, 🔵 info):
BotProfile. No configuration. Catch issues that are easy to miss when reading raw bot config.CUSTOM_RULES_FILE at your own file.Every bot analysis automatically evaluates these checks. They run before the YAML rules and use the same severity legend.
| Check | Severity | What it catches |
|---|---|---|
| Disabled topics | warning | Topics with state ≠ Active — enable or remove to reduce clutter |
| No trigger queries | warning | User topics without trigger phrases — recognizer can never match them |
| Weak descriptions | info | Topics, tools, agents, etc. with missing, too-short, or display-name-matching descriptions |
| Missing system topics | warning | Missing OnError, OnUnknownIntent, or OnEscalate handlers |
| Unused global variables | info | Global variables whose schema name isn't referenced by other components (heuristic) |
| Check | Severity | What it catches |
|---|---|---|
| Missing connector definition | warning | Connection reference points to a connector ID with no matching definition |
| Duplicate connection reference | warning | Same logical name appears more than once |
| Orphaned connector definition | info | Connector defined but not referenced by any connection reference |
| Unused connection reference | info | Connection reference defined but not used by any component |
Agent Analyser ships with 18 best-practice rules across 4 categories. They're defined in data/default_rules.yaml and evaluated after the built-in heuristic checks.
| ID | Category | Severity | What it checks |
|---|---|---|---|
| BP001 | Architecture | warning | No conversation starters defined |
| BP002 | Architecture | warning | No system instructions configured |
| BP003 | Architecture | warning | No explicit model hint configured |
| BP004 | Architecture | warning | Authentication mode is Unknown |
| BP005 | Architecture | info | No GPT description set |
| BP017 | Architecture | warning | Instructions lack constraint/boundary language |
| BP018 | Architecture | info | No escalation or handoff guidance in instructions |
| BP006 | Security | fail | Content moderation is Unknown |
| BP007 | Security | fail | Sensitive properties logged to Application Insights |
| BP008 | Security | warning | Access control policy is Unknown |
| BP009 | Security | warning | Instructions don't mention data handling or privacy |
| BP011 | Knowledge | info | Code interpreter is enabled |
| BP012 | Knowledge | info | Web browsing is enabled |
| BP010 | Operations | info | Automatic model updates enabled |
| BP013 | Operations | warning | No Application Insights configured |
| BP014 | Operations | warning | Activity logging disabled in Application Insights |
| BP015 | Operations | warning | No deployment channels configured |
| BP016 | Operations | info | No knowledge sources configured |
rules:
- rule_id: BP001
severity: warning # fail | warning | info
category: Architecture # free-text grouping
message: "No conversation starters defined"
condition:
field: "gpt_info.conversation_starters"
operator: eq # eq | not_exists | not_contains
value: []
Field paths reference BotProfile attributes using dot notation. Use [] for array fields (e.g. channels, knowledge_sources).
Supported operators (custom_rules.py:_apply_operator):
exists — field is not Nonenot_exists — field is None or missingeq — field equals the given valuene — field is not equal to the given valuecontains — string field contains substring, or list contains elementnot_contains — inverse of containsmatches — string field matches a regex (capped at 500 chars; nested-quantifier patterns rejected for safety)gt / gte / lt / lte — numeric comparisons (returns False on type mismatch)Set CUSTOM_RULES_FILE in .env to point to your own rules file. Falls back to data/default_rules.yaml if unset.
CUSTOM_RULES_FILE=data/default_rules.yaml
/rules) — view, edit, and manage rules in the web UIEach generated report contains:
The dynamic analysis view adds interactive versions of these sections across 6 tabs, plus conversation analysis features: turn efficiency, response quality scoring, dead code detection, knowledge source effectiveness, multi-agent delegation tracing, latency bottleneck analysis, and instruction-to-behavior alignment checking.
Transcript reports contain:
Every dynamic-page surface is reflected in the exports — what you see on screen is what lands in the file you download.
| Format | Trigger | Content |
|---|---|---|
Markdown (.md) | Download → Markdown | Canonical text export. Drives every other format. |
HTML (.html) | Download → HTML | Self-contained HTML built from the markdown via build_standalone_html. Embedded Mermaid diagrams. |
| PDF (Print) | Download → Print to PDF | Browser print of the HTML view. |
Audit bundle (.md) | Download Audit (Quality tab) | Audit-runner output on its own — every selected mode's result, model attribution, error per mode. |
The markdown report includes: TL;DR, Quick Wins, AI configuration, security, bot metadata, sequence + Gantt diagrams, conversation flow with AUTO/MANUAL annotations, Performance Waterfall, Variable Tracker, orchestrator reasoning, decision timeline, plan evolution, topic lifecycles, topic + tool inventory (split by tool_type), Component Settings Explained (per-component action tree), integration map, model comparison, knowledge inventory + coverage + source details + search results, Citation Verification table, trigger phrase analysis, MCS credit estimate, Failure Diagnosis when applicable.
Agent Analyser connects to Dataverse to fetch bot configuration, components, and conversation transcripts. Authentication uses OAuth 2.0 device code flow against the Dataverse Web API.
Before connecting, make sure the following are in place:
1. Licensing
The user signing in needs a license that includes Dataverse access:
Any of these grants access to the Dataverse environment where your bot lives.
2. Conversation transcripts
Transcripts must be enabled explicitly — they're off by default.
Important details:
3. Dataverse security role
The signed-in user needs Read access to three tables:
| Table | Schema name | Used for |
|---|---|---|
| Bot | bot | Resolving bot identity and configuration |
| Bot Component | botcomponent | Fetching topics, skills, entities, connectors |
| Conversation Transcript | conversationtranscript | Fetching conversation activity logs |
Built-in roles that have this access:
For least-privilege access, ask your admin to assign the Bot Transcript Viewer role (created by Copilot Studio), or create a custom security role with Read on those three tables.
4. Session details
You need three values from Copilot Studio:
Agent Analyser can auto-fill these — just paste the full Session details block into the text area on the Import page.
Agent Analyser supports two authentication modes. Try the default first.
Option 1: Default (no app registration)
By default, Agent Analyser uses the Microsoft Azure CLI client ID (04b07795-8ddb-461a-bbee-02f9e1bf7b46). This is a well-known first-party Microsoft application that works across all tenants without any setup.
No app registration, no admin involvement. Works for most tenants.
Option 2: Custom app registration (if default is blocked)
Some tenants block third-party client IDs via Conditional Access policies. If the default flow fails with AADSTS65002 or a similar auth error, register your own app:
user_impersonation → AddEnter this client ID in the Client ID field on the Import page instead of the default.
What you do NOT need:
| Symptom | Cause | Fix |
|---|---|---|
AADSTS65002 during auth | Public client flows not enabled, or client ID blocked by Conditional Access | Enable public client flows on the app registration, or register your own app (Option 2) |
| 403 after connecting | Missing Read permission on one or more Dataverse tables | Ask admin to assign System Administrator, Bot Transcript Viewer, or a custom role with Read on bot, botcomponent, conversationtranscript |
| Empty transcript list | Transcripts not enabled, or conversations too recent | Enable transcripts in Copilot Studio and wait ~30 minutes after a conversation completes |
| Device code expired | The code is valid for ~15 minutes | Retry the connection — click Connect again to get a fresh code |
| Consent prompt on sign-in | Admin hasn't pre-consented user_impersonation | Ask your tenant admin to grant admin consent, or consent yourself if allowed |
For admins reviewing network access or firewall rules:
https://<your-env>.crm.dynamics.com/api/data/v9.2/https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/devicecodehttps://<your-env>.crm.dynamics.com/.defaultbots, botcomponents, conversationtranscriptsThe two LLM-driven capabilities live on the Quality tab. Both are opt-in, both run in your tenant against your own API keys, and both append their output to the exported reports.
The Quality tab carries an audit runner that puts OPENAI_API_KEY or ANTHROPIC_API_KEY to work over your bot config and conversation transcript. Every audit mode is opt-in except the legacy default; clicking Instruction Lint with no other interaction reproduces the original behaviour.
| Mode | Default | Inputs | What it answers |
|---|---|---|---|
| Static Config | ✅ on | bot profile | Are the system instructions clear? Guardrails, knowledge config, topic architecture, component health. |
| Conversation Summary | ⬜ opt-in | transcript | 3-bullet recap + a single actionable insight. |
| User Sentiment | ⬜ opt-in | transcript | Per-turn sentiment, escalation signals, final-state risk score. |
| PII Detection | ⬜ opt-in | transcript | Categorised findings table + per-finding source + risk + recommendations. |
| Answer Accuracy | ⬜ opt-in | transcript | Per user-question verdict (Answered / Partial / Avoided / Wrong) with evidence. |
| Topic Routing Quality | ⬜ opt-in | profile + transcript | Did the orchestrator pick the right topic? Lists missed-better-fit cases. |
| Custom prompt | ⬜ opt-in | available | Free-form prompt — useful one-off audits. |
Modes shipped in data/default_lint_modes.yaml — extend or override by editing the file. Selected modes run in parallel; per-audit failures are isolated (one mode crashing doesn't take out the others). Transcript-only modes auto-disable when no dialog.json is uploaded.
The audit results are appended to the markdown report (so .md / .html / PDF downloads include them) and downloadable on their own as a separate audit bundle.
A native re-implementation of the AgentRx pattern from Microsoft Research, tailored to Copilot Studio. When a transcript looks broken, click Diagnose failure on the Quality tab and you get back: the first unrecoverable failure step, one of 10 root-cause categories with confidence, optional secondary findings (other failure-shaped events the judge spotted), canned MCS-specific recommendations, and a streaming chat panel to interrogate the verdict. Heuristics run for free; the LLM judge runs only when you ask.
| Mode | Behaviour |
|---|---|
| Online (default) | Heuristic constraint engine + LLM judge in one pass. Categorises into 1 of 10. Verdict + reasoning + secondary findings. |
| Offline (no LLM) | Heuristic engine only. Picks a category seed from the highest-severity rule. Free, deterministic. |
| Redact PII | Regex pass scrubs emails, phones, IBAN, BSN, credit-card numbers, names before any LLM call. |
| Ask the judge | Per-verdict streaming chat panel — questions like "Where did you see Contoso invented?" get token-by-token answers grounded in the same payload the judge originally saw. Persists across page reloads via LocalStorage. |
flowchart TD
A[Conversation Timeline + Bot Profile] --> B[6 heuristic constraints]
B --> C{Any critical violation?}
C -- No --> D[Succeeded — audit-trail violations only]
C -- Yes --> E[Critical step localization<br/>first unrecovered violation]
E --> F{LLM judge enabled?}
F -- No --> G[Heuristic verdict + canned recs]
F -- Yes --> H[LLM judge call]
H --> I[Verdict: category + confidence + secondary failures]
I --> J[Canned recs + AI recs option]
J --> K[Ask the judge — streaming chat]
classDef input fill:#dbeafe,stroke:#1e40af,color:#1e3a8a
classDef process fill:#fef3c7,stroke:#a16207,color:#713f12
classDef decision fill:#ede9fe,stroke:#6d28d9,color:#4c1d95
classDef success fill:#d1fae5,stroke:#047857,color:#064e3b
classDef llm fill:#cffafe,stroke:#0e7490,color:#155e75
class A input
class B,E,J process
class C,F decision
class D,G success
class H,I,K llm
flowchart LR
subgraph Agent["🔴 Agent-side"]
A1[Plan Adherence Failure]
A2[Invention of New Information]
A3[Invalid Invocation]
A4[Misinterpretation of Tool Output]
end
subgraph UAGap["🟠 User-Agent gap"]
B1[Intent-Plan Misalignment]
B2[Underspecified User Intent]
B3[Intent Not Supported]
end
subgraph Env["⚫ Environmental"]
C1[Guardrails Triggered]
C2[System Failure]
end
subgraph Unclassified["⚪ Unclassified"]
D1[Inconclusive]
end
classDef agent fill:#fee2e2,stroke:#b91c1c,color:#7f1d1d
classDef gap fill:#ffedd5,stroke:#c2410c,color:#7c2d12
classDef env fill:#e5e7eb,stroke:#374151,color:#111827
classDef unc fill:#f3f4f6,stroke:#9ca3af,color:#374151
class A1,A2,A3,A4 agent
class B1,B2,B3 gap
class C1,C2 env
class D1 unc
The taxonomy is lifted verbatim from the AgentRx repo. The headline always shows ONE primary category (the critical step's verdict) — that's faithful to the paper's design — but the violation log and the LLM judge's secondary_failures surface every other failure-shaped event spotted in the trajectory. AgentRx's own benchmark notes ~68% of failed trajectories contain two or more failures, which matches what you'll see in real Copilot Studio traces.
Inconclusive is reserved for "evidence is insufficient or contradictory; you cannot pick one of 1..9 with at least medium confidence" — the judge is allowed to decline rather than guess.
flowchart LR
R1[knowledge_zero_results_with_citation] -->|seeds| C1[Invention]
R2[fallback_when_match_plausible] -->|seeds| C2[Plan Adherence]
R3[slot_loop_no_progress] -->|seeds| C3[Underspecified Intent]
R4[tool_error_ignored] -->|seeds| C4[Tool Misinterpretation]
R5[ungrounded_generative_answer] -->|seeds| C5[Invention or Guardrails]
R6[automatic_retry_after_misinterpretation] -->|seeds| C4
R1 -. watches .-> E1[KnowledgeSearch + BOT_MESSAGE]
R2 -. watches .-> E2[STEP_TRIGGERED + USER_MESSAGE]
R3 -. watches .-> E3[STEP_TRIGGERED + VARIABLE_ASSIGNMENT]
R4 -. watches .-> E4[ToolCall.state == failed + BOT_MESSAGE]
R5 -. watches .-> E5[GenerativeAnswerTrace]
R6 -. watches .-> E5
classDef rule fill:#fef3c7,stroke:#a16207,color:#713f12
classDef category fill:#fee2e2,stroke:#b91c1c,color:#7f1d1d
classDef event fill:#dbeafe,stroke:#1e40af,color:#1e3a8a
class R1,R2,R3,R4,R5,R6 rule
class C1,C2,C3,C4,C5 category
class E1,E2,E3,E4,E5 event
knowledge_zero_results_with_citation — knowledge search returned 0 hits, the bot's next reply quotes a URL or [1]-style citation. Almost always invented.fallback_when_match_plausible — orchestrator routed to a *Fallback topic but a non-fallback topic had ≥ 0.6 trigger-phrase similarity to the user's query. Trigger-phrase tuning fix.slot_loop_no_progress — same slot-question topic re-triggered ≥ 3 times without an intervening VariableAssignment. The user can't or won't supply the requested value.tool_error_ignored — ToolCall.state == "failed" but the bot's next reply doesn't acknowledge the error or quote it. Strong tool-misinterpretation signal.ungrounded_generative_answer — triggered_fallback=True (GPT-default fallback) or Answered state with zero citations under a grounded-only config.automatic_retry_after_misinterpretation — first generative-answer attempt returned Not Found / Wrong despite ≥ 1 search hit; orchestrator retried automatically. Useful even when the retry recovers — flaky knowledge sources surface this way.The list is extensible — diagnosis/constraints/ is one file per rule. Add a new rule by dropping a new module and registering it in __init__.py.
flowchart TD
S[Walk violations in position order] --> V{Severity == warn or critical?}
V -- No --> N[Skip]
V -- Yes --> L[Look ahead in timeline]
L --> R1{Later 'Answered' generative answer?}
R1 -- Yes --> R[Recovered → continue scan]
R1 -- No --> R2{Later successful tool call<br/>for same task_dialog_id?}
R2 -- Yes --> R
R2 -- No --> CR[Critical step found]
CR --> END[Return this violation as critical]
N --> S
R --> S
classDef process fill:#fef3c7,stroke:#a16207,color:#713f12
classDef decision fill:#ede9fe,stroke:#6d28d9,color:#4c1d95
classDef recovered fill:#d1fae5,stroke:#047857,color:#064e3b
classDef critical fill:#fee2e2,stroke:#b91c1c,color:#7f1d1d
class S,L process
class V,R1,R2 decision
class N,R recovered
class CR,END critical
Lifted verbatim from AgentRx's root-cause detection algorithm. The "first unrecoverable failure" rule is what makes the diagnostic robust: a transient retry that recovers (e.g. bot2's failed-then-succeeded generative answer) doesn't get flagged as critical even though it's recorded in the violation log for the audit trail. Only failures that persist to the end of the trajectory become the headline verdict.
When a transcript contains an Approvals / Request-for-Information action (anything matching humanintheloop / request_for_information in the task_dialog_id), the Conversation Flow renders a single rich amber card in place of the two opaque "Step start / Step end" rows. The card shows:
This was added because the most consequential exchange in many Copilot Studio transcripts — a human supplying missing details over Teams / email — used to be invisible in the analyser, leading to false-positive "invented data" verdicts from the judge.
This integration is a native re-implementation of the pattern from microsoft/AgentRx. The 10-category taxonomy, the recovery algorithm, and the "first unrecoverable failure" framing are lifted verbatim with credit. We do not import AgentRx as a runtime dependency — its Azure-AD-only auth model and tau-bench / Magentic-One trajectory shapes don't fit Copilot Studio without a non-trivial adapter.
Inconclusive is the 10th)Recommendation: Self-host this in your own tenant or run it locally. Bot configuration data and conversation transcripts are sensitive — keep them under your control.
Reflex 0.9.x serves the frontend and backend on a single port. The repo ships a Dockerfile, Procfile, and nixpacks.toml, so most platforms work out of the box.
| Variable | Default | Description |
|---|---|---|
REFLEX_ENV | dev | dev = separate ports, prod = single-port mode |
FRONTEND_PORT | 3000 | Frontend port (dev mode only) |
BACKEND_PORT | 8000 | Backend port (dev mode only) |
REFLEX_HOT_RELOAD_EXCLUDE_PATHS | data | Exclude paths from hot-reload watcher (prevents bot_profile wipe) |
PORT | 2009 | Single port for prod mode (--single-port) |
LOG_LEVEL | INFO | Logging verbosity (DEBUG, INFO, WARNING, ERROR) |
USERS | (none) | Web UI credentials, comma-separated user:pass pairs |
OPENAI_API_KEY | (none) | OpenAI API key for Instruction Lint + AgentRX judge (required for OpenAI-model bots) |
ANTHROPIC_API_KEY | (none) | Anthropic API key for Instruction Lint (required for Anthropic-model bots) |
PYTHONUTF8 | 1 | Forces UTF-8 encoding for all Python I/O (prevents charmap errors on Windows and in Docker) |
CUSTOM_RULES_FILE | data/default_rules.yaml | Path to YAML file with custom rules for analysis |
This repo is built to deploy cleanly on Coolify.
Create a new application in Coolify pointing at this Git repo.
Build pack: choose Dockerfile (recommended for Reflex apps — nixpacks.toml is also present if you prefer Nixpacks).
Port: 2009 (matches the PORT env var). Coolify maps this internal port to your public domain.
Healthcheck path: /_health (Reflex's built-in liveness endpoint, returns HTTP 200). The Dockerfile already declares an internal HEALTHCHECK against the same path; Coolify can use that directly or probe via HTTP.
Environment variables (paste into Coolify's environment editor):
REFLEX_ENV=prod
PORT=2009
USERS=admin:choose-a-strong-password # comma-separated user:pass list
LOG_LEVEL=INFO
OPENAI_API_KEY=sk-... # optional, enables Lint + AgentRX for OpenAI-model bots
ANTHROPIC_API_KEY=sk-ant-... # optional, enables Lint for Anthropic-model bots
CUSTOM_RULES_FILE=data/default_rules.yaml # optional, override path to rules YAML
Persistent volume (recommended): mount /app/data so that
data/bot_profile.json (last-uploaded profile, used when reopening reports)data/instruction_versions.json (drift snapshots between deploys)survive redeploys. Gotcha: data/default_rules.yaml ships in the image; if you mount over /app/data with an empty volume on first deploy, the default rules will disappear. Either copy default_rules.yaml into the volume after the first deploy, or set CUSTOM_RULES_FILE to a path outside /app/data.
Deploy. First deploy can take a few minutes (Reflex compiles the frontend during reflex export in the Dockerfile build).
Same env vars as above. The Procfile runs reflex run --env prod. Set REFLEX_ENV=prod so rxconfig.py collapses frontend and backend onto the single PORT.
docker build -t agent-analyser .
docker run -p 2009:2009 --env-file .env agent-analyser
curl -fsS http://localhost:2009/_health # 200 OK with JSON liveness payload
Make sure .env contains at least REFLEX_ENV=prod and PORT=2009.
cp .env.example .env # edit credentials
uv sync
uv run pytest # 200+ tests
uv run ruff check .
uv run ruff format .
uv run reflex run # dev server — frontend :3000, backend :8000
main.py CLI entry point (Typer)
models.py Pydantic models (BotProfile, ConversationTimeline, GptInfo, TopicConnection)
parser.py YAML + JSON parsing, GPT extraction, topic connection extraction
timeline.py Dialog activity → timeline event conversion
transcript.py Transcript JSON parsing and normalization
conversation_analysis.py Turn efficiency, dead code, plan diffs, knowledge effectiveness, response quality, delegation, latency, instruction alignment
dataverse_client.py Dataverse Web API client (bot config, components, transcripts)
analytics.py Multi-transcript aggregation used by the Dataverse batch analytics view
custom_rules.py YAML rule loader and evaluator
instruction_store.py Instruction storage utilities
linter.py Instruction lint logic (OpenAI + Anthropic, model resolution, audit prompt)
utils.py Shared utilities
rxconfig.py Reflex app config
diagnosis/ AgentRX-style failure diagnosis
models.py FailureCategory enum (10), DiagnosisReport, ConstraintViolation, SecondaryFailure, Recommendation
constraints/ One file per heuristic rule + registry
recovery.py "First unrecoverable failure" critical-step localization
judge.py LLM judge (OpenAI streaming) + JSON parser
chat.py Streaming chat-with-judge module
redaction.py PII redaction (regex stage, optional LLM stage)
recommendations.py Canned recommendation loader
labels.py Shared category labels + colour-group badges
orchestrator.py Public diagnose() / diagnose_async() entry points
renderer/ Markdown + Mermaid rendering
_helpers.py Shared rendering helpers
conversation_analysis.py Renderers for conversation analysis features (markdown output)
diagnosis.py Failure Diagnosis markdown rendering
knowledge.py Knowledge source rendering
profile.py Bot profile rendering
report.py Main report assembly
sections.py Routing tab builders + Conversation Flow + HITL exchange card
timeline_render.py Timeline / conversation trace rendering
tools.py Tool call analysis rendering
prompts/ LLM prompt templates
judge.md AgentRX failure-diagnosis judge prompt
judge_chat.md AgentRX chat-with-judge persona prompt
web/
web.py Page definitions and Reflex app setup
mermaid.py Mermaid diagram rendering (CDN loader, MutationObserver, segment splitter)
state/ Reflex state management
_auth.py Authentication state
_base.py Base / shared state
_counter.py Analysis counter state
_dataverse.py Dataverse connection state
_diagnosis.py AgentRX diagnosis + chat state
_lint.py Instruction lint state
_report.py Report generation state
_rules.py Custom rules state
_dynamic.py Dynamic analysis state (6 tabs)
_upload.py File upload state + conversation analysis population
components/ UI components
common.py Shared components (navbar, dashboard cards, login)
dataverse.py Dataverse import form
diagnosis_tab.py AgentRX failure-diagnosis card + chat panel
report.py Report viewer
rules.py Rules editor
dynamic_analysis.py Dynamic analysis panels (6 tabs)
upload.py Upload form
data/
default_rules.yaml 18 default best-practice rules (custom_rules YAML)
default_lint_modes.yaml LLM Audit Runner mode definitions (system prompts + input declarations)
recommendations.yaml AgentRX canned recommendations keyed by FailureCategory
topic_explainer.yaml Curated KB feeding the Component Explorer's hover-card explanations + per-component settings tree
best_practices/ GPT model best-practice reference docs
samples/ Sample reports
tests/ Test suite (200+ tests)
MIT License — see LICENSE for details.
This is an open-source tool. Use it, modify it, deploy it however you like. For CoE (Center of Excellence) teams, we recommend local deployment or a self-hosted Azure container within your own tenant to keep bot data under your control.
Python
100.0%
Copilot Studio botcomponent or conversation transcript analyser
Python
17
51 commits
updated Sep 7, 2026
Peek under the hood of your Copilot Studio agents. Upload a bot export, drop a conversation transcript, or connect straight to Dataverse — instantly see what your agent is actually doing under the hood: how the orchestrator routes decisions, which topics/tools/agents fire and why, where knowledge searches hit or miss, how long each step takes, and what falls through the cracks. Architecture reports, best-practice rules, trigger overlap detection, execution timelines, credit estimates, response quality scoring, and instruction compliance checking.
Everything you need to build with confidence and debug without guessing. If you're serious about Copilot Studio development, this belongs in your toolkit.

.zip export, or connect directly to your environment and auto-analyse on login| Feature | Description |
|---|---|
| Upload bot export | Drop a .zip, or botContent.yml + dialog.json — get a full architecture report with quick wins |
| Connect to Dataverse | Device-code auth to your environment, auto-analyses your bot the moment you connect |
| Routing analysis | Orchestrator decision timeline with routing scores, topic lifecycles with redirect tracking, trigger phrase similarity, plan evolution diffs with thrashing detection |
| Conversation transcripts | Upload or fetch transcripts from Dataverse — sequence diagrams, Gantt charts, event logs, per-turn efficiency and latency breakdown |
| Single conversation lookup | Fetch and analyse a specific conversation by ID directly from Dataverse |
| Response quality scoring | Groundedness assessment for every bot response — detects ungrounded answers, hallucination risk from zero-result searches, and silently swallowed tool errors |
| Instruction alignment | Checks if the bot's runtime behavior matches its system instructions — language compliance, escalation triggers, scope restrictions |
| Dead code detection | Cross-references bot components against runtime evidence to find topics, tools, and knowledge sources that are never used |
| Knowledge effectiveness | Per-source hit rate, contribution rate, and error tracking — identifies knowledge sources that never contribute to grounded answers |
| Multi-agent delegation | Traces orchestrator-to-agent delegation chains — detects dead agents, always-failing agents, and shows orchestrator reasoning per delegation |
| Latency bottlenecks | Per-turn time breakdown showing where time is spent (thinking, tools, knowledge, delivery) with bottleneck flagging |
| Plan evolution diffs | Structured diffs between consecutive orchestrator plans within a turn — detects thrashing, scope creep, and re-planning patterns |
| Batch analytics | Aggregate multiple Dataverse transcripts — success/failure/escalation rates, topic usage, error patterns, credit estimates |
| Custom rules | 18 default best-practice rules + user-defined YAML rules, evaluated during analysis |
| Tool call analysis | Runtime tool call tracing — per-tool statistics, async chain detection, orchestrator reasoning, Mermaid flow diagrams. Supports MCP servers, connectors, child/connected agents, A2A, flows, CUA |
| Component Explorer | Inline searchable picker over every topic and tool (User / System / Automation topics, MCP servers, connectors, flows, child / connected / A2A agents) with KB-sourced explanations per setting |
| LLM Audit Runner | Multi-mode audit (default + opt-in: conversation summary / sentiment / PII / answer accuracy / topic routing / custom prompts) — runs in parallel via OpenAI or Anthropic |
| Failure Diagnosis (AgentRX) | AgentRx-style root-cause diagnosis: 10-category taxonomy, 6 heuristic rules, LLM judge with confidence + secondary findings, streaming chat-with-judge, HITL exchange surfacing in the Conversation Flow |
| Exports | Markdown / HTML / Print → PDF / Audit-bundle downloads — every dynamic-page surface (Variable Tracker, Performance Waterfall, Citation Verification, etc.) is reflected in the exports |
| Dark / Light mode | Respects your OS preference, green accent theme throughout |
| Analysis counter | Tracks how many analyses you've run, with cat-themed gamification milestones |
Five ways to get data in, two channels of data, one parsing pipeline, optional LLM enrichment, four export formats. Nothing leaves your machine unless you explicitly click an LLM action — the dashed box below is the only network boundary.
flowchart TD
subgraph Inputs["📥 Input sources"]
direction TB
I1[Upload ZIP<br/>full Copilot Studio export]
I2[Upload loose files<br/>botContent.yml + dialog.json]
I3[Upload transcript JSON]
I4[Loose files + transcript<br/>combined upload]
I5[Live Dataverse fetch<br/>OAuth device code]
end
I1 --> BC
I2 --> BC
I4 --> BC
I4 --> TR
I3 --> TR
I5 --> BC
I5 --> TR
BC[Bot content channel<br/>static structure: topics, tools,<br/>knowledge, routing] --> PB[Parse YAML / JSON]
TR[Transcript channel<br/>runtime trace: events,<br/>tool calls, decisions] --> PT[Parse activity log<br/>→ timeline events]
PB --> M[Pydantic models<br/>BotProfile + ConversationTimeline]
PT --> M
M --> R[Renderer<br/>markdown + mermaid]
R --> T1[Profile / Tools /<br/>Knowledge / Routing tabs]
R --> T2[Conversation / Quality tabs]
T1 -.->|opt-in| LLM
T2 -.->|opt-in| LLM
subgraph LLM["🔒 Opt-in LLM enrichment — leaves your machine ONLY when you click Run / Diagnose"]
L1[LLM Audit Runner<br/>instruction lint]
L2[AgentRX judge<br/>10-category verdict]
L3[Ask the judge<br/>streaming chat]
L4[AI recs<br/>augment canned recs]
end
T1 --> Out
T2 --> Out
LLM --> Out
subgraph Out["📤 Outputs"]
O1[Markdown .md]
O2[HTML .html]
O3[PDF print]
O4[Audit bundle .md]
end
classDef input fill:#dbeafe,stroke:#1e40af,color:#1e3a8a
classDef channel fill:#ede9fe,stroke:#6d28d9,color:#4c1d95
classDef process fill:#fef3c7,stroke:#a16207,color:#713f12
classDef tabs fill:#d1fae5,stroke:#047857,color:#064e3b
classDef llm fill:#fecaca,stroke:#b91c1c,color:#7f1d1d
classDef output fill:#d1fae5,stroke:#047857,color:#064e3b
class I1,I2,I3,I4,I5 input
class BC,TR channel
class PB,PT,M,R process
class T1,T2 tabs
class L1,L2,L3,L4 llm
class O1,O2,O3,O4 output
style LLM fill:#fee2e2,stroke:#b91c1c,stroke-width:2px,color:#7f1d1d,stroke-dasharray:5 5
Two data channels, not one. Bot content is the static structure of your agent (topics, tools, knowledge sources, routing config). Transcript is a runtime trace of what actually happened in a conversation. Either alone gets you partial coverage:
A live Dataverse fetch always pulls both — it grabs bot + botcomponent (bot content) and conversationtranscript (transcripts) in one go.
Opt-in LLM enrichment. The dashed cyan box is the only place your data hits an external API. Parsing, heuristic rules, rendering, exports — all local. The LLM Audit Runner needs your own OpenAI / Anthropic key; the AgentRX judge + chat reuse it. Off by default. When you do turn them on, PII redaction (emails, phones, IBAN, BSN, credit-card numbers, names) is on by default for the AgentRX judge so transcripts get scrubbed before they leave the box.
git clone https://github.com/Roelzz/mcs-agent-analyser.git
cd mcs-agent-analyser
cp .env.example .env # default login: inspector / underthehood
uv sync
uv run reflex run
git clone https://github.com/Roelzz/mcs-agent-analyser.git
cd mcs-agent-analyser
Copy-Item .env.example .env # default login: inspector / underthehood
uv sync
uv run reflex run
git clone https://github.com/Roelzz/mcs-agent-analyser.git
cd mcs-agent-analyser
copy .env.example .env REM default login: inspector / underthehood
uv sync
uv run reflex run
Open http://localhost:3000, sign in with inspector / underthehood, and upload a .zip bot export or connect to Dataverse.
Privacy note: Deploy this locally or self-host in your own Azure tenant. Bot exports and Dataverse data never leave your machine. External API calls are only made when you opt into the LLM Audit Runner (uses OpenAI or Anthropic).
uv run python main.py path/to/botContent --all
Scans all subfolders containing botContent.yml + dialog.json and writes a report.md into each. If a Transcripts/ subfolder exists, every .json transcript gets a matching .md report.
Single folder:
uv run python main.py path/to/botContent_folder
uv run python main.py path/to/botContent_folder -o custom_report.md
The dynamic analysis page presents bot and conversation data across 6 purpose-driven tabs:
| Tab | Icon | What it answers |
|---|---|---|
| Profile | user-round | What is this bot? Architecture, AI config, model, security, metadata, custom findings |
| Tools | wrench | What can it do, and did it work? Component Explorer (topics + tools + agents), inventory, runtime stats, agent delegation, topic graph |
| Knowledge | database | Is the knowledge useful? Sources, search results, source effectiveness, citation verification |
| Routing | route | How did orchestration work? Decision timeline, plan evolution diffs, topic lifecycles, trigger analysis, topic coverage |
| Conversation | message-square | What happened? Visual dashboard, chat replay, sequence/Gantt diagrams, performance waterfall, variable tracker, turn efficiency, latency bottlenecks |
| Quality | shield-check | How can I improve? LLM Audit Runner, AgentRX failure diagnosis, credits estimate, quick wins, response quality, dead code, instruction alignment |
When uploading a transcript without a bot export, a reduced tab bar shows: Conversation, Tools, Routing, Quality.

tool_type

Deep-linking: every entity-naming visualization on this tab is clickable. Variable Tracker cards, Waterfall rows, Phase Breakdown rows, Reasoning rows, and Conversation Flow rows jump to the canonical destination (Tools tab Component Explorer for tools / topics / agents; Knowledge tab for knowledge calls). An Expand all / Collapse all toolbar at the top toggles every accordion in one click.

From botContent.yml:
BeginDialog)From dialog.json:
From Dataverse (live connection):
From transcript .json files:
Quick Wins (custom rules):
BotProfile with emoji severity indicators (🔴 🟡 🔵)Instruction Lint (AI-powered):
OPENAI_API_KEY and/or ANTHROPIC_API_KEY in .env (depending on the bot's configured model)When a conversation includes orchestrator-driven tool invocations (MCP servers, connectors, child agents, etc.), Agent Analyser traces every call from trigger to finish and presents runtime analysis in the Tools tab.
Supported tool types: MCP Server, Connector Tool, Child Agent, Connected Agent, A2A Agent, Flow Tool, CUA Tool.
What it shows:
thought for each tool selectionbotContent.yml against tools actually invoked in dialog.jsonTool call data is captured from DynamicPlanStepTriggered, DynamicPlanStepBindUpdate, and DynamicPlanStepFinished events in the conversation trace. Works with both full bot exports (ZIP) and transcript-only uploads.
Two layers of rule evaluation, both surfacing in the analysis report's Quick Wins section with emoji severity indicators (🔴 fail, 🟡 warning, 🔵 info):
BotProfile. No configuration. Catch issues that are easy to miss when reading raw bot config.CUSTOM_RULES_FILE at your own file.Every bot analysis automatically evaluates these checks. They run before the YAML rules and use the same severity legend.
| Check | Severity | What it catches |
|---|---|---|
| Disabled topics | warning | Topics with state ≠ Active — enable or remove to reduce clutter |
| No trigger queries | warning | User topics without trigger phrases — recognizer can never match them |
| Weak descriptions | info | Topics, tools, agents, etc. with missing, too-short, or display-name-matching descriptions |
| Missing system topics | warning | Missing OnError, OnUnknownIntent, or OnEscalate handlers |
| Unused global variables | info | Global variables whose schema name isn't referenced by other components (heuristic) |
| Check | Severity | What it catches |
|---|---|---|
| Missing connector definition | warning | Connection reference points to a connector ID with no matching definition |
| Duplicate connection reference | warning | Same logical name appears more than once |
| Orphaned connector definition | info | Connector defined but not referenced by any connection reference |
| Unused connection reference | info | Connection reference defined but not used by any component |
Agent Analyser ships with 18 best-practice rules across 4 categories. They're defined in data/default_rules.yaml and evaluated after the built-in heuristic checks.
| ID | Category | Severity | What it checks |
|---|---|---|---|
| BP001 | Architecture | warning | No conversation starters defined |
| BP002 | Architecture | warning | No system instructions configured |
| BP003 | Architecture | warning | No explicit model hint configured |
| BP004 | Architecture | warning | Authentication mode is Unknown |
| BP005 | Architecture | info | No GPT description set |
| BP017 | Architecture | warning | Instructions lack constraint/boundary language |
| BP018 | Architecture | info | No escalation or handoff guidance in instructions |
| BP006 | Security | fail | Content moderation is Unknown |
| BP007 | Security | fail | Sensitive properties logged to Application Insights |
| BP008 | Security | warning | Access control policy is Unknown |
| BP009 | Security | warning | Instructions don't mention data handling or privacy |
| BP011 | Knowledge | info | Code interpreter is enabled |
| BP012 | Knowledge | info | Web browsing is enabled |
| BP010 | Operations | info | Automatic model updates enabled |
| BP013 | Operations | warning | No Application Insights configured |
| BP014 | Operations | warning | Activity logging disabled in Application Insights |
| BP015 | Operations | warning | No deployment channels configured |
| BP016 | Operations | info | No knowledge sources configured |
rules:
- rule_id: BP001
severity: warning # fail | warning | info
category: Architecture # free-text grouping
message: "No conversation starters defined"
condition:
field: "gpt_info.conversation_starters"
operator: eq # eq | not_exists | not_contains
value: []
Field paths reference BotProfile attributes using dot notation. Use [] for array fields (e.g. channels, knowledge_sources).
Supported operators (custom_rules.py:_apply_operator):
exists — field is not Nonenot_exists — field is None or missingeq — field equals the given valuene — field is not equal to the given valuecontains — string field contains substring, or list contains elementnot_contains — inverse of containsmatches — string field matches a regex (capped at 500 chars; nested-quantifier patterns rejected for safety)gt / gte / lt / lte — numeric comparisons (returns False on type mismatch)Set CUSTOM_RULES_FILE in .env to point to your own rules file. Falls back to data/default_rules.yaml if unset.
CUSTOM_RULES_FILE=data/default_rules.yaml
/rules) — view, edit, and manage rules in the web UIEach generated report contains:
The dynamic analysis view adds interactive versions of these sections across 6 tabs, plus conversation analysis features: turn efficiency, response quality scoring, dead code detection, knowledge source effectiveness, multi-agent delegation tracing, latency bottleneck analysis, and instruction-to-behavior alignment checking.
Transcript reports contain:
Every dynamic-page surface is reflected in the exports — what you see on screen is what lands in the file you download.
| Format | Trigger | Content |
|---|---|---|
Markdown (.md) | Download → Markdown | Canonical text export. Drives every other format. |
HTML (.html) | Download → HTML | Self-contained HTML built from the markdown via build_standalone_html. Embedded Mermaid diagrams. |
| PDF (Print) | Download → Print to PDF | Browser print of the HTML view. |
Audit bundle (.md) | Download Audit (Quality tab) | Audit-runner output on its own — every selected mode's result, model attribution, error per mode. |
The markdown report includes: TL;DR, Quick Wins, AI configuration, security, bot metadata, sequence + Gantt diagrams, conversation flow with AUTO/MANUAL annotations, Performance Waterfall, Variable Tracker, orchestrator reasoning, decision timeline, plan evolution, topic lifecycles, topic + tool inventory (split by tool_type), Component Settings Explained (per-component action tree), integration map, model comparison, knowledge inventory + coverage + source details + search results, Citation Verification table, trigger phrase analysis, MCS credit estimate, Failure Diagnosis when applicable.
Agent Analyser connects to Dataverse to fetch bot configuration, components, and conversation transcripts. Authentication uses OAuth 2.0 device code flow against the Dataverse Web API.
Before connecting, make sure the following are in place:
1. Licensing
The user signing in needs a license that includes Dataverse access:
Any of these grants access to the Dataverse environment where your bot lives.
2. Conversation transcripts
Transcripts must be enabled explicitly — they're off by default.
Important details:
3. Dataverse security role
The signed-in user needs Read access to three tables:
| Table | Schema name | Used for |
|---|---|---|
| Bot | bot | Resolving bot identity and configuration |
| Bot Component | botcomponent | Fetching topics, skills, entities, connectors |
| Conversation Transcript | conversationtranscript | Fetching conversation activity logs |
Built-in roles that have this access:
For least-privilege access, ask your admin to assign the Bot Transcript Viewer role (created by Copilot Studio), or create a custom security role with Read on those three tables.
4. Session details
You need three values from Copilot Studio:
Agent Analyser can auto-fill these — just paste the full Session details block into the text area on the Import page.
Agent Analyser supports two authentication modes. Try the default first.
Option 1: Default (no app registration)
By default, Agent Analyser uses the Microsoft Azure CLI client ID (04b07795-8ddb-461a-bbee-02f9e1bf7b46). This is a well-known first-party Microsoft application that works across all tenants without any setup.
No app registration, no admin involvement. Works for most tenants.
Option 2: Custom app registration (if default is blocked)
Some tenants block third-party client IDs via Conditional Access policies. If the default flow fails with AADSTS65002 or a similar auth error, register your own app:
user_impersonation → AddEnter this client ID in the Client ID field on the Import page instead of the default.
What you do NOT need:
| Symptom | Cause | Fix |
|---|---|---|
AADSTS65002 during auth | Public client flows not enabled, or client ID blocked by Conditional Access | Enable public client flows on the app registration, or register your own app (Option 2) |
| 403 after connecting | Missing Read permission on one or more Dataverse tables | Ask admin to assign System Administrator, Bot Transcript Viewer, or a custom role with Read on bot, botcomponent, conversationtranscript |
| Empty transcript list | Transcripts not enabled, or conversations too recent | Enable transcripts in Copilot Studio and wait ~30 minutes after a conversation completes |
| Device code expired | The code is valid for ~15 minutes | Retry the connection — click Connect again to get a fresh code |
| Consent prompt on sign-in | Admin hasn't pre-consented user_impersonation | Ask your tenant admin to grant admin consent, or consent yourself if allowed |
For admins reviewing network access or firewall rules:
https://<your-env>.crm.dynamics.com/api/data/v9.2/https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/devicecodehttps://<your-env>.crm.dynamics.com/.defaultbots, botcomponents, conversationtranscriptsThe two LLM-driven capabilities live on the Quality tab. Both are opt-in, both run in your tenant against your own API keys, and both append their output to the exported reports.
The Quality tab carries an audit runner that puts OPENAI_API_KEY or ANTHROPIC_API_KEY to work over your bot config and conversation transcript. Every audit mode is opt-in except the legacy default; clicking Instruction Lint with no other interaction reproduces the original behaviour.
| Mode | Default | Inputs | What it answers |
|---|---|---|---|
| Static Config | ✅ on | bot profile | Are the system instructions clear? Guardrails, knowledge config, topic architecture, component health. |
| Conversation Summary | ⬜ opt-in | transcript | 3-bullet recap + a single actionable insight. |
| User Sentiment | ⬜ opt-in | transcript | Per-turn sentiment, escalation signals, final-state risk score. |
| PII Detection | ⬜ opt-in | transcript | Categorised findings table + per-finding source + risk + recommendations. |
| Answer Accuracy | ⬜ opt-in | transcript | Per user-question verdict (Answered / Partial / Avoided / Wrong) with evidence. |
| Topic Routing Quality | ⬜ opt-in | profile + transcript | Did the orchestrator pick the right topic? Lists missed-better-fit cases. |
| Custom prompt | ⬜ opt-in | available | Free-form prompt — useful one-off audits. |
Modes shipped in data/default_lint_modes.yaml — extend or override by editing the file. Selected modes run in parallel; per-audit failures are isolated (one mode crashing doesn't take out the others). Transcript-only modes auto-disable when no dialog.json is uploaded.
The audit results are appended to the markdown report (so .md / .html / PDF downloads include them) and downloadable on their own as a separate audit bundle.
A native re-implementation of the AgentRx pattern from Microsoft Research, tailored to Copilot Studio. When a transcript looks broken, click Diagnose failure on the Quality tab and you get back: the first unrecoverable failure step, one of 10 root-cause categories with confidence, optional secondary findings (other failure-shaped events the judge spotted), canned MCS-specific recommendations, and a streaming chat panel to interrogate the verdict. Heuristics run for free; the LLM judge runs only when you ask.
| Mode | Behaviour |
|---|---|
| Online (default) | Heuristic constraint engine + LLM judge in one pass. Categorises into 1 of 10. Verdict + reasoning + secondary findings. |
| Offline (no LLM) | Heuristic engine only. Picks a category seed from the highest-severity rule. Free, deterministic. |
| Redact PII | Regex pass scrubs emails, phones, IBAN, BSN, credit-card numbers, names before any LLM call. |
| Ask the judge | Per-verdict streaming chat panel — questions like "Where did you see Contoso invented?" get token-by-token answers grounded in the same payload the judge originally saw. Persists across page reloads via LocalStorage. |
flowchart TD
A[Conversation Timeline + Bot Profile] --> B[6 heuristic constraints]
B --> C{Any critical violation?}
C -- No --> D[Succeeded — audit-trail violations only]
C -- Yes --> E[Critical step localization<br/>first unrecovered violation]
E --> F{LLM judge enabled?}
F -- No --> G[Heuristic verdict + canned recs]
F -- Yes --> H[LLM judge call]
H --> I[Verdict: category + confidence + secondary failures]
I --> J[Canned recs + AI recs option]
J --> K[Ask the judge — streaming chat]
classDef input fill:#dbeafe,stroke:#1e40af,color:#1e3a8a
classDef process fill:#fef3c7,stroke:#a16207,color:#713f12
classDef decision fill:#ede9fe,stroke:#6d28d9,color:#4c1d95
classDef success fill:#d1fae5,stroke:#047857,color:#064e3b
classDef llm fill:#cffafe,stroke:#0e7490,color:#155e75
class A input
class B,E,J process
class C,F decision
class D,G success
class H,I,K llm
flowchart LR
subgraph Agent["🔴 Agent-side"]
A1[Plan Adherence Failure]
A2[Invention of New Information]
A3[Invalid Invocation]
A4[Misinterpretation of Tool Output]
end
subgraph UAGap["🟠 User-Agent gap"]
B1[Intent-Plan Misalignment]
B2[Underspecified User Intent]
B3[Intent Not Supported]
end
subgraph Env["⚫ Environmental"]
C1[Guardrails Triggered]
C2[System Failure]
end
subgraph Unclassified["⚪ Unclassified"]
D1[Inconclusive]
end
classDef agent fill:#fee2e2,stroke:#b91c1c,color:#7f1d1d
classDef gap fill:#ffedd5,stroke:#c2410c,color:#7c2d12
classDef env fill:#e5e7eb,stroke:#374151,color:#111827
classDef unc fill:#f3f4f6,stroke:#9ca3af,color:#374151
class A1,A2,A3,A4 agent
class B1,B2,B3 gap
class C1,C2 env
class D1 unc
The taxonomy is lifted verbatim from the AgentRx repo. The headline always shows ONE primary category (the critical step's verdict) — that's faithful to the paper's design — but the violation log and the LLM judge's secondary_failures surface every other failure-shaped event spotted in the trajectory. AgentRx's own benchmark notes ~68% of failed trajectories contain two or more failures, which matches what you'll see in real Copilot Studio traces.
Inconclusive is reserved for "evidence is insufficient or contradictory; you cannot pick one of 1..9 with at least medium confidence" — the judge is allowed to decline rather than guess.
flowchart LR
R1[knowledge_zero_results_with_citation] -->|seeds| C1[Invention]
R2[fallback_when_match_plausible] -->|seeds| C2[Plan Adherence]
R3[slot_loop_no_progress] -->|seeds| C3[Underspecified Intent]
R4[tool_error_ignored] -->|seeds| C4[Tool Misinterpretation]
R5[ungrounded_generative_answer] -->|seeds| C5[Invention or Guardrails]
R6[automatic_retry_after_misinterpretation] -->|seeds| C4
R1 -. watches .-> E1[KnowledgeSearch + BOT_MESSAGE]
R2 -. watches .-> E2[STEP_TRIGGERED + USER_MESSAGE]
R3 -. watches .-> E3[STEP_TRIGGERED + VARIABLE_ASSIGNMENT]
R4 -. watches .-> E4[ToolCall.state == failed + BOT_MESSAGE]
R5 -. watches .-> E5[GenerativeAnswerTrace]
R6 -. watches .-> E5
classDef rule fill:#fef3c7,stroke:#a16207,color:#713f12
classDef category fill:#fee2e2,stroke:#b91c1c,color:#7f1d1d
classDef event fill:#dbeafe,stroke:#1e40af,color:#1e3a8a
class R1,R2,R3,R4,R5,R6 rule
class C1,C2,C3,C4,C5 category
class E1,E2,E3,E4,E5 event
knowledge_zero_results_with_citation — knowledge search returned 0 hits, the bot's next reply quotes a URL or [1]-style citation. Almost always invented.fallback_when_match_plausible — orchestrator routed to a *Fallback topic but a non-fallback topic had ≥ 0.6 trigger-phrase similarity to the user's query. Trigger-phrase tuning fix.slot_loop_no_progress — same slot-question topic re-triggered ≥ 3 times without an intervening VariableAssignment. The user can't or won't supply the requested value.tool_error_ignored — ToolCall.state == "failed" but the bot's next reply doesn't acknowledge the error or quote it. Strong tool-misinterpretation signal.ungrounded_generative_answer — triggered_fallback=True (GPT-default fallback) or Answered state with zero citations under a grounded-only config.automatic_retry_after_misinterpretation — first generative-answer attempt returned Not Found / Wrong despite ≥ 1 search hit; orchestrator retried automatically. Useful even when the retry recovers — flaky knowledge sources surface this way.The list is extensible — diagnosis/constraints/ is one file per rule. Add a new rule by dropping a new module and registering it in __init__.py.
flowchart TD
S[Walk violations in position order] --> V{Severity == warn or critical?}
V -- No --> N[Skip]
V -- Yes --> L[Look ahead in timeline]
L --> R1{Later 'Answered' generative answer?}
R1 -- Yes --> R[Recovered → continue scan]
R1 -- No --> R2{Later successful tool call<br/>for same task_dialog_id?}
R2 -- Yes --> R
R2 -- No --> CR[Critical step found]
CR --> END[Return this violation as critical]
N --> S
R --> S
classDef process fill:#fef3c7,stroke:#a16207,color:#713f12
classDef decision fill:#ede9fe,stroke:#6d28d9,color:#4c1d95
classDef recovered fill:#d1fae5,stroke:#047857,color:#064e3b
classDef critical fill:#fee2e2,stroke:#b91c1c,color:#7f1d1d
class S,L process
class V,R1,R2 decision
class N,R recovered
class CR,END critical
Lifted verbatim from AgentRx's root-cause detection algorithm. The "first unrecoverable failure" rule is what makes the diagnostic robust: a transient retry that recovers (e.g. bot2's failed-then-succeeded generative answer) doesn't get flagged as critical even though it's recorded in the violation log for the audit trail. Only failures that persist to the end of the trajectory become the headline verdict.
When a transcript contains an Approvals / Request-for-Information action (anything matching humanintheloop / request_for_information in the task_dialog_id), the Conversation Flow renders a single rich amber card in place of the two opaque "Step start / Step end" rows. The card shows:
This was added because the most consequential exchange in many Copilot Studio transcripts — a human supplying missing details over Teams / email — used to be invisible in the analyser, leading to false-positive "invented data" verdicts from the judge.
This integration is a native re-implementation of the pattern from microsoft/AgentRx. The 10-category taxonomy, the recovery algorithm, and the "first unrecoverable failure" framing are lifted verbatim with credit. We do not import AgentRx as a runtime dependency — its Azure-AD-only auth model and tau-bench / Magentic-One trajectory shapes don't fit Copilot Studio without a non-trivial adapter.
Inconclusive is the 10th)Recommendation: Self-host this in your own tenant or run it locally. Bot configuration data and conversation transcripts are sensitive — keep them under your control.
Reflex 0.9.x serves the frontend and backend on a single port. The repo ships a Dockerfile, Procfile, and nixpacks.toml, so most platforms work out of the box.
| Variable | Default | Description |
|---|---|---|
REFLEX_ENV | dev | dev = separate ports, prod = single-port mode |
FRONTEND_PORT | 3000 | Frontend port (dev mode only) |
BACKEND_PORT | 8000 | Backend port (dev mode only) |
REFLEX_HOT_RELOAD_EXCLUDE_PATHS | data | Exclude paths from hot-reload watcher (prevents bot_profile wipe) |
PORT | 2009 | Single port for prod mode (--single-port) |
LOG_LEVEL | INFO | Logging verbosity (DEBUG, INFO, WARNING, ERROR) |
USERS | (none) | Web UI credentials, comma-separated user:pass pairs |
OPENAI_API_KEY | (none) | OpenAI API key for Instruction Lint + AgentRX judge (required for OpenAI-model bots) |
ANTHROPIC_API_KEY | (none) | Anthropic API key for Instruction Lint (required for Anthropic-model bots) |
PYTHONUTF8 | 1 | Forces UTF-8 encoding for all Python I/O (prevents charmap errors on Windows and in Docker) |
CUSTOM_RULES_FILE | data/default_rules.yaml | Path to YAML file with custom rules for analysis |
This repo is built to deploy cleanly on Coolify.
Create a new application in Coolify pointing at this Git repo.
Build pack: choose Dockerfile (recommended for Reflex apps — nixpacks.toml is also present if you prefer Nixpacks).
Port: 2009 (matches the PORT env var). Coolify maps this internal port to your public domain.
Healthcheck path: /_health (Reflex's built-in liveness endpoint, returns HTTP 200). The Dockerfile already declares an internal HEALTHCHECK against the same path; Coolify can use that directly or probe via HTTP.
Environment variables (paste into Coolify's environment editor):
REFLEX_ENV=prod
PORT=2009
USERS=admin:choose-a-strong-password # comma-separated user:pass list
LOG_LEVEL=INFO
OPENAI_API_KEY=sk-... # optional, enables Lint + AgentRX for OpenAI-model bots
ANTHROPIC_API_KEY=sk-ant-... # optional, enables Lint for Anthropic-model bots
CUSTOM_RULES_FILE=data/default_rules.yaml # optional, override path to rules YAML
Persistent volume (recommended): mount /app/data so that
data/bot_profile.json (last-uploaded profile, used when reopening reports)data/instruction_versions.json (drift snapshots between deploys)survive redeploys. Gotcha: data/default_rules.yaml ships in the image; if you mount over /app/data with an empty volume on first deploy, the default rules will disappear. Either copy default_rules.yaml into the volume after the first deploy, or set CUSTOM_RULES_FILE to a path outside /app/data.
Deploy. First deploy can take a few minutes (Reflex compiles the frontend during reflex export in the Dockerfile build).
Same env vars as above. The Procfile runs reflex run --env prod. Set REFLEX_ENV=prod so rxconfig.py collapses frontend and backend onto the single PORT.
docker build -t agent-analyser .
docker run -p 2009:2009 --env-file .env agent-analyser
curl -fsS http://localhost:2009/_health # 200 OK with JSON liveness payload
Make sure .env contains at least REFLEX_ENV=prod and PORT=2009.
cp .env.example .env # edit credentials
uv sync
uv run pytest # 200+ tests
uv run ruff check .
uv run ruff format .
uv run reflex run # dev server — frontend :3000, backend :8000
main.py CLI entry point (Typer)
models.py Pydantic models (BotProfile, ConversationTimeline, GptInfo, TopicConnection)
parser.py YAML + JSON parsing, GPT extraction, topic connection extraction
timeline.py Dialog activity → timeline event conversion
transcript.py Transcript JSON parsing and normalization
conversation_analysis.py Turn efficiency, dead code, plan diffs, knowledge effectiveness, response quality, delegation, latency, instruction alignment
dataverse_client.py Dataverse Web API client (bot config, components, transcripts)
analytics.py Multi-transcript aggregation used by the Dataverse batch analytics view
custom_rules.py YAML rule loader and evaluator
instruction_store.py Instruction storage utilities
linter.py Instruction lint logic (OpenAI + Anthropic, model resolution, audit prompt)
utils.py Shared utilities
rxconfig.py Reflex app config
diagnosis/ AgentRX-style failure diagnosis
models.py FailureCategory enum (10), DiagnosisReport, ConstraintViolation, SecondaryFailure, Recommendation
constraints/ One file per heuristic rule + registry
recovery.py "First unrecoverable failure" critical-step localization
judge.py LLM judge (OpenAI streaming) + JSON parser
chat.py Streaming chat-with-judge module
redaction.py PII redaction (regex stage, optional LLM stage)
recommendations.py Canned recommendation loader
labels.py Shared category labels + colour-group badges
orchestrator.py Public diagnose() / diagnose_async() entry points
renderer/ Markdown + Mermaid rendering
_helpers.py Shared rendering helpers
conversation_analysis.py Renderers for conversation analysis features (markdown output)
diagnosis.py Failure Diagnosis markdown rendering
knowledge.py Knowledge source rendering
profile.py Bot profile rendering
report.py Main report assembly
sections.py Routing tab builders + Conversation Flow + HITL exchange card
timeline_render.py Timeline / conversation trace rendering
tools.py Tool call analysis rendering
prompts/ LLM prompt templates
judge.md AgentRX failure-diagnosis judge prompt
judge_chat.md AgentRX chat-with-judge persona prompt
web/
web.py Page definitions and Reflex app setup
mermaid.py Mermaid diagram rendering (CDN loader, MutationObserver, segment splitter)
state/ Reflex state management
_auth.py Authentication state
_base.py Base / shared state
_counter.py Analysis counter state
_dataverse.py Dataverse connection state
_diagnosis.py AgentRX diagnosis + chat state
_lint.py Instruction lint state
_report.py Report generation state
_rules.py Custom rules state
_dynamic.py Dynamic analysis state (6 tabs)
_upload.py File upload state + conversation analysis population
components/ UI components
common.py Shared components (navbar, dashboard cards, login)
dataverse.py Dataverse import form
diagnosis_tab.py AgentRX failure-diagnosis card + chat panel
report.py Report viewer
rules.py Rules editor
dynamic_analysis.py Dynamic analysis panels (6 tabs)
upload.py Upload form
data/
default_rules.yaml 18 default best-practice rules (custom_rules YAML)
default_lint_modes.yaml LLM Audit Runner mode definitions (system prompts + input declarations)
recommendations.yaml AgentRX canned recommendations keyed by FailureCategory
topic_explainer.yaml Curated KB feeding the Component Explorer's hover-card explanations + per-component settings tree
best_practices/ GPT model best-practice reference docs
samples/ Sample reports
tests/ Test suite (200+ tests)
MIT License — see LICENSE for details.
This is an open-source tool. Use it, modify it, deploy it however you like. For CoE (Center of Excellence) teams, we recommend local deployment or a self-hosted Azure container within your own tenant to keep bot data under your control.
Python
100.0%