The Big Picture
Before you read any single file, build a mental map of where things live. This codebase follows Clean Architecture — a deliberate set of dependency rules that keeps business logic independent of frameworks. We'll cover what each layer owns, the rules that govern them, and the "where does X live?" lookup table you'll come back to often.
Clean Architecture in one paragraph
The codebase is divided into four concentric layers. Domain at the center knows nothing about frameworks, databases, or HTTP. Application wraps Domain with use-cases (commands, queries, interfaces) but still imports nothing concrete. Infrastructure provides concrete implementations of Application interfaces — Azure OpenAI clients, MCP servers, file system access. Presentation is the entry point — console UI, SignalR hub, REST API, MCP server — that wires everything together. Dependencies point only inward.
When the business rule "an agent must check the budget before calling a tool" lives in Domain, it survives any change to the LLM provider, the UI, or the database. You can swap Azure OpenAI for Claude or Console for SignalR without touching it. The opposite — business logic tangled into a Razor view or a SQL stored procedure — is the shape of code that becomes legacy.
The most important distinction: .Common vs .Core
Before we get to the layers, there's one naming convention you have to internalize early or
you'll spend weeks confused. Every layer has projects whose names end in
.Common (and sometimes .AI.Common) — and at least one project that
is something else, like Application.Core. They mean very different things.
.Common / .AI.Common projects
This is the harness. Reusable, template-level code — pipeline
behaviors, factories, the agent runtime, RAG, MCP, governance, observability. You
shouldn't normally edit these, in the same way you don't edit
Microsoft.Extensions.Logging. If you fork the template for a new
application, this is the part you bring along unchanged.
.Core / your domain projects
This is your application. The specific commands, business rules,
and domain types for whatever you're actually building. If you fork this template to
build a dog-walking platform, your ScheduleWalkCommand,
WalkerProfile, and DogProfile classes go here. Currently
only Application.Core exists, with example agent commands you can extend
or replace.
Imagine you forked this template to build "PupWalk" — an AI-powered dog-walking scheduler. The pieces would shake out like this:
-
Domain.Common,Domain.AI, all the*.Commonprojects — unchanged. Same harness. -
Application.Core— you'd add (or replace with) commands likeScheduleWalkCommand,CancelBookingCommand,FindAvailableWalkersQuery. -
You'd add a new
Domain.PupWalkproject for dog/walker/booking entities. -
You'd add a new
Infrastructure.PupWalkfor the booking-database EF DbContext and Stripe payment client. -
Presentation.ConsoleUIstays (great for ops & debugging) plus you add aPresentation.PupWalkApifor the customer-facing REST endpoints. -
Your skills go in
skills/at the repo root:skills/walk-coordinator/SKILL.md,skills/customer-support/SKILL.md.
Throughout this guide, when we say "you'll edit this" we usually mean a
.Core project, an infrastructure.<your-domain> project, or a
Markdown file in skills/ or agents/. When we say "the harness does
X" we mean code in a .Common project that you should treat as a black box —
understand it, but don't fork it unless you have a very good reason.
Before editing anything in a .Common project, ask: "would another team
forking this template also benefit from my change?" If yes, edit it (and add a test). If
no — if the change is specific to your application — it belongs in
.Core or a new domain-specific project. Keeping that line clean is what
lets the harness keep being a useful template.
The four layers
Domain
Pure C#. No NuGet dependencies beyond System.*. Defines the language of the
problem: AgentManifest, SkillDefinition,
ToolDeclaration, WorkflowState, AgentCard,
Result<T>, all the strongly-typed config classes. If you can describe a
concept on a whiteboard without mentioning a vendor or framework, it lives here.
Application
The "what should happen" layer. CQRS commands like ExecuteAgentTurnCommand,
interfaces like IAgentFactory and ISkillMetadataRegistry, FluentValidation
validators, MediatR pipeline behaviors, the AIToolConverter that bridges internal
tools to Microsoft.Extensions.AI. Application can depend on Domain, but it must
never depend on Infrastructure — it only knows interfaces.
If a file imports anything beyond System.*,
Microsoft.Extensions.*, MediatR, or other Application/Domain
types, it doesn't belong in Application. Push it to Infrastructure.
Infrastructure
The "how it actually happens" layer. ChatClientFactory talks to Azure OpenAI.
FileSystemService reads disk under sandbox rules. RagPipeline
coordinates ingestion and retrieval. McpClient connects to external MCP servers.
Each Infrastructure project implements one or more interfaces declared in Application.
Presentation
Where execution starts. A "Presentation host" is just a runnable program with a
Program.cs. The harness ships several — the full set is in the layer diagram
above — and each is a different way of getting a request into the same engine. The four you are
most likely to run first:
-
Presentation.ConsoleUI— you type at a terminal menu. The simplest entry point, and the one to reach for when debugging. -
Presentation.AgentHub— a SignalR (WebSocket) server, so a browser can stream a conversation token-by-token. -
Presentation.ExecutionApi— a plain REST API. Another system uploads a zipped agent and runs it over HTTP, with no UI involved. See Chapter 17. -
Infrastructure.AI.MCPServer— exposes the harness's own tools to other MCP clients over HTTP. (It lives in an Infrastructure folder for historical reasons, but it behaves as a Presentation host.)
Every one of these runs the same composition root —
services.GetServices(...) in Presentation.Common — which calls every
layer's Add*Dependencies() extension. That is why a feature you add once shows up
in all of them.
The dependency rule, illustrated
Solid arrows are "depends on." The arrow always points inward:
Presentation ──▶ Application ──▶ Domain
│ ▲
│ │
└──▶ Infrastructure ──▶ Application interfaces
└────▶ Domain (read-only)
Two things to internalize:
-
Infrastructure depends on Application, not the other way around.
Infrastructure.AIreferencesApplication.AI.Commonto get theIChatClientFactoryinterface — and then provides the concrete class. Application never knows the concrete exists. -
Presentation is the only layer that touches everything. It does the wiring
— calls
AddDomainDependencies(),AddApplicationDependencies(),AddInfrastructureDependencies(), then builds the service provider.
Where to find anything
Use this table when you know what but not where:
| If you're looking for... | Look in... |
|---|---|
A configuration class (every AppConfig.* property) |
Domain.Common/Config/ |
| An agent manifest type, skill definition, tool declaration | Domain.AI/Agents/, Domain.AI/Skills/, Domain.AI/Tools/ |
| A CQRS command and its handler | Application.Core/CQRS/<feature>/ |
| An interface for an AI service | Application.AI.Common/Interfaces/ |
| A MediatR pipeline behavior | Application.*.Common/MediatRBehaviors/ |
| The Azure OpenAI / OpenAI client wiring | Infrastructure.AI/Factories/ChatClientFactory.cs |
| A built-in tool (file system, calculator, etc.) | Infrastructure.AI/Tools/ |
| MCP server endpoint code | Infrastructure.AI.MCPServer/ |
| The REST API for running an uploaded agent bundle | Presentation.ExecutionApi/ (guide: Ch. 17) |
| MCP client (consuming external MCP servers) | Infrastructure.AI.MCP/ |
| The RAG ingestion + retrieval pipeline | Infrastructure.AI.RAG/ |
| OpenTelemetry setup, exporters, span processors | Infrastructure.Observability/ |
| DI registration for any layer | <Project>/DependencyInjection.cs |
| SKILL.md files (runtime skill definitions) | skills/ at the repo root |
| AGENT.md files (agent manifests) | agents/ at the repo root |
| Tests | src/Content/Tests/<project>.Tests/ — one per source project |
DI registration: the composition root
Every project has its own DependencyInjection.cs with an
Add{Layer}Dependencies() extension method. The Presentation layer's
services.GetServices(...) calls them in order:
// In Presentation.Common — every Presentation host calls this
services
.AddDomainCommonDependencies(appConfig)
.AddDomainAIDependencies(appConfig)
.AddApplicationCommonDependencies(appConfig)
.AddApplicationAICommonDependencies(appConfig)
.AddApplicationCoreDependencies(appConfig)
.AddInfrastructureCommonDependencies(appConfig)
.AddInfrastructureAIDependencies(appConfig)
.AddInfrastructureMCPDependencies(appConfig)
.AddInfrastructureRAGDependencies(appConfig)
.AddInfrastructureObservabilityDependencies(appConfig);
When you add a new service, you don't touch the composition root — you add the registration to
the relevant layer's DependencyInjection.cs. It gets picked up the next time the
host boots.
What's in each Infrastructure project
Infrastructure has the most projects. Here's a quick mental map:
Infrastructure.AIChatClientFactory), sandboxed file/calculation tools, state management, A2A host. Note the AgentFactory itself lives in Application.AI.Common/Factories/, not here.Infrastructure.AI.MCPITool.Infrastructure.AI.MCPServerInfrastructure.AI.RAGInfrastructure.AI.ConnectorsIConnectorClient.Infrastructure.AI.GovernanceInfrastructure.AI.KnowledgeGraphInfrastructure.ObservabilityInfrastructure.APIAccessInfrastructure.CommonTwo rules to internalize
1 · Direction of the arrow
Before you add a project reference, ask: does this point inward? Application → Domain ✓. Infrastructure → Application ✓. Domain → anything ✗. Application → Infrastructure ✗.
The litmus: open .csproj, look at the <ProjectReference> lines.
They should always point at a layer the same color or further left (Domain) than the current
project's color in the stack diagram above.
2 · Interfaces live with the consumer
If Application.AI.Common needs a chat client, it defines
IChatClientFactory in its own Interfaces/ folder.
Infrastructure.AI provides the implementation.
Don't put the interface in Infrastructure "near the
implementation" — that breaks the dependency rule.