dotnet-testing-orchestrator 架構解析 — 1 個 Orchestrator + 4 個 Subagents

系列:從鐵人賽到 Agent Orchestration — AI 自動建立 .NET 測試的完整方案(4/10)

前言

上一篇介紹了 VS Code v1.109 帶來的技術突破 — Agent Skills GA、Custom Agent 的 Subagent 機制、Context 隔離。這些能力組合在一起,讓 Agent Orchestration 從概念變成了可以實作的架構。

有了技術基礎之後,接下來的問題是:架構到底該怎麼切?

一開始我嘗試過 3 個 Subagent(把 Executor 的工作併進 Writer),但很快發現 Writer 的 Context Window 會被 Skills 內容和編譯錯誤訊息同時擠壓。最後定案的是 1 Orchestrator + 4 Subagents — 這篇文章會拆解這個架構的設計決策與分工策略。


前置設定

在使用 Agent Orchestration 之前,需要確認 VS Code 的相關設定已正確配置。開啟設定檔(Ctrl+Shift+PPreferences: Open User Settings (JSON)),確認以下設定:

{
  // 允許 custom agents 作為 subagent 被調用(必要)
  "chat.customAgentInSubagent.enabled": true,

  // AI 推理深度(建議用 high 以獲得更好的推理品質)
  "github.copilot.chat.responsesApiReasoningEffort": "high",

  // Agent 檔案搜尋位置
  "chat.agentFilesLocations": {
    "${workspaceFolder}/.github/agents": true,
    "~/.vscode/agents": true
  }
}

其中 chat.customAgentInSubagent.enabled 是 Agent Orchestration 能運作的前提 — 沒有這個設定,Orchestrator 無法呼叫任何 Subagent。chat.agentFilesLocations 則決定 VS Code 從哪些路徑載入 .agent.md 檔案,.github/agents/ 是預設路徑,不設定也會自動偵測。

更完整的設定說明請參考上一篇文章的「啟用相關設定」段落。


架構總覽

整個 Unit Test Orchestrator 採用 1 Orchestrator + 4 Subagents 的架構,5 個 Agent 定義檔都放在 .github/agents/ 目錄下:

.github/agents/
├── dotnet-testing-orchestrator.agent.md      # 指揮中心
├── dotnet-testing-analyzer.agent.md          # 程式碼分析器
├── dotnet-testing-writer.agent.md            # 測試撰寫器
├── dotnet-testing-executor.agent.md          # 測試執行器
└── dotnet-testing-reviewer.agent.md          # 品質審查器

使用者只需要與 Orchestrator 互動,它會依序委派 4 個 Subagent 完成從分析到審查的完整流程:

每個 Subagent 都有獨立的 Context Window,這是整個架構能運作的關鍵 — Orchestrator 的主 Context 從約 39K tokens 降到約 12K tokens(減少 69%)。

每個 Subagent 都有獨立的 Context Window,這是整個架構能運作的關鍵 — Orchestrator 的主 Context 從約 39K tokens 降到約 12K tokens(減少 69%)。


Orchestrator — 指揮中心

Orchestrator 是整個架構的核心,但它的角色是「指揮」而不是「執行」。

核心職責

  • 接收使用者需求
  • 嚴格按序委派 4 個 Subagent(Analyzer → Writer → Executor → Reviewer)
  • 傳遞每個階段的輸出給下一個階段
  • 整合最終結果回報使用者

HARD STOP 規則

Orchestrator 的 .agent.md 定義了嚴格的限制,這些限制是架構正確運作的保證:

## ⛔ HARD STOP Constraints

The Orchestrator MUST NEVER:
- ❌ Read any SKILL.md file
- ❌ Write any C# test code
- ❌ Modify any .csproj file
- ❌ Skip any of the 4 stages

為什麼要「限制」Orchestrator 的能力?

老實說,第一版的 Orchestrator 我沒有加這些限制。結果 AI 很「熱心」地直接讀了 SKILL.md、跳過 Analyzer 就開始寫測試。測試品質當然不好 — 因為它根本不知道被測類別用了 TimeProvider。

所以 HARD STOP 規則是被「教訓」出來的。如果 Orchestrator 可以直接讀取 SKILL.md、直接寫程式碼,就會發生以下問題:

  1. Context Window 爆炸:29 個 Skills 的 SKILL.md 內容加起來非常龐大,全部載入會擠壓其他工作的空間
  2. 職責混亂:當指揮者同時也是執行者,就很難確保每個步驟都被正確完成
  3. 品質不可控:跳過 Analyzer 直接寫測試,就無法根據程式碼特徵選擇正確的技術

HARD STOP 規則強制 Orchestrator 只做「指揮」的事,確保每個 Subagent 在自己的 Context Window 中獨立運作。

Frontmatter 定義

---
name: dotnet-testing-orchestrator
description: '.NET 單元測試指揮中心 — 分析被測試目標、決定技術組合、委派 subagent 撰寫、執行與審查測試'
argument-hint: '描述要測試的類別/方法,例如「OrderProcessingService 的 ProcessOrder 方法」'
tools: ['agent', 'read', 'search', 'search/usages', 'search/listDirectory']
agents: ['dotnet-testing-analyzer', 'dotnet-testing-writer', 'dotnet-testing-executor', 'dotnet-testing-reviewer']
model: ['Claude Sonnet 4.6 (copilot)', 'Claude Opus 4.6 (copilot)']
---

agents: 欄位明確列出可呼叫的 4 個 Subagent,這是 VS Code Subagent 機制的白名單控制。tools: 中有 agent 工具,這是呼叫 Subagent 的關鍵。

注意 Orchestrator 的 tools 中沒有 editexecute/* — 因為它不需要修改檔案或執行命令,這些是 Writer 和 Executor 的職責。

自我檢查清單

HARD STOP 規則要怎麼落實?光寫「禁止」不夠,AI 在長對話中容易忘記限制。所以 Orchestrator 的 .agent.md 中內建了一份自我檢查清單,在每次行動前觸發:

### 自我檢查清單

在每次行動前,問自己:

- 我是否正在嘗試讀取 SKILL.md?→ **停止,這是 Writer 的工作**
- 我是否正在嘗試撰寫 C# 程式碼?→ **停止,委派給 Writer**
- 我是否正在嘗試執行 `dotnet build` 或 `dotnet test`?→ **停止,委派給 Executor**
- 我是否已經收到 Analyzer 的分析報告?→ 沒有的話,**先委派 Analyzer**

這個設計的靈感來自飛行員的 checklist — 不是信不過飛行員的能力,而是在高壓環境下,結構化的檢查比記憶更可靠。實測中,加入這份清單後,Orchestrator 越權行為從約 30% 降到接近 0%。

執行進度顯示規範

Agent Orchestration 的一個實際問題是:使用者不知道目前執行到哪裡了。4 個 Subagent 依序執行,整個流程可能需要幾分鐘,如果沒有任何進度提示,使用者只能盯著 spinner 等。

所以 Orchestrator 的 .agent.md 定義了必要的進度輸出:

動作時機必輸出文字
委派 Analyzer 階段 1:委派分析(Analyzer)
Analyzer 回傳後Analyzer 分析完成!識別出 N 個方法、Y 個依賴,需要 [技術清單]。現在委派 Writer 撰寫測試。
委派 Writer 階段 2:委派撰寫(Test Writer)
Writer 回傳後Writer 完成!已建立測試檔案,共 N 個測試案例。現在委派 Executor 建置與執行。
委派 Executor 階段 3:委派執行(Test Executor)
Executor 回傳後全數通過!N 個測試案例通過,修正 Y 次。現在委派 Reviewer 進行品質審查。
委派 Reviewer 階段 4:委派審查(Test Reviewer)

每個階段之間有明確的過渡摘要,使用者可以即時掌握目前正在執行哪個環節、上個環節的結果是什麼。


四個 Subagent 的角色定位

Orchestrator 依序委派 4 個 Subagent,每個都有明確的職責邊界和獨立的 Context Window。

Analyzer — 程式碼分析器

Phase 1 的執行者。負責深度分析被測類別的結構 — 建構子依賴、方法簽章、目標分類(Service / Validator / Legacy)。最重要的輸出是 requiredTechniques 列表,這份列表決定了 Writer 需要載入哪些 Skills。

Analyzer 不載入任何 Skills,靠 LLM 自身的程式碼理解能力完成分析。

Frontmatter 定義:

---
name: dotnet-testing-analyzer
description: '分析 .NET 被測試目標的類別結構、依賴項、需要的測試技術'
user-invokable: false
tools: ['read', 'search', 'search/usages', 'search/listDirectory']
model: Claude Sonnet 4.6 (copilot)
---

工具選擇的考量:Analyzer 只需要讀取和搜尋程式碼,不需要修改任何檔案,所以 tools 中沒有 editsearch/usages 讓它能追蹤介面的實作位置,search/listDirectory 用來掃描測試專案中既有的基礎設施。

Analyzer 有一個重要的分析流程 — 目標類型識別。讀取被測類別後,它會立即判斷目標屬於哪種類型:

  • Service(一般服務類別):走標準的建構子依賴 + 方法簽章分析
  • Validator(FluentValidation 驗證器):跳過方法簽章分析,改為掃描建構子中的 RuleForSetValidatorMust 等規則定義
  • Legacy(有靜態依賴的舊程式碼):掃描靜態方法呼叫、寫死的資料,標記不可 Mock 的依賴

這個分類直接影響後續 Writer 使用的測試策略 — Validator 類型使用 TestValidate() 模式,Legacy 類型使用 Characterization Test 模式。

Writer — 測試撰寫器

Phase 2 的執行者,也是整個架構中唯一會大量載入 Skills 的 Subagent。根據 Analyzer 產出的 requiredTechniques,使用 read 工具明確載入對應的 SKILL.md(一般 3~5 個),在獨立的 Context Window 中依照 12 條撰寫規範產出完整的測試程式碼。

這裡的關鍵是明確使用 read 工具讀取 SKILL.md,而不是等待 AI 自動觸發 — 這正是解決第二篇文章提到的觸發問題的核心設計。Writer 不會 build 或執行測試,這是 Executor 的職責。

Frontmatter 定義:

---
name: dotnet-testing-writer
description: '根據分析結果載入對應的 Agent Skills,撰寫符合最佳實踐的 .NET 單元測試'
user-invokable: false
tools: ['read', 'search', 'create', 'edit', 'execute/getTerminalOutput', 'execute/runInTerminal',
        'read/terminalLastCommand', 'read/terminalSelection']
model: ['Claude Sonnet 4.6 (copilot)', 'GPT-5.1-Codex-Max (copilot)']
---

Writer 的 tools 比 Analyzer 多了 createedit 和終端機相關工具。create 用來建立新的測試檔案,edit 用來修改既有檔案和 .csproj;終端機工具用來執行 dotnet list package --outdated 查詢套件可升級版本 — 這是為了確保 .csproj 中的套件版本不會停留在 SKILL.md 記載的最低版本上。

Writer 的 model 陣列中有兩個模型(Claude Sonnet 4.6 和 GPT-5.1-Codex-Max),第一個可用的會被採用。這個設計讓 Writer 在主模型不可用時有備援方案。

Writer 的核心工作流程包含六個步驟:

  1. 載入技術型 Skills — 根據 requiredTechniques 清單,用 read 工具讀取每份 SKILL.md
  2. 掃描既有測試 Pattern — 檢查測試專案是否有 AutoDataWithCustomization 等既有基礎設施,有就沿用
  3. 讀取被測試目標原始碼 — 包含被測類別、依賴介面、相關 Model/DTO
  4. 查詢可升級套件版本 — 執行 dotnet list package --outdated,作為版本決策的唯一權威來源
  5. 撰寫測試程式碼 — 依照 12 條撰寫規範產出完整測試(AAA Pattern、中文三段式命名、AwesomeAssertions 等)
  6. 回傳結果 — 測試程式碼、使用的 Skills 清單、新增的 NuGet 套件、測試檔案路徑

Executor — 測試執行器

Phase 3 的執行者。採用 Build-first 策略,先確認編譯通過再執行測試。如果遇到編譯錯誤或測試失敗,會進入修正迴圈(最多 3 輪)。

Executor 載入的 dotnet-test Skill 並非本專案自製,而是引用社群中 Oleksii Nikiforov 發佈的 dotnet-test Skill,不計入本專案的 29 個 Skills 數量中,但是 Executor 運作時的重要依賴。

Frontmatter 定義:

---
name: dotnet-testing-executor
description: '建置與執行 .NET 單元測試,處理編譯錯誤與測試失敗的修正迴圈'
user-invokable: false
tools: ['read', 'search', 'create', 'edit', 'execute/getTerminalOutput', 'execute/runInTerminal',
        'read/terminalLastCommand', 'read/terminalSelection', 'execute/createAndRunTask']
model: Claude Sonnet 4.6 (copilot)
---

Executor 的工具配置是四個 Subagent 中最完整的 — 它需要 createedit 來建立或修正測試程式碼,需要完整的終端機工具來執行 dotnet builddotnet test,還多了 execute/createAndRunTask 用來建立 VS Code Task 執行長時間建置。

修正迴圈的設計考量: 為什麼是 3 輪而不是 5 輪或無限重試?實測中發現,大多數問題(缺少 using、Mock 設定錯誤、型別不匹配)都能在 2 輪內解決。如果 3 輪後仍有失敗,通常代表 Writer 的測試邏輯本身有問題,繼續重試只會消耗 Context Window 而不會收斂。此時回報 Orchestrator「需要 Writer 介入」是更有效的策略。

Executor 的 .agent.md 中還內建了常見修正模式的對照表,例如 CS0246 通常是缺少 using 或 NuGet 套件、CS1061 通常是方法名稱或屬性名稱與被測目標不一致。這些 pattern 讓 Executor 能快速定位問題,而不是每次都從頭分析。

Reviewer — 品質審查器

Phase 4 的執行者,也是最終的品質把關者。與 Writer 的動態載入不同,Reviewer 採用固定 + 條件的 Skills 載入策略 — 固定載入 3 個品質型 Skills(命名規範、斷言指引、測試基礎),條件載入 0~3 個(視測試中使用的技術而定)。

Frontmatter 定義:

---
name: dotnet-testing-reviewer
description: '審查 .NET 單元測試的品質,載入品質相關 Skills 驗證命名、斷言、覆蓋率等最佳實踐'
user-invokable: false
tools: ['read', 'search', 'search/listDirectory', 'execute/getTerminalOutput',
        'execute/runInTerminal', 'read/terminalLastCommand', 'read/terminalSelection']
model: ['Claude Sonnet 4.6 (copilot)', 'Claude Opus 4.6 (copilot)']
---

注意 Reviewer 的 tools 中沒有 edit — 這是刻意的設計。Reviewer 只負責審查和產出品質報告,不會直接修改測試程式碼。如果發現問題,它會在報告中提出具體的修正建議,讓使用者或後續的迭代來處理。

Reviewer 對測試程式碼進行六個維度的品質審查:

審查維度來源 Skill檢查重點
命名品質test-naming-conventions中文三段式格式、情境與預期描述的具體性
斷言品質awesome-assertions-guide.Should() 語法、精確度、物件級別比較
測試結構unit-test-fundamentalsAAA Pattern、FIRST 原則、一測試一概念
程式碼品質(內建規則)未使用的 using、不必要的命名空間引入
Mock 品質nsubstitute-mocking是否只 Mock 介面、Received() 驗證是否合理
覆蓋完整性(內建規則)正常路徑、邊界條件、例外情境是否都有覆蓋

最終輸出結構化的品質報告,包含整體評分(A+ ~ D)、具體的 issues 清單(每個都有 severity、category、suggestion)、遺漏的測試案例建議、以及做得好的 positives。

每個 Subagent 的實戰展示,會在下一篇文章中用一個具體案例走過完整的四階段流程。


Skills 分工策略 — 每個 Subagent 載入什麼

整個架構的 Skills 分工是經過精心設計的。不是每個 Agent 都需要載入所有 Skills,而是根據職責分配:

Skills 分工策略

AgentSkills 載入策略說明
Orchestrator不載入任何 Skills只負責指揮和傳遞
Analyzer不載入任何 Skills靠自身能力分析程式碼
Writer技術型 Skills(動態,3~5 個)如:nsubstitute-mocking、autofixture-basics、datetime-testing-timeprovider
Executor執行型 Skill(固定,1 個)dotnet-test
Reviewer品質型 Skills(固定 3 + 條件 0~3 個)固定:naming、assertions、fundamentals;條件:mocking、comparison、coverage

Context Window 使用分析

這個分工策略帶來的 Context Window 效益:

AgentSkills 載入量預估 Context 使用
Orchestrator0 個約 12K tokens(指令 + 對話)
Analyzer0 個約 8K tokens(指令 + 分析結果)
Writer3–5 個約 15K–25K tokens(Skills + 程式碼)
Executor1 個約 5K tokens(Skill + 錯誤修正)
Reviewer3–6 個約 12K–20K tokens(Skills + 審查報告)

對比單一 Agent 載入 5 個以上 Skills 的約 39K tokens,每個 Subagent 都在合理的 Context 範圍內運作。

為什麼不讓 Orchestrator 和 Analyzer 載入 Skills?

這個問題我也思考過。直覺上覺得「多讀一些 Skill 不是更好嗎?」,但實測發現答案是否定的:

  • Orchestrator:它的職責是指揮,載入 Skills 只會浪費 Context Window。我試過讓 Orchestrator 載入 Skills,結果它開始「越權」自己寫測試,完全跳過了 Writer
  • Analyzer:它需要的是「理解程式碼結構」的能力,這是 LLM 本身就具備的。它需要知道的是「偵測到 TimeProvider 依賴就加入 datetime-testing-timeprovider 到 requiredTechniques」,而不是需要讀取整份 TimeProvider SKILL.md

多目標平行執行

當使用者要求為多個類別建立測試時,Orchestrator 支援平行執行模式:

  • Analyzer 階段:可平行分析多個目標類別
  • Writer 階段:可平行為多個目標撰寫測試
  • Executor 階段:必須循序執行(避免 Build 衝突)
  • Reviewer 階段:可平行審查多個測試檔案

階段間的資料傳遞

Agent Orchestration 能否正確運作,很大程度取決於 Orchestrator 傳給每個 Subagent 的 prompt 是否包含足夠的資訊。這裡的設計原則是:每個 Subagent 只收到它需要的資料,不多也不少。

Orchestrator → Analyzer

Orchestrator 傳給 Analyzer 的 prompt 相對簡單:

  • 被測試目標的檔案路徑或類別名稱
  • 測試專案的路徑(讓 Analyzer 能掃描既有基礎設施)
  • 使用者的特殊需求(如果有的話)

Analyzer → Orchestrator → Writer

這是最關鍵的傳遞環節。Analyzer 回傳結構化的 JSON 分析報告,Orchestrator 會將完整的分析報告轉傳給 Writer,同時附上額外的指示。Writer 收到的 prompt 必須包含:

  1. 完整的分析報告 JSON — 包含 requiredTechniquessuggestedTestScenariosexistingTestInfrastructure
  2. 被測試目標與依賴介面的檔案路徑 — 讓 Writer 能用 read 工具讀取原始碼
  3. 測試檔案的預期輸出路徑 — 依照現有專案結構推導
  4. 沿用既有基礎設施的提醒 — 如果 Analyzer 發現測試專案有 AutoDataWithCustomization 等基礎設施,明確告知 Writer 必須沿用

這裡有一個經驗教訓:第一版設計中,我只傳了 requiredTechniques 清單給 Writer,沒有傳完整的分析報告。結果 Writer 不知道被測類別有哪些方法、有幾個依賴,只能自己重新分析一遍 — 浪費了 Context Window,也可能與 Analyzer 的分析結果不一致。

Writer → Orchestrator → Executor

Writer 完成後回傳測試程式碼的檔案路徑和新增的 NuGet 套件資訊。Orchestrator 傳給 Executor 的 prompt 包含:

  • 測試專案路徑
  • 被測試目標的專案路徑(供修正錯誤時參考)
  • Writer 修改的 NuGet 套件資訊

Executor → Orchestrator → Reviewer

Executor 回傳 dotnet test 的執行結果。Orchestrator 傳給 Reviewer 的 prompt 包含:

  • 測試檔案路徑(Executor 已在原檔案上完成修正,Reviewer 直接讀取最終版本)
  • 被測試目標的檔案路徑(供比對覆蓋率)
  • Analyzer 的分析報告(讓 Reviewer 知道使用了哪些技術、是否有特殊依賴)
  • Executor 的執行結果(是否全數通過)

這裡值得注意的是:Reviewer 也需要收到 Analyzer 的分析報告。因為 Reviewer 要根據 dependencies 判斷是否需要額外載入 nsubstitute-mocking Skill 來審查 Mock 品質,也要根據 complexModelAnalysis 判斷是否應該使用 BeEquivalentTo() 做物件比對。


錯誤處理策略

Agent Orchestration 不是每次都能順利跑完四個階段。Orchestrator 的 .agent.md 定義了每個階段的錯誤處理策略:

Analyzer 失敗

最常見的原因是找不到被測試目標。處理方式:

  1. 向使用者確認檔案路徑是否正確
  2. Orchestrator 自己用 readsearch 工具嘗試找到目標檔案
  3. 重新委派 Analyzer

Executor 修正後仍有失敗

如果 Executor 經過 3 輪修正後仍有測試失敗:

  1. 將失敗訊息和 Executor 的分析一併傳給 Reviewer
  2. 在最終結果中明確標示哪些測試仍然失敗
  3. 提供修正方向建議(讓使用者決定是否手動修正或重新執行)

Reviewer 發現重大品質問題

如果 Reviewer 的 overallScore 為 C 或以下:

  1. 在結果中凸顯主要問題
  2. 建議使用者手動修正,或再次執行 Orchestrator 並附上改善方向

這些錯誤處理策略的共同原則是:不隱藏問題,讓使用者做最終決策。 Agent Orchestration 的定位是助手而不是黑箱。


小結

回頭看這個架構,核心想法很單純:讓每個 Agent 專注做一件事,在有限的 Context Window 中發揮最大效能。

5 個 Agent 定義檔共同構成了一個從分析、撰寫、執行到審查的完整流水線。HARD STOP 規則是被踩坑踩出來的、自我檢查清單是對抗 AI 越權的有效手段、Skills 分工策略是反覆調整 Context Window 使用量後定案的、資料傳遞協定是踩過「資訊不足」的坑後才完善的。

這篇聚焦在「為什麼這樣設計」— Orchestrator 的職責限制與進度顯示、4 個 Subagent 的角色定位與工具配置、Skills 的分工原則、階段間的資料傳遞協定、以及錯誤處理策略。下一篇會深入每個 Subagent 的運作機制,並用一個具體的案例走過完整的四階段流程。


參考資源

純粹是在寫興趣的,用寫程式、寫文章來抒解工作壓力