cd /news/ai-agents/multi-agent-orchestration-in-net-usi… · home topics ai-agents article
[ARTICLE · art-123781] src=dev.to ↗ pub= topic=ai-agents verified=true sentiment=· neutral

Multi-Agent Orchestration in .NET Using A2A

A developer demonstrated how to orchestrate a multi-agent system in .NET using the Agent-to-Agent (A2A) protocol, converting remote agents into AIFunction tools for an LLM-based orchestrator. The proof-of-concept dynamically registers agent cards, enabling parallel calls to specialized agents like an assortment specialist and a supply chain agent without code changes.

read3 min views3 publishedSep 8, 2026

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.

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:

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)]

IChatClient (Azure OpenAI, Bedrock, OpenAI, etc.). 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);
}

There are several ways to orchestrate agents

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

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).

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

See docs/architecture.md for diagrams and the full request flow, and AGENTS.md

── more in #ai-agents 4 stories · sorted by recency
── more on @microsoft 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/multi-agent-orchestr…] indexed:0 read:3min 2026-09-08 ·