AI & ML
Multi-Agent Orchestration in .NET Using A2A
ohalay Dev.to (EN Zone)
2 views
Investigation of how to orchestrate an agentic system using an A2A protocol based on .NET primitives and create a PoC. Use Case: We have existing agents that support A2A, and we want to build it into a multi-agent system.
Protocol
Let's start from theory. The agent-to-agent (A2A) protocol is designed to define well-known contracts for communication between agents without humans. Every agent publishes an AgentCard /.well-known/agent-card.json. An Agent client discovers the card, then sends tasks to the agent as messages.
Our building blocks:
AgentCard - what the agent can do
Agent handler - the server-side logic
A2AClient - sends messages to a remote agent
graph LR
User([User]) --> Orch[Orchestrator]
Orch -. discover AgentCard .-> A[Assortment agent]
Orch -. discover AgentCard .-> S[SupplyChain agent]
Orch -- A2A SendMessage --> A
Orch -- A2A SendMessage --> S
A --> AT[(Catalog tools)]
S --> ST[(Stock tools)]
PoC setup
Two agents: Assortment and SupplyChain that support A2A
Orchestrator: agent that routes user requests to agents and aggregates results
LLM: Ollama based on Microsoft.Extensions.AI as an abstraction that support tool calling. Can be swapped for any IChatClient (Azure OpenAI, Bedrock, OpenAI, etc.).
Aspire host, which connects and runs all services together and helps to understand flow using OpenTelemetry
Agent implementation
Microsoft provides an abstraction for the A2A spec for ASP.NET Core
var agentCard = new AgentCard
{
Name = "AssortmentSpecialist",
Description = "Handles shop inventories, product categorizations, catalogs, and store assortments.",
Skills =
[
new AgentSkill
{
Id = "get-product",
Name = "GetProduct",
Description = "Look up a product's SKU, category, active status, and store coverage by name.",
},
],
};
builder.Services.AddA2AAgent<DomainAgentHandler>(agentCard);
var app = builder.Build();
app.MapWellKnownAgentCard(agentCard, "");
app.MapA2A("/");
The handler processes tasks using the LLM and the agent's own tools.
public async Task ExecuteAsync(RequestContext context, AgentEventQueue eventQueue, CancellationToken ct)
{
var responder = new MessageResponder(eventQueue, context.ContextId);
var messages = new List<ChatMessage>
{
new(ChatRole.System, "You are the Assortment specialist. Use the tools to look up real data."),
new(ChatRole.User, context.UserText ?? string.Empty),
};
var options = new ChatOptions { Tools = [AIFunctionFactory.Create(tools.GetProduct)] };
var response = await chatClient.GetResponseAsync(messages, options, ct);
await responder.ReplyAsync(response.Text, ct);
}
Orchestrator
There are several ways to orchestrate agents
We craft Manual explicit route
Define Workflow engine
Let LLM route
We chose the last one because we want to have the possibility to add a new agent without any code changes. That way, we register every agent card dynamically as an orchestrator agent tool, and aggregation happens in the same LLM loop.
public async Task<string> HandleAsync(ChatThread thread, string userMessage, CancellationToken ct)
{
var agents = await registry.GetAgents(ct);
var tools = agents.Select(ToTool).Cast<AITool>().ToList();
var messages = new List<ChatMessage> { new(ChatRole.System, SystemPrompt) };
messages.Add(new ChatMessage(ChatRole.User, userMessage));
using var client = new FunctionInvokingChatClient(chatClient)
{
AllowConcurrentInvocation = true,
}.AsBuilder().Build();
var response = await client.GetResponseAsync(
messages,
new ChatOptions { Tools = tools, AllowMultipleToolCalls = true },
ct);
return response.Text;
}
We convert remote agents to AIFunction from AgentCard
private AIFunction ToTool(RemoteAgent agent)
{
var dispatch = async (string request, CancellationToken ct) =>
{
var response = await agent.Client!.SendMessageAsync(request, Role.User, cancellationToken: ct);
return ExtractText(response);
};
return AIFunctionFactory.Create(dispatch, agent.Card!.Name,
$"Ask the {agent.Card.Name} specialist. {agent.Card.Description}");
}
The orchestrator resolves each card with A2ACardResolver, then turns it into a tool.
A request that needs both agents makes the LLM call both agents in parallel and merge their replies into one.
sequenceDiagram
actor User
participant Orch as Orchestrator (LLM loop)
participant A as Assortment agent
participant S as SupplyChain agent
User->>Orch: "Stores carrying the coat AND its stock?"
par send both messages in parallel
Orch->>A: A2A SendMessage(sub-task)
and
Orch->>S: A2A SendMessage(sub-task)
end
A-->>Orch: catalog answer
S-->>Orch: stock answer
Orch->>Orch: merge results
Orch-->>User: one cohesive answer
Conclusion
The result is visualized with traces in the cover image
Protocol has a stable version, but .NET libs are still in preview
Communication between agents and tool calls takes time. It is better suited to long-running tasks than for immediate responses.
Managing chat history is also a pain point.
ohalay
/
a2a-poc
A2A Multi-Agent Orchestrator
A .NET 10 PoC for the A2A. Orchestrator discovers agents over HTTP, exposes each as a tool to one LLM loop, and aggregate to one response. All LLM inference runs locally through Ollama (llama3.2).
Architecture
graph TB
Ollama[("Ollama<br/>llama3.2<br/>local LLM (external)")]
User([User / Browser]) -->|HTTP| Orch
subgraph Orch["Orchestrator"]
API["Minimal API + chat UI<br/>/api/chat"]
Svc["OrchestrationService<br/>one tool-calling LLM loop"]
Reg["AgentRegistry<br/>(AgentCards + A2AClients)"]
Store["ChatStore<br/>(history by threadId)"]
API --> Svc
Svc --> Reg
Svc --> Store
end
Svc -->|LLM: tool loop + synthesis| Ollama
subgraph Assort["AssortmentSpecialist (A2A server)"]
AH["DomainAgentHandler"]
AT["AssortmentTools<br/>GetProduct / GetActiveCatalog"]
AH --> AT
end
subgraph Supply["SupplyChainAnalyst (A2A server)"]
SH["DomainAgentHandler"]
ST["SupplyChainTools<br/>GetStock / GetShipments"]
SH --> ST
end
Reg -.->|discover AgentCard| Assort
Reg -.->|discover AgentCard| Supply
Svc -->|A2A SendMessage / tool call| Assort
Svc -->|A2A SendMessage / tool call| Supply
AH -->|LLM: tool-calling| Ollama
SH -->|LLM: tool-calling| Ollama
Loading
See docs/architecture.md for diagrams and the full request flow, and
AGENTS.md…
View on GitHub
Read original: https://dev.to/ohalay/multi-agent-orchestration-in-net-using-a2a-4m5f
← Previous
DevOps in Financial Services: Moving Fast Without Losing Control
Next →
I Created an AI Fitness Coach with Grok Bot
Related
OpenAI can't rule out that it stole its most recent breakthrough
AI & ML
1
DEV Community
Does an LSP help a coding agent?
AI & ML
2
DEV Community
My 3B Model Found a Shortcut. It Took Me Three Fixes to Close It.
AI & ML
2
Dev.to (EN Zone)
How we made 2,000 customer conversations queryable in a few hours
AI & ML
2
Dev.to (EN Zone)
Comments0
No comments yet — be the first