One long chat with one AI assistant works for small tasks. It starts to break down when the work has stages: understand the idea, plan it, build it, test it. Context gets muddy, and the assistant happily skips steps.
The fix is orchestration. Give each stage to a focused agent, make each agent write a file, and let the next agent read that file. This is the core idea behind BMAD (Breakthrough Method for Agile AI Driven Development): specialized roles, written handoffs, and a repeatable flow.
In this tutorial you will build a condensed version of that idea in Visual Studio Code:
- Four Claude Code subagents: Analyst, PM, Developer, Tester
- Slash commands to run each stage
- A small C# orchestrator that runs the whole flow and checks every handoff
By the end you can open one folder in VS Code, press a task, and watch an idea become a brief, a PRD, working code, and a test report.
Note. This is a lightweight take on BMAD concepts, not the official BMAD Method package. It keeps the parts that matter most: roles, artifacts, and gates.
What you will build
Idea -> Analyst -> PM -> Developer -> Tester -> Verdict brief.md prd.md code + notes test report
Each arrow is a handoff: one agent writes a file, the next reads it.
| Step | Agent | Reads | Writes |
|---|---|---|---|
| 1 | analyst | samples/idea.md | artifacts/01-brief.md |
| 2 | pm | artifacts/01-brief.md | artifacts/02-prd.md |
| 3 | developer | artifacts/02-prd.md | workspace/ and 03-build-notes.md |
| 4 | tester | PRD and code | artifacts/04-test-report.md |
Prerequisites
| Tool | Version | Check |
|---|---|---|
| Visual Studio Code | current | code --version |
| .NET SDK | 8.0 or newer | dotnet --version |
| Claude Code CLI | current | claude --version |
| Git | any | git --version |
You also need a Claude account that can use Claude Code, and you must be signed in (claude once in a terminal will guide you).
Install these VS Code extensions:
- C# Dev Kit (
ms-dotnettools.csdevkit) - C# (
ms-dotnettools.csharp) - Claude Code for VS Code (
anthropic.claude-code)
Checkpoint: all four version commands print a version number.
Step 1: Create the project structure
Create this layout (or use the ready made project that comes with this article):
bmad-lite/ CLAUDE.md BmadLite.sln .claude/ agents/ analyst.md pm.md developer.md tester.md commands/ brief.md prd.md build.md test.md settings.json .vscode/ tasks.json launch.json extensions.json src/BmadLite.Orchestrator/ the C# orchestrator samples/idea.md artifacts/ agent outputs land here workspace/ generated code lands here
Open the folder with File > Open Folder in VS Code.
Checkpoint: the Explorer shows the .claude and src folders.
Step 2: Write the shared rules in CLAUDE.md
CLAUDE.md is loaded by Claude Code at the start of every session. Use it for rules that every agent must follow.
BMAD Lite Project Rules Rules for all agents Stay in your role. Do not do the job of another agent. Write your output artifact to artifacts/ using the exact file name. End every artifact with a ## Handoff section (open questions, next agent). Number requirements as FR-1, FR-2 so the tester can trace them. Target stack for generated code is C# on .NET 8, under workspace/. If input is missing or unclear, say so in ## Handoff and stop.
Rule 3 matters most. The orchestrator will later refuse to continue if an artifact has no Handoff section.
Checkpoint: CLAUDE.md exists in the project root.
Step 3: Define the four agents as subagents
Claude Code subagents are markdown files in .claude/agents/. Each has a small header (name, description, allowed tools) and a system prompt.
.claude/agents/analyst.md
---name: analystdescription: Business analyst. Turns a raw idea into a short project brief. Use first.tools: Read, Write, Glob, GrepYou are the Analyst in a condensed BMAD workflow.Input: the idea file you are given (default samples/idea.md).Output: artifacts/01-brief.md.Sections: Problem, Target users, Goals, Scope (in and out),Assumptions and risks, Handoff (next agent is pm).Rules: Do not propose a technical design. Keep the brief under 60 lines.
.claude/agents/pm.md
---name: pmdescription: Product manager. Converts the brief into a PRD with numbered, testable requirements.tools: Read, Write, Glob, GrepYou are the Product Manager in a condensed BMAD workflow.Input: artifacts/01-brief.md. Output: artifacts/02-prd.md.Sections: Summary, Functional requirements (FR-1...), Non functionalrequirements (NFR-1...), Acceptance criteria (Given, When, Then),Out of scope, Handoff (next agent is developer).Rules: Every requirement must be testable without asking you. At most 8 functional requirements.
.claude/agents/developer.md
---name: developerdescription: Developer. Implements the PRD as a small C# on .NET 8 solution under workspace/.tools: Read, Write, Edit, Glob, Grep, BashYou are the Developer in a condensed BMAD workflow.Input: artifacts/02-prd.md.Output: code under workspace/ and artifacts/03-build-notes.md.Steps: implement each FR, reference the FR id in a short comment,run dotnet build and fix errors, then write build notes with anFR coverage table and a Handoff section (next agent is tester).Rules: Never claim an FR is done if the build fails.
.claude/agents/tester.md
---name: testerdescription: Tester. Verifies the code against the PRD and reports pass or fail per requirement.tools: Read, Write, Edit, Glob, Grep, BashYou are the Tester in a condensed BMAD workflow.Input: PRD, build notes, and code under workspace/.Output: artifacts/04-test-report.md.Steps: write or find automated tests for each FR and NFR, rundotnet test, then report a table (requirement, test, result, evidence).Rules: Do not fix production code. Report defects instead. Report only results you actually observed. End with exactly VERDICT: PASS or VERDICT: FAIL, then a Handoff section.
Two design choices make this reliable:
- Least privilege. The analyst and PM cannot run shell commands. Only the developer and tester can.
- Separation of duties. The tester cannot edit production code, so it cannot quietly “fix” a failure to get a pass.
Checkpoint: in a Claude Code session, run /agents. You should see all four agents listed.
Step 4: Add slash commands for each stage
Slash commands are markdown files in .claude/commands/. They let you run one stage on demand.
.claude/commands/brief.md
---description: Run the analyst agent to create the project briefargument-hint: path to idea fileUse the analyst subagent to read $ARGUMENTS (default samples/idea.md)and write artifacts/01-brief.md. Report only the file path when done.
Create prd.md, build.md, and test.md the same way, each pointing at its own agent and artifact.
Checkpoint: type / in the Claude Code panel. You should see /brief, /prd, /build, and /test.
Step 5: Set safe permissions
Agents write files and run commands, so limit what they can do in .claude/settings.json:
{ "permissions": { "allow": [ "Read", "Write(artifacts/**)", "Write(workspace/**)", "Edit(workspace/**)", "Bash(dotnet build:*)", "Bash(dotnet test:*)", "Bash(dotnet new:*)" ], "deny": [ "Read(.env)", "Bash(rm -rf:*)" ] }}
Agents can now write only to artifacts/ and workspace/, and run only .NET build, test, and new commands.
Step 6: Try the flow by hand
Before automating, prove the flow works manually. Write a small idea in samples/idea.md:
Idea: Tip SplitterA small .NET library and console app that splits a restaurant billamong friends. It should handle a tip percentage, uneven shares,and rounding so the total always matches.
Then, in the Claude Code panel, run the stages in order:
/brief samples/idea.md/prd/build/test
After each command, open the new file in artifacts/ and read it. This is the human review gate that BMAD encourages: you stay in control between stages.
Checkpoint: artifacts/ contains 01-brief.md through 04-test-report.md.
Step 7: Build the C# orchestrator
Manual runs are good for learning. For repeatable runs, add a small orchestrator that calls Claude Code in non interactive print mode (claude -p) and checks each handoff.
Create the project:
dotnet new sln -n BmadLitedotnet new console -n BmadLite.Orchestrator -o src/BmadLite.Orchestratordotnet sln add src/BmadLite.Orchestrator
The orchestrator has four small parts.
7.1 The workflow definition
AgentStep.cs declares each step as data: the agent, the prompt, the artifact it must produce, and the inputs it needs.
namespace BmadLite.Orchestrator;public sealed record AgentStep( string Agent, string Prompt, string OutputArtifact, string[] RequiredInputs);public static class Workflow{ public static IReadOnlyList<AgentStep> Build(string ideaPath) => [ new("analyst", $"Use the analyst subagent to read {ideaPath} and write artifacts/01-brief.md.", "artifacts/01-brief.md", [ideaPath]), new("pm", "Use the pm subagent to read artifacts/01-brief.md and write artifacts/02-prd.md.", "artifacts/02-prd.md", ["artifacts/01-brief.md"]), new("developer", "Use the developer subagent to read artifacts/02-prd.md, implement it under workspace/, and write artifacts/03-build-notes.md.", "artifacts/03-build-notes.md", ["artifacts/02-prd.md"]), new("tester", "Use the tester subagent to verify workspace/ against artifacts/02-prd.md and write artifacts/04-test-report.md.", "artifacts/04-test-report.md", ["artifacts/02-prd.md", "artifacts/03-build-notes.md"]), ];}
Because the workflow is plain data, adding a fifth agent (for example an Architect) means adding one record and one agent file.
7.2 The Claude runner
ClaudeRunner.cs starts the claude CLI. It uses ArgumentList so you never fight shell quoting, and it supports a dry run.
using System.Diagnostics;namespace BmadLite.Orchestrator;public sealed record RunResult(int ExitCode, string Output, string Error);public sealed class ClaudeRunner{ private readonly string _workingDirectory; private readonly bool _dryRun; public ClaudeRunner(string workingDirectory, bool dryRun) { _workingDirectory = workingDirectory; _dryRun = dryRun; } public async Task<RunResult> RunAsync(string prompt, CancellationToken ct = default) { if (_dryRun) return new RunResult(0, $"[dry run] claude -p \"{prompt}\"", string.Empty); var psi = new ProcessStartInfo { FileName = "claude", WorkingDirectory = _workingDirectory, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, }; psi.ArgumentList.Add("-p"); psi.ArgumentList.Add(prompt); psi.ArgumentList.Add("--permission-mode"); psi.ArgumentList.Add("acceptEdits"); try { using var process = Process.Start(psi) ?? throw new InvalidOperationException("Could not start claude."); var stdout = process.StandardOutput.ReadToEndAsync(ct); var stderr = process.StandardError.ReadToEndAsync(ct); await process.WaitForExitAsync(ct); return new RunResult(process.ExitCode, await stdout, await stderr); } catch (System.ComponentModel.Win32Exception) { return new RunResult(127, string.Empty, "The 'claude' command was not found. Install Claude Code and add it to PATH."); } }}
--permission-mode acceptEdits lets the agent write files without stopping to ask, while your settings.json still limits where it may write.
7.3 The handoff gate
HandoffGate.cs is the quality gate. It is the most important file in the orchestrator, because it turns “the agent said it finished” into “the artifact really exists and is well formed.”
namespace BmadLite.Orchestrator;public static class HandoffGate{ public static IReadOnlyList<string> CheckInputs(string root, AgentStep step) { var problems = new List<string>(); foreach (var input in step.RequiredInputs) { var path = Path.Combine(root, input); if (!File.Exists(path)) problems.Add($"Missing input: {input}"); else if (new FileInfo(path).Length == 0) problems.Add($"Empty input: {input}"); } return problems; } public static IReadOnlyList<string> CheckOutput(string root, AgentStep step) { var problems = new List<string>(); var path = Path.Combine(root, step.OutputArtifact); if (!File.Exists(path)) { problems.Add($"Missing output: {step.OutputArtifact}"); return problems; } var text = File.ReadAllText(path); if (!text.Contains("## Handoff", StringComparison.OrdinalIgnoreCase)) problems.Add($"{step.OutputArtifact} has no '## Handoff' section."); if (step.Agent == "tester" && !text.Contains("VERDICT: PASS") && !text.Contains("VERDICT: FAIL")) problems.Add("Test report has no VERDICT line."); return problems; } public static bool IsPass(string root, AgentStep step) { var path = Path.Combine(root, step.OutputArtifact); return File.Exists(path) && File.ReadAllText(path).Contains("VERDICT: PASS"); }}
7.4 The entry point
Program.cs parses two commands: run for the full flow and step for one agent. It finds the project root by walking up until it sees CLAUDE.md, so it works from any folder in the repo.
The core loop is short:
var steps = Workflow.Build(idea);for (var i = 0; i < steps.Count; i++){ var step = steps[i]; var inputProblems = HandoffGate.CheckInputs(root, step); if (inputProblems.Count > 0) return Fail(string.Join(Environment.NewLine, inputProblems)); var result = await runner.RunAsync(step.Prompt); if (result.ExitCode != 0) return Fail($"{step.Agent} failed. {result.Error}"); var outputProblems = HandoffGate.CheckOutput(root, step); if (outputProblems.Count > 0) return Fail(string.Join(Environment.NewLine, outputProblems)); Console.WriteLine($"[{i + 1}/{steps.Count}] {step.Agent} OK: {step.OutputArtifact}");}
The full file, with the step command, usage text, and the final verdict check, is in the project download.
Checkpoint: run dotnet build BmadLite.sln. It should finish with Build succeeded.
Step 8: Wire everything into VS Code
Add tasks so you never type long commands. In .vscode/tasks.json:
{ "version": "2.0.0", "tasks": [ { "label": "build orchestrator", "type": "process", "command": "dotnet", "args": ["build", "${workspaceFolder}/BmadLite.sln"], "group": { "kind": "build", "isDefault": true }, "problemMatcher": "$msCompile" }, { "label": "dry run workflow", "type": "process", "command": "dotnet", "args": ["run", "--project", "${workspaceFolder}/src/BmadLite.Orchestrator", "--", "run", "samples/idea.md", "--dry-run"], "options": { "cwd": "${workspaceFolder}" }, "problemMatcher": [] }, { "label": "run full workflow", "type": "process", "command": "dotnet", "args": ["run", "--project", "${workspaceFolder}/src/BmadLite.Orchestrator", "--", "run", "samples/idea.md"], "options": { "cwd": "${workspaceFolder}" }, "dependsOn": ["build orchestrator"], "problemMatcher": [] } ]}
For debugging, add .vscode/launch.json with a coreclr configuration that points at bin/Debug/net8.0/BmadLite.Orchestrator.dll and passes run samples/idea.md --dry-run. Now you can set breakpoints in the gate and step through a run with F5.
Checkpoint: press Ctrl+Shift+P, choose Tasks: Run Task, pick dry run workflow.
Step 9: Run it end to end
First, always do a dry run. It prints the exact command for each step and calls no AI:
dotnet run --project src/BmadLite.Orchestrator -- run samples/idea.md --dry-run
Expected output:
[1/4] analyst [dry run] claude -p "Use the analyst subagent to read samples/idea.md and write artifacts/01-brief.md."[2/4] pm [dry run] claude -p "Use the pm subagent to read artifacts/01-brief.md and write artifacts/02-prd.md."[3/4] developer [dry run] claude -p "Use the developer subagent to read artifacts/02-prd.md, ..."[4/4] tester [dry run] claude -p "Use the tester subagent to verify workspace/ against ..."Workflow finished.
Then run the real flow:
dotnet run --project src/BmadLite.Orchestrator -- run samples/idea.md
A real run can take several minutes because the developer and tester agents build and test code. When it ends, check:
artifacts/01-brief.mdto04-test-report.mdall exist.workspace/has a solution that builds withdotnet build.- The test report ends with
VERDICT: PASS(orFAILwith defects listed).
If the verdict is FAIL, the orchestrator exits with code 1, which makes it easy to use in a CI pipeline later.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
The 'claude' command was not found | CLI not on PATH | Reinstall Claude Code, restart VS Code, check claude --version |
Agent is not listed in /agents | Wrong folder or bad header | Files must be in .claude/agents/ and start with the --- header |
Missing output: artifacts/... | Agent wrote elsewhere or was blocked | Check settings.json permissions and the agent prompt path |
has no '## Handoff' section | Agent skipped the rule | Strengthen rule 3 in CLAUDE.md and re run that step |
| Tester always reports PASS | Tester did not run tests | Require dotnet test output as evidence in the report table |
| Build fails at restore on a locked down network | NuGet is blocked | Use an internal NuGet mirror or an offline package cache |
Best practices
- Review between stages the first few times. Automate only after you trust the artifacts.
- Keep artifacts short. Small files are cheaper to read and easier for the next agent to use.
- Keep the tester independent. Never let the same agent write and grade the code.
- Commit the artifacts to Git. They become a readable history of decisions.
- Version your agents. Treat
.claude/agents/*.mdlike code and review changes in pull requests.
Where to go next
- Add an Architect agent between PM and Developer for design decisions.
- Add a retry loop: if the verdict is FAIL, send the defects back to the developer once.
- Run the orchestrator in GitHub Actions or Azure Pipelines for repeatable builds.
- Move from the CLI to a code first framework such as Semantic Kernel or Microsoft Agent Framework if you need finer control over tools and state.
Summary
You built agent orchestration with three simple ideas: specialized roles (.claude/agents), written handoffs (artifacts/), and enforced gates (the C# orchestrator). Claude Code does the reasoning, and your C# code keeps the process honest. The same pattern scales from a tip splitter to a real product backlog. Ah you can download the sample codes here https://github.com/ridiferd/leanbmad
Leave a Reply