This post is the final part in a series of posts about taking your AI coding agents to the next level.
In the previous posts of this series we’ve covered instructions files, skills files, MCP servers, and workflows. All of these pieces together work to make a single AI agent more effective. For the final post in the series we’re going to break that mold apart. Instead of one generalist agent trying to do everything, we’re now going to look at creating a team of specialists, each one of them focused on a single area, and coordinated toward a shared goal.
One well-equipped agent is like a skilled solo developer. A team of specialized agents is like a dev team that never sleeps.
Introduction
Up to this point we’ve looked at how we can improve the skills of a single AI coding agent to make it more useful and productive. We’ve improved the agent’s skills via a solid base instruction set, a playbook of rules, a set of tools to work from, and well established workflow patterns.
But that agent is really still just a generalist coder. And it suffers from a number of concerns. First, context window limits restrict just how much an agent by itself can do. Next, a single agent can have conflicting concerns. Lastly, as a generalist, it suffers from the same issue as all generalists - it knows a little about a lot of things, but lacks the depth a specialist has to address deeper issues.
The solution is to break complex work across multiple agents, each with a specific area of specialization. Together, they can stay narrowly focused, build deep domain knowledge, and address concerns with enough context for their domain. Putting those specialist agents to work as a team allows you to tackle bigger projects with more efficiency and success than a single generalist agent just can’t provide.
What Is Agent Specialization?
A specialist agent will have a tightly focused and scoped instructions file, a curated skills library, and only those MCP tools that are relevant to its role. For example, you might have an agent that specializes in testing processes, one that specializes in front-end development, one that specializes in back-end domain development, one that specializes in back-end API development, another that focuses on CI/CD pipelines and DevOps work, and one that specializes as an architect to plan out the overall design and coordinate the work of the other agents. You might also have one that specializes in code reviews and enforcement of programming conventions, helping to keep everyone else in line.
By specializing your team of agents, each has less noise attached to their context, making each more efficient and providing more consistent output from each in turn. Compare it to a senior .NET developer who focuses on the domain layer and back-end work. Such a specialist has deeper knowledge and makes fewer mistakes in their domain of expertise than a generalist who works across the full stack of development.
Building a Specialized Agent
Let’s walk through the creation of a domain specialist agent as an example. For our examples throughout this post we’re going to demonstrate how to do this for Claude agents. Each vendor is slightly different in their approach, so if you’re using a different AI coding agent you’ll need to refer to their documentation on how to perform the equivalent tasks for that agent.
We’ll start with the folder structure:
.claude/
agents/
domain-agent/
CLAUDE.md
skills/
create-aggregate/
SKILL.md
add-value-object/
SKILL.md
add-domain-event/
SKILL.md
Under the .claude folder we’ll create an agents sub-folder. For each specialist agent we’ll have a subfolder named for that agent. In this example, it’s domain-agent. In that folder we’ll add our CLAUDE.md file as our instructions file. We’ll also create a skills subfolder in that agent’s folder as a place to put the skills specific to that agent.
In the CLAUDE.md file, we’ll place instructions that tell the agent what its area of focus is. We start by providing a narrow focus or role right up front: “You are a domain modeling specialist. Your only concern is the creation of code for the domain layer of the application.”
After that we’ll add instructions, details on skills, MCP tools, and workflow processes that are specific to a domain specialist. We’ll also want to provide rules for things that are explicitly out of scope: “Do not write endpoints, migrations, unit tests, or UI/UX.”
The file would look something like this:
# Domain Agent Instructions
## Role
You are a domain modeling specialist. Your responsibility is the Domain layer only.
You design and implement aggregates, entities, value objects, and domain events
that accurately reflect the bounded context you are working in.
Do not write endpoints, application handlers, EF Core configuration, migrations,
or tests unless explicitly asked. If a task requires work outside the Domain layer,
complete your portion and note what the next agent needs to do.
## What You Own
- Aggregate roots and child entities
- Value objects
- Domain events
- Domain exceptions
- Domain service interfaces (not implementations)
- Enumerations and constants used within the domain
## What You Do Not Own
- EF Core `IEntityTypeConfiguration` classes
- MediatR handlers or commands
- FastEndpoints request/response classes
- Repository implementations
- Unit or integration tests
- Migrations
---
## Domain Modeling Conventions
### Aggregate Roots
- Implement `IAggregateRoot` from `Ardalis.SharedKernel`
- Protect invariants — no public setters on domain state
- Expose behavior through methods, not property assignment
- Raise domain events via `RegisterDomainEvent()`
- Use private constructors with a static `Create()` factory method
```csharp
public class Order : EntityBase, IAggregateRoot
{
public OrderId Id { get; private set; }
public CustomerId CustomerId { get; private set; }
public OrderStatus Status { get; private set; }
private readonly List<OrderLine> _lines = [];
public IReadOnlyCollection<OrderLine> Lines => _lines.AsReadOnly();
private Order() { }
public static Order Create(CustomerId customerId)
{
Guard.Against.Null(customerId);
var order = new Order
{
Id = new OrderId(Guid.NewGuid()),
CustomerId = customerId,
Status = OrderStatus.Draft
};
order.RegisterDomainEvent(new OrderCreatedEvent(order.Id));
return order;
}
}
```
### Value Objects
- Use sealed records
- All properties set at construction — no mutation
- Include validation in the constructor via `Ardalis.GuardClauses`
- Never use primitive types for identifiers — wrap them in a typed ID value object
```csharp
public sealed record OrderId(Guid Value)
{
public OrderId() : this(Guid.NewGuid()) { }
}
public sealed record Money(decimal Amount, string Currency)
{
public Money
{
Guard.Against.Negative(Amount);
Guard.Against.NullOrWhiteSpace(Currency);
}
public Money Add(Money other)
{
Guard.Against.InvalidInput(other, nameof(other),
m => m.Currency == Currency, "Currency mismatch");
return this with { Amount = Amount + other.Amount };
}
}
```
### Domain Events
- Suffix with `Event` (e.g., `OrderCreatedEvent`, `OrderShippedEvent`)
- Immutable records — no setters
- Carry only the data consumers need — no full entity references
```csharp
public sealed record OrderCreatedEvent(OrderId OrderId) : DomainEventBase;
```
### Domain Exceptions
- Suffix with `Exception`
- Extend `Exception` directly — not application or infrastructure exceptions
- Use for true domain invariant violations only, not validation errors
```csharp
public class InvalidOrderTransitionException(OrderStatus from, OrderStatus to)
: Exception($"Cannot transition order from {from} to {to}.");
```
### Guard Clauses
- Always at the top of methods and constructors
- Use `Ardalis.GuardClauses` — do not write manual null checks
- One guard per concern — do not chain conditions into a single guard
---
## Naming Conventions
- Aggregates and entities: nouns in the domain's ubiquitous language
- Value objects: descriptive of what they represent, not how they're stored
- Domain events: past tense — something that happened (`OrderPlaced`, not `PlaceOrder`)
- Typed IDs: `{Entity}Id` (e.g., `OrderId`, `CustomerId`)
- Factory methods: `Create()` for new instances, `Reconstitute()` for rehydration from persistence
---
## Skills
Before creating a new aggregate, read `.claude/agents/domain-agent/skills/create-aggregate/SKILL.md`.
Before adding a value object, read `.claude/agents/domain-agent/skills/add-value-object/SKILL.md`.
Before adding a domain event, read `.claude/agents/domain-agent/skills/add-domain-event/SKILL.md`.
---
## Available Tools
- Use the GitHub MCP tool to create branches if starting new domain work from scratch.
- Do not use the database MCP tool — you have no knowledge of persistence concerns.
- Do not use the Azure DevOps MCP tool unless fetching a work item to understand
domain requirements. Do not update work item status.
---
## Build & Verify
- Run `dotnet build` when your work is complete.
- Do not run `dotnet test` — the Test Agent owns that step.
- Flag any compile errors and fix them before handing off.
---
## Handoff
When your work is complete, summarize:
1. What was created or changed
2. Any domain decisions made and why
3. What the next agent needs to do (e.g., "API Agent needs to add an endpoint
for the new CreateOrder use case")
Do not begin work outside the Domain layer. Stop and hand off.
Note that the “What You Do Not Own” section is as important as the other conventions. Instructions like this keep the agent from drifting into other areas of concern and producing half-finished work across the codebase. Code examples are also useful for keeping an agent aligned to best practices, especially if you’re working in a specific coding language.
The “Handoff” section is also critical. It’s the glue that ties agents together, providing necessary transition between the specialists and the agent at the center that is orchestrating their work. Without that, the orchestrator has to waste time and resources trying to figure out what the specialist has done, or even worse, guessing.
Orchestrating the Team
So just what is an orchestrator? With each team, one agent acts as the orchestrator, the one who plans, delegates, and coordinates the work of the other specialists. The orchestrator is responsible for taking the request from the user and determining which specialist agents to invoke. It launches those agents and provides them with direction on what piece to work on. And then it waits for them to provide their reports of completed work, taking those handoff summaries and determining what feedback to provide to the user.
The orchestrator doesn’t need to know how each of the agents does their job. Its only concern is to determine how to break apart the request into the assignments and then how to validate the end result to ensure the request has been successfully completed. This mirrors the way human development teams work, where a lead will break down tasks, make assignments, and then evaluate the results for the client or business owner.
The CLAUDE.md file for an orchestrator will look different than the ones for the specialist agents. Their file will be much heavier on workflow and process, including details on how to delegate and to whom, as well as how to evaluate results overall, and will be very light on implementation detail.
Example Orchestrator CLAUDE.md File
# Orchestrator Agent Instructions
## Role
You are the orchestrator. You plan, delegate, and validate — you do not implement.
When given a goal, your job is to break it into tasks, assign each task to the
right specialist agent, validate the results, and assemble the final output.
Do not write domain models, endpoints, tests, or migrations yourself. If you find
yourself writing implementation code, stop and delegate to the appropriate agent.
---
## Available Agents
### Domain Agent
**Invoke for:** aggregates, entities, value objects, domain events
**Instructions:** `.claude/agents/domain-agent/CLAUDE.md`
**Handoff:** provide the module name, aggregate name, and requirements from the PBI
**Expects back:** a summary of what was created and what downstream agents need to do
### API Agent
**Invoke for:** FastEndpoints request/response classes, endpoint wiring, validation
**Instructions:** `.claude/agents/api-agent/CLAUDE.md`
**Handoff:** provide the use case name, expected request/response shape, and HTTP verb
**Expects back:** endpoint files created, route confirmed, Result mapping complete
### Test Agent
**Invoke for:** unit tests, integration tests, test data builders
**Instructions:** `.claude/agents/test-agent/CLAUDE.md`
**Handoff:** provide the handler or endpoint being tested and the acceptance criteria
**Expects back:** tests written and passing
### DevOps Agent
**Invoke for:** EF Core migrations, CI/CD pipeline changes, infrastructure updates
**Instructions:** `.claude/agents/devops-agent/CLAUDE.md`
**Handoff:** provide the entity changes that require a migration
**Expects back:** migration generated, reviewed, and build passing
### Review Agent
**Invoke for:** final review before a PR is opened
**Instructions:** `.claude/agents/review-agent/CLAUDE.md`
**Handoff:** provide the diff or list of changed files
**Expects back:** list of violations found (if any) and confirmation that output is clean
---
## Delegation Rules
- Always delegate in this order: Domain → API → Test → DevOps → Review
- Do not invoke the API Agent until the Domain Agent has handed off
- Do not invoke the Test Agent until both the Domain and API Agents have handed off
- Do not invoke the Review Agent until `dotnet build` and `dotnet test` both pass
- If any agent reports a failure or flags a blocker, stop and report to the user
before continuing
---
## Available Tools
### Azure DevOps
- Use to fetch work item details at the start of every feature workflow
- Do not update work item status
### GitHub
- Use to create the feature branch before any agent begins work
- Use to open the draft PR after the Review Agent signs off
- Branch naming: `feature/<short-description>`
- Always target `main` unless told otherwise
---
## Workflows
### Feature Implementation
When given a PBI or task number:
1. Fetch the work item from Azure DevOps and read the full description
and acceptance criteria.
2. Summarize your understanding of the task and confirm with the user
before delegating any work.
3. Create a feature branch via GitHub MCP.
4. Decompose the work into tasks and identify which agents are needed.
5. Delegate to each agent in order, passing the relevant context and
waiting for the handoff summary before proceeding.
6. After all agents have completed their work, run `dotnet build`
and `dotnet test`. If either fails, route the failure back to the
responsible agent for a fix.
7. Invoke the Review Agent with the full list of changed files.
8. If the Review Agent reports violations, route them to the responsible
agent for correction and re-review.
9. Open a draft PR via GitHub MCP with:
- PBI number and title
- Summary of what was implemented
- Which agents contributed and what each one did
- Any decisions or assumptions made
10. Stop and report to the user. Do not begin the next task automatically.
---
## Validation Checklist
Before opening a PR, confirm:
- [ ] `dotnet build` passes with no warnings
- [ ] `dotnet test` passes with no failures
- [ ] Review Agent has signed off
- [ ] No agent reported an unresolved blocker
- [ ] PR description references the PBI number
Note a few specific details of the orchestrator file. First, the delegation order rule provides the guidance for ensuring work occurs in the correct sequence so that coordination between the specialist agents is correct and no agent attempts to do their work without the correct needed input from the previous agent. Also note the last step, to “stop and report to the user”. This ensures that the user gets proper feedback after each task. Lastly, note there are virtually no implementation details. The file is almost entirely about process, making it drastically different from the domain agent example above.
End-to-End Example
So how would this process work in implementation? We would start with a single prompt directed to the orchestrator:
Implement PBI 4822
The orchestrator workflow would then proceed:
- Fetches the details of the work item from the work system (Jira, DevOps, GitHub)
- Breaks up the work item into tasks by specialist area of work: domain, API, front-end, tests, database, etc
- Delegates the domain work items to the domain agent, waiting for completion report and details
- Delegates the other work items to each subsequent agent in turn, waiting for their feedback, and providing needed details from the previous agent if needed
- Once all agents have completed their work, performs the validation checklist to ensure all tasks completed successfully, including getting signoff from the review agent and creating the PR
- Reports back final results to the user
Practical Considerations
There are a few things to keep in mind to ensure your agent teams are implemented effectively.
- Agent boundaries need to be explicit – If workers have overlapping areas of responsibility there will be conflicts that result from scope overlap. Ensure that all agents have clearly defined handoff points.
- Output format matters – The orchestrators work best when the workers return structured, predictable outputs that don’t require the orchestrator to use creative interpretation.
- Start with the minimum number of agents – Start with the smallest number of agents needed to do the work, maybe a domain agent and test agent. Determine the value that this organization provides. Don’t over-engineer at the start. If you need to break it down further later on, that’s fine.
- Shared conventions still matter – A shared root
CLAUDE.mdfile with project-wide rules is still a critical piece of the design. Don’t duplicate instruction across multiple agent files. Start with the core, unified ruleset and then extend it by providing instructions specific to each agent in their own file. - Use the available tooling support – Claude Code provides support for spawning subagents natively. Other systems like Cursor and Copilot require a more manual orchestration approach currently. There are also a wide variety of third-party and community tools available for setting up and running orchestration, such as Brady Gaster’s Squad for Copilot.
When Agent Teams Are Worth It (and When They’re Not)
Teams can be useful, but there are also times when they aren’t worth the extra effort involved in setting them up and using them.
| Scenario | Solo Agent | Agent Team |
|---|---|---|
| Small feature, single module | ✅ | Overkill |
| Large feature spanning modules | Struggles | ✅ |
| Greenfield module scaffold | ✅ | Overkill |
| Full feature + tests + migration + PR | Inconsistent | ✅ |
| Bug fix | ✅ | Overkill |
| Codebase-wide refactor | ❌ | ✅ |
Conclusion
AI coding agents can be powerful tools, properly guided and directed. Throughout this series we’ve looked at various ways to make AI useful and usable. We’ve looked at instructions files, skills files, MCP tools, workflows, and now agent specialization and teams. It’s taken you on a journey from “I use AI to autocomplete code” to “I orchestrate a team of AI specialists to develop complex applications.” With that guidance, AI coding agents can be effective pair programmers and partners, instead of being tools of chaos that you spend countless hours fixing.

