As AI systems mature into multi‑agent ecosystems, the need for agents to communicate reliably and securely has become fundamental. Traditionally, agents built on different frameworks like Semantic Kernel, LangChain, custom orchestrators, or enterprise APIs do not share a common communication model. This creates brittle integrations, duplicate logic, and siloed intelligence. The Agent‑to‑Agent Standard (A2AS) addresses this gap by defining a universal, vendor‑neutral protocol for structured agent interoperability.
A2A establishes a common language for agents, built on familiar web primitives: JSON‑RPC 2.0 for messaging and HTTPS for transport. Each agent exposes a machine‑readable Agent Card describing its capabilities, supported input/output modes, and authentication requirements. Interactions are modeled as Tasks, which support synchronous, streaming, and long‑running workflows. Messages exchanged within a task contain Parts; text, structured data, files, or streams, that allow agents to collaborate without exposing internal implementation details.
By standardizing discovery, communication, authentication, and task orchestration, A2A enables organizations to build composable AI architectures. Specialized agents can coordinate deep reasoning, planning, data retrieval, or business automation regardless of their underlying frameworks or hosting environments. This modularity, combined with industry adoption and Linux Foundation governance, positions A2A as a foundational protocol for interoperable AI systems.
A2AS in .NET — Implementation Guide
Prerequisites
• .NET 8 SDK
• Visual Studio 2022 (17.8+)
• A2A and A2A.AspNetCore packages
• Curl/Postman (optional, for direct endpoint testing)
The open‑source A2A project provides a full‑featured .NET SDK, enabling developers to build and host A2A agents using ASP.NET Core or integrate with other agents as a client. Two A2A and A2A.AspNetCore packages power the experience.
The SDK offers:
- A2AClient - to call remote agents
- TaskManager - to manage incoming tasks & message routing
- AgentCard / Message / Task models - strongly typed protocol objects
- MapA2A() - ASP.NET Core router integration that auto‑generates protocol endpoints
This allows you to expose an A2A‑compliant agent with minimal boilerplate.
Project Setup
- Create two separate projects:
-
- CurrencyAgentService → ASP.NET Core web project that hosts the agent
- A2AClient → Console app that discovers the agent card and sends a message
- Install the packages from the pre-requisites in the above projects.
Building a Simple A2A Agent (Currency Agent Example)
Below is a minimal Currency Agent implemented in ASP.NET Core. It responds by converting amounts between currencies.
Step 1: In CurrencyAgentService project, create the CurrencyAgentImplementation class to implement the A2A agent. The class contains the logic for the following:
a) Describing itself (agent “card” metadata).
b) Processing the incoming text messages like “100 USD to EUR”.
c) Returning a single text response with the conversion.
The AttachTo(ITaskManager taskManager) method hooks two delegates on the provided taskManager -
a) OnAgentCardQuery → GetAgentCardAsync: returns agent metadata.
b) OnMessageReceived → ProcessMessageAsync: handles incoming messages and produces a response.
Step 2:
In the Program.cs of the Currency Agent Solution, create a TaskManager , and attach the agent to it, and expose the A2A endpoint.
Typical flow:
- GET /agent → A2A host asks OnAgentCardQuery → returns the card
- POST /agent with a text message → A2A host calls OnMessageReceived → returns the conversion text.
All fully A2A‑compliant.
Calling an A2A Agent from .NET
To interact with any A2A‑compliant agent from .NET, the client follows a predictable sequence: identify where the agent lives, discover its capabilities through the Agent Card, initialize a correctly configured A2AClient, construct a well‑formed message, send it asynchronously, and finally interpret the structured response. This ensures your client is fully aligned with the agent’s advertised contract and remains resilient as capabilities evolve.
Below are the steps implemented to call the A2A agent from the A2A client:
- Identify the agent endpoint:
- Why: You need a stable base URL to resolve the agent’s metadata and send messages.
- What: Construct a Uri pointing to the agent service, e.g., https://localhost:7009/agent.
- Discover agent capabilities via an Agent Card.
- Why: Agent Cards provide a contract: name, description, final URL to call, and features (like streaming). This de-couples your client from hard-coded assumptions and enables dynamic capability checks.
- What: Use A2ACardResolver with the endpoint Uri, then call GetAgentCardAsync() to obtain an AgentCard.
- Initialize the A2AClient with the resolved URL.
- Why: The client encapsulates transport details and ensures messages are sent to the correct agent endpoint, which may differ from the discovery URL.
- What: Create A2AClient using new Uri (currencyCard.Url) from the Agent Card for correctness.
- Construct a well-formed agent request message.
- Why: Agents typically require structured messages for roles, traceability, and multi-part inputs. A unique message ID supports deduplication and logging.
- What: Build an AgentMessage:
• Role = MessageRole.User clarifies intent.
• MessageId = Guid.NewGuid().ToString() ensures uniqueness.
• Parts contains content; for simple queries, a single TextPart with the prompt (e.g., “100 USD to EUR”).
- Package and send the message.
- Why: MessageSendParams can carry the message plus any optional settings (e.g., streaming flags or context). Using a dedicated params object keeps the API extensible.
- What: Wrap the AgentMessage in MessageSendParams and call SendMessageAsync(...) on the A2AClient.
- Outcome: Await the asynchronous response to avoid blocking and to stay scalable.
- Interpret the agent response.
- Why: Agents can return multiple Parts (text, data, attachments). Extracting the appropriate part avoids assumptions and keeps your client robust.
- What: Cast to AgentMessage, then read the first TextPart’s Text for the conversion result in this scenario.
Best Practices
1. Keep Agents Focused and Single‑Purpose
Design each agent around a clear, narrow capability (e.g., currency conversion, scheduling, document summarization). Single‑responsibility agents are easier to reason about, scale, and test, especially when they become part of larger multi‑agent workflows.
2. Maintain Accurate and Helpful Agent Cards
The Agent Card is the first interaction point for any client. Ensure it accurately reflects:
-
Supported input/output formats
-
Streaming capabilities
-
Authentication requirements (if any)
-
Version information
A clean and honest card helps clients integrate reliably without guesswork.
3. Prefer Structured Inputs and Outputs
Although A2A supports plain text, using structured payloads through DataPart objects significantly improves consistency. JSON inputs and outputs reduce ambiguity, eliminate prompt‑engineering edge cases, and make agent behavior more deterministic especially when interacting with other automated agents.
4. Use Meaningful Task States
Treat A2A Tasks as proper state machines. Transition through states intentionally (Submitted → Working → Completed, or Working → InputRequired → Completed). This gives clients clarity on progress, makes long‑running operations manageable, and enables more sophisticated control flows.
5. Provide Helpful Error Messages
Make use of A2A and JSON‑RPC error codes such as -32602 (invalid input) or -32603 (internal error), and include additional context in the error payload. Avoid opaque messages, error details should guide the client toward recovery or correction.
6. Keep Agents Stateless Where Possible
Stateless agents are easier to scale and less prone to hidden failures. When state is necessary, ensure it is stored externally or passed through messages or task contexts. For local POCs, in‑memory state is acceptable, but design with future statelessness in mind.
7. Validate Input Strictly
Do not assume incoming messages are well‑formed. Validate fields, formats, and required parameters before processing. For example, a currency conversion agent should confirm both currencies exist and the value is numeric before attempting a conversion.
8. Design for Streaming Even if Disabled
Streaming is optional, but it’s a powerful pattern for agents that perform progressive reasoning or long computations. Structuring your logic so it can later emit partial TextPart updates makes it easy to upgrade from synchronous to streaming workflows.
9. Include Traceability Metadata
Embed and log identifiers such as TaskId, MessageId, and timestamps. These become crucial for debugging multi‑agent scenarios, improving observability, and correlating distributed workflows—especially once multiple agents collaborate.
10. Offer Clear Guidance When Input Is Missing
Instead of returning a generic failure, consider shifting the task to InputRequired and explaining what the client should provide. This improves usability and makes your agent self‑documenting for new consumers.
