July 31st, 2025
0 reactions

Building AI Agents with the A2A .NET SDK

Building AI Agents with the A2A .NET SDK

The AI development landscape is rapidly evolving. Autonomous agents are becoming increasingly sophisticated, specialized, and capable of handling complex tasks across diverse domains. From customer service bots to data analysis agents, from content creation assistants to workflow orchestrators – AI agents are proliferating at an unprecedented pace.

But what happens when these agents need to work together? How do specialized agents discover each other, communicate effectively, and collaborate on complex tasks that require multiple capabilities?

Today, we’re excited to announce the release of the A2A .NET SDK – an implementation of the Agent2Agent (A2A) protocol that enables .NET developers to build both A2A servers and clients. With this SDK, you can now create agents that seamlessly communicate and collaborate with other agents in the ecosystem, whether they’re built with .NET or any other technology that supports the A2A protocol.

This release strengthens the broader exisitng AI ecosystem, particularly for developers working with Azure AI Foundry and Semantic Kernel. Semantic Kernel already includes A2A agent capabilities through its Python and .NET implementations, and this community-driven .NET SDK enables the Semantic Kernel team to adopt the latest A2A protocol features more rapidly while maintaining the framework’s enterprise-grade stability.

Note: This SDK is currently in preview. While we strive for stability, the API may change as we continue to refine and improve the implementation based on community feedback and protocol evolution.

What is the Agent2Agent (A2A) Protocol?

The Agent2Agent (A2A) Protocol is an open standard designed to solve a fundamental challenge in the AI ecosystem: how do independent AI agents discover, communicate, and collaborate with each other?

In today’s world, AI agents are often isolated systems built with different frameworks, by different vendors, and with varying capabilities. A2A provides a common language that allows these agents to:

  • Discover each other’s capabilities
  • Negotiate interaction modalities (text, forms, media)
  • Securely collaborate on long-running tasks
  • Operate without exposing their internal state, memory, or tools

Key Features of the A2A .NET SDK

🔍 Agent Capability Discovery

The SDK includes the A2ACardResolver class for retrieving agent capabilities described in standardized Agent Cards. These cards provide essential information about agent capabilities (streaming, push notifications, extensions), agent host URL, image, supported modalities, and other details.

A2ACardResolver cardResolver = new A2ACardResolver(new Uri("http://localhost:5100/"));
AgentCard agentCard = await cardResolver.GetAgentCardAsync();

💬 Flexible Communication Patterns

Message-based Communication: Direct, synchronous messaging for immediate responses

A2AClient client = new A2AClient(new Uri(agentCard.Url));

// Send a message that the agent can handle promptly and will respond with a message.
Message response = (Message)await client.SendMessageAsync(new MessageSendParams
{
    Message = new Message
    {
        Role = MessageRole.User,
        Parts = [new TextPart { Text = "What is the current weather in Settle" }]
    }
});

Console.WriteLine($"Received: {((TextPart)response.Parts[0]).Text}");

Task-based Communication: Persistent, long-running agent tasks for asynchronous workflows

A2AClient client = new A2AClient(new Uri(agentCard.Url));

// Send a message that will require the agent to process it asynchronously and the agent will answer with a task.
AgentTask agentTask = (AgentTask)await client.SendMessageAsync(new MessageSendParams
{
    Message = new Message
    {
        Role = MessageRole.User,
        Parts = [new TextPart { Text = "Generate an image of a sunset over the mountains" }]
    }
});

Console.WriteLine("Received task details:");
Console.WriteLine($" ID: {agentTask.Id}");
Console.WriteLine($" Status: {agentTask.Status.State}");
Console.WriteLine($" Artifact: {(agentTask.Artifacts?[0].Parts?[0] as TextPart)?.Text}");

// Poll the task progress
agentTask = await client.GetTaskAsync(agentTask.Id);
switch (agentTask.Status.State)
{
    case TaskState.Running:
        // Handle running task
        break;
    case TaskState.Completed:
        // Process completed task
        break;
    case TaskState.Failed:
        // Handle task failure
        break;
    default:
        Console.WriteLine($"Task status: {agentTask.Status.State}");
        break;
}

For more details about these communication patterns, see Message or a Task.

🌊 Real-time Streaming Support

For scenarios requiring real-time updates, the SDK provides comprehensive streaming support using Server-Sent Events:

A2AClient client = new A2AClient(new Uri(agentCard.Url));

await foreach (SseItem<A2AEvent> sseItem in client.SendMessageStreamAsync(new MessageSendParams { Message = userMessage }))
{
    Message agentResponse = (Message)sseItem.Data;
    // Process streaming response chunks as they arrive
    Console.WriteLine($"Received: {((TextPart)agentResponse.Parts[0]).Text}");
}

🏗️ ASP.NET Core Integration

Building A2A-compatible agents is straightforward with the included ASP.NET Core extensions:

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
WebApplication app = builder.Build();

TaskManager taskManager = new TaskManager();
TaskManager agent = new TaskManager();
agent.Attach(taskManager);

// Single line to expose your agent via A2A protocol
app.MapA2A(taskManager, "/agent");
app.Run();

Getting Started with A2A Agent

To see how the A2A protocol works in practice, we’ll build a simple echo agent that demonstrates the core concepts. This agent will receive messages and respond by echoing them back, providing a clear example of how A2A communication flows.

Prerequisites

Before we start building our A2A agent, make sure you have the following installed on your development machine:

  • .NET 8.0 SDK or later – Download from dotnet.microsoft.com
  • A code editor – Visual Studio, Visual Studio Code, or your preferred .NET IDE
  • Basic familiarity with C# and ASP.NET Core – We’ll be creating web applications and working with async/await patterns

You can verify your .NET installation by running:

dotnet --version

Setting Up Our Agent Project

We start by creating a new web application that will host our agent. This hosting app provides the foundation where our agent will live and communicate with the outside world:

dotnet new webapp -n A2AAgent -f net9.0
cd A2AAgent

With our project created, we need to add the A2A packages that will handle the protocol communication for us:

dotnet add package A2A --prerelease              # Core A2A protocol support
dotnet add package A2A.AspNetCore --prerelease   # A2A protocol integration with ASP.NET Core
dotnet add package Microsoft.Extensions.Hosting  # Hosting services

Building Our Echo Agent

Now comes the interesting part – creating our actual agent. We’ll build a simple echo agent that takes any message it receives and sends it right back. While this might seem basic, it demonstrates all the fundamental concepts you’ll need for more sophisticated agents.

Let’s create a new file called EchoAgent.cs with our agent implementation:

using A2A;

namespace A2AAgent;

public class EchoAgent
{
    public void Attach(ITaskManager taskManager)
    {
        taskManager.OnMessageReceived = ProcessMessageAsync;
        taskManager.OnAgentCardQuery = GetAgentCardAsync;
    }

    private async Task<Message> ProcessMessageAsync(MessageSendParams messageSendParams, CancellationToken ct)
    {
        // Get incoming message text
        string request = messageSendParams.Message.Parts.OfType<TextPart>().First().Text;

        // Create and return an echo message
        return new Message()
        {
            Role = MessageRole.Agent,
            MessageId = Guid.NewGuid().ToString(),
            ContextId = messageSendParams.Message.ContextId,
            Parts = [new TextPart() { Text = $"Echo: {request}" }]
        };
    }

    private async Task<AgentCard> GetAgentCardAsync(string agentUrl, CancellationToken cancellationToken)
    {
        return new AgentCard()
        {
            Name = "Echo Agent",
            Description = "An agent that will echo every message it receives.",
            Url = agentUrl,
            Version = "1.0.0",
            DefaultInputModes = ["text"],
            DefaultOutputModes = ["text"],
            Capabilities = new AgentCapabilities() { Streaming = true },
            Skills = [],
        };
    }
}

Looking at this code, you can see how our agent comes to life through three key pieces. The Attach() method is where we connect our agent to the A2A framework – think of it as plugging our agent into the communication network. When messages arrive, they flow through the ProcessMessageAsync() method, which is the heart of our agent where all the thinking happens. In our case, we’re simply echoing messages back, but this is where you’d integrate your LLM or other AI processing logic. The GetAgentCardAsync() method serves as our agent’s business card, telling other agents who we are and what we can do.

Notice that our agent declares streaming capability, which means other agents can choose to communicate with us in real-time for immediate responses.

Bringing Our Agent Online

With our agent implementation ready, we need to connect it to the web so other agents can find and talk to it. This is where the magic of the A2A .NET SDK really shines – we only need a few lines of code to make our agent discoverable.

Let’s update our Program.cs to host our agent:

using A2A;
using A2A.AspNetCore;
using A2AAgent;
using Microsoft.AspNetCore.Builder;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
WebApplication app = builder.Build();

// Create and attach the EchoAgent to the TaskManager
EchoAgent agent = new EchoAgent();
TaskManager taskManager = new TaskManager();
agent.Attach(taskManager);

// Expose agent via A2A protocol
app.MapA2A(taskManager, "/agent");
await app.RunAsync();

This simple setup creates our web application, connects our EchoAgent to a TaskManager (which handles the A2A protocol details), and exposes everything through the “/agent” endpoint. The MapA2A extension method does all the heavy lifting, automatically handling agent discovery, message routing, and protocol compliance.

Once we run our agent with dotnet run, it becomes available at http://localhost:5000 and ready to communicate with any other A2A-compatible agent or client.

Testing Our Agent

Now that our agent is running, we need a way to talk to it. Let’s create a simple client application that will demonstrate how agents communicate with each other. This client will discover our agent, learn about its capabilities, and then send it some messages to see the echo in action.

We’ll start by creating a new console application:

dotnet new console -n A2AClient -f net9.0
cd A2AClient
dotnet add package A2A --prerelease
dotnet add package System.Net.ServerSentEvents --prerelease

With our client project ready, we can create a program that connects to our echo agent and demonstrates both regular and streaming communication:

using A2A;
using System.Net.ServerSentEvents;

// 1. Get the agent card
A2ACardResolver cardResolver = new(new Uri("http://localhost:5000/"));
AgentCard echoAgentCard = await cardResolver.GetAgentCardAsync();

Console.WriteLine($"Connected to agent: {echoAgentCard.Name}");
Console.WriteLine($"Description: {echoAgentCard.Description}");
Console.WriteLine($"Streaming support: {echoAgentCard.Capabilities?.Streaming}");

// 2. Create an A2A client to communicate with the agent using url from the agent card
A2AClient agentClient = new(new Uri(echoAgentCard.Url));

// 3. Create a message to send to the agent
Message userMessage = new()
{
    Role = MessageRole.User,
    MessageId = Guid.NewGuid().ToString(),
    Parts = [new TextPart { Text = "Hello from the A2A client!" }]
};

// 4. Send the message using non-streaming API
Console.WriteLine("\n=== Non-Streaming Communication ===");
Message agentResponse = (Message)await agentClient.SendMessageAsync(new MessageSendParams { Message = userMessage });
Console.WriteLine($"Received response: {((TextPart)agentResponse.Parts[0]).Text}");

// 5. Send the message using streaming API
Console.WriteLine("\n=== Streaming Communication ===");
await foreach (SseItem<A2AEvent> sseItem in agentClient.SendMessageStreamAsync(new MessageSendParams { Message = userMessage }))
{
    Message streamingResponse = (Message)sseItem.Data;
    Console.WriteLine($"Received streaming chunk: {((TextPart)streamingResponse.Parts[0]).Text}");
}

This client demonstrates the complete A2A communication flow. First, we discover our agent by fetching its agent card, which tells us what the agent can do and how to reach it. Then we create an A2A client using the agent’s URL and send it a friendly message. The beautiful thing about A2A is that it supports both streaming and non-streaming communication patterns, allowing us to choose the approach that best fits our needs.

When we run our client with dotnet run, we can see our echo agent in action:

A2A Client Output

Exploring Further

With our echo agent working, you might want to explore what it can do using the A2A Inspector. This web-based tool connects to any A2A agent and lets you examine its capabilities, send test messages, and see how it responds. The inspector is also invaluable for debugging purposes, as it shows you the raw request and response messages being exchanged, helping you understand exactly what’s happening under the hood. Simply point the inspector at the running agent at http://localhost:5000 and start experimenting.

You can find the A2A Inspector and installation instructions at the A2A Inspector repository.

What we’ve built here is just the beginning. Our simple echo agent demonstrates the core concepts, but you can easily extend it by replacing the echo logic with calls to AI models or even to other AI agents to create truly intelligent and collaborative systems.

For developers already using Semantic Kernel, this community-driven A2A .NET SDK offers a path to keep pace with the evolving A2A protocol. Semantic Kernel’s existing A2A agent implementation can leverage improvements and features from this SDK, ensuring your multi-agent orchestration remains current with the latest protocol developments while maintaining the production-ready stability that Semantic Kernel provides.

Where Your Agent Journey Continues

Now that you’ve created your first A2A agent, you’re ready to explore more. The echo agent is your foundation – here’s where to go next:


The A2A .NET SDK is open source and available under the Apache 2.0 License. Contributions, feedback, and community involvement are welcome!

Author

Sergey Menshykh
Principal Software Engineer
Adam Sitnik
Senior Software Engineer
Brandon H
Principal Cloud Solution Architect

1 comment

Sort by :