
This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the
in C# are 100% mine.
Hi!
What happens when a Python-based AI agent and a .NET-based AI agent need to work together?
That is the idea behind MAF-A2A-NVIDIA-NemoAgents (https://github.com/elbruno/MAF-A2A-NVIDIA-NemoAgents-private): a reference app that shows how to combine NVIDIA NeMo Agent Toolkit, Microsoft Agent Framework, Agent-to-Agent communication, and Aspire into one multi-agent workflow. The repo describes itself as a production-ready sample that demonstrates NVIDIA NeMo Agent Toolkit + Microsoft Agent Framework with A2A communication, orchestrated with Aspire.
And yes, this is exactly the kind of demo I like: different stacks, different responsibilities, one shared protocol. No “everything must be rewritten in my favorite framework” drama. Just agents talking to agents. Beautiful.
The scenario: analysis first, action second
The application models a practical enterprise workflow:
A NeMo Data Analysis Agent receives business or operational data, analyzes metrics, detects trends or anomalies, and generates insights. Then, when an action is needed, a MAF Action Agent can trigger alerts, generate reports, or execute remediation steps. The web UI acts as the human-facing chat surface where users can request analysis and trigger actions.
So the flow is simple:
User ↓ Web Chat UI ↓ NeMo Data Analysis Agent ↓ Analysis result ↓ MAF Action Agent ↓ Alert / Report / Action
The interesting part is that these agents are not built with the same framework. The NeMo agent is Python-based. The MAF agent is .NET-based. The bridge between them is A2A, using JSON-RPC and agent discovery. The repo architecture describes A2A as the communication layer between the web UI, the NeMo agent, and the MAF agent, with Aspire providing health checks, service discovery, tracing, and startup orchestration.
Why two agents?
Because “one mega-agent to rule them all” sounds cool until you need to debug, scale, secure, or explain it.
This repo separates responsibilities:
- NeMo Agent: data analysis, trends, anomalies, metrics, recommendations.
- MAF Agent: actions, alerts, reports, remediation workflows.
- Web UI: chat experience, discovery, orchestration, response rendering.
- Aspire: local distributed app orchestration, health, logs, and OpenTelemetry traces.
The repo calls out this separation of concerns directly: data analysis expertise is not the same as action execution expertise. It also helps with scalability, reliability, and vendor flexibility.
Where A2A fits
A2A is the key enabler here. It gives agents a common way to expose capabilities and communicate across frameworks and languages.
On the Microsoft Agent Framework side, A2A support allows agents to be exposed over A2A endpoints and discovered through agent cards. Microsoft’s A2A docs show that Agent Framework can expose multiple agents with separate A2A endpoints, and the agent-framework-a2a package can both connect to external A2A-compliant agents and expose Agent Framework agents over A2A.
The .NET A2A pattern looks like this in the official MAF examples:
builder.AddA2AServer("weather-agent");app.MapA2AHttpJson("weather-agent", "/a2a/weather-agent");app.MapWellKnownAgentCard(new AgentCard{ Name = "WeatherAgent", Description = "A helpful weather assistant.", SupportedInterfaces = [ new AgentInterface { Url = "https://your-host/a2a/weather-agent", ProtocolBinding = ProtocolBindingNames.HttpJson, ProtocolVersion = "1.0", } ]});
That pattern matters because agent discovery is not a nice-to-have. In a multi-agent system, the orchestrator needs to know:
Who are you?What can you do?Where do I call you?Which protocol do you speak?
That is where the Agent Card becomes useful.
NeMo as an A2A agent
On the NVIDIA side, NeMo Agent Toolkit can publish workflows as A2A agents. NVIDIA’s docs describe nat a2a serve as the command that starts an A2A server, publishes a workflow as an A2A agent, and exposes an Agent Card at /.well-known/agent-card.json.
In this repo, Aspire starts the NeMo workflow using the NeMo Agent Toolkit A2A server approach. The AppHost launches the NeMo agent as an executable, configures the workflow file, host, port, public base URL, NVIDIA API key, Azure OpenAI settings, and OpenTelemetry exporter.
A simplified version of that idea looks like this:
var nvidiaApiKey = builder.AddParameter("nvidia-api-key", secret: true);var azureOpenAiEndpoint = builder.AddParameter("azure-openai-endpoint", secret: true);var azureOpenAiApiKey = builder.AddParameter("azure-openai-api-key", secret: true);var nemo = builder.AddExecutable( name: "nemo-agent", command: "powershell", workingDirectory: ".", args: [ "-NoProfile", "-Command", "nat a2a serve --config_file .\\src\\NemoDataAnalysisAgent\\nemo\\workflow.yml" ]) .WithHttpEndpoint(name: "http", env: "NEMO_PORT") .WithEnvironment("NEMO_HOST", "127.0.0.1") .WithEnvironment("NVIDIA_API_KEY", nvidiaApiKey) .WithEnvironment("AZURE_OPENAI_ENDPOINT", azureOpenAiEndpoint) .WithEnvironment("AZURE_OPENAI_API_KEY", azureOpenAiApiKey) .WithOtlpExporter();
The important idea: NeMo owns the analysis workflow, but it is exposed as an A2A-compatible service.
MAF as the action agent
The MAF side is a .NET service focused on action execution. The repo exposes endpoints for executing actions, triggering alerts, generating reports, and handling A2A JSON-RPC requests. The README describes the MAF Action Agent as responsible for pluggable action handlers, multi-level alerts, async report generation, health checks, A2A integration, and OpenTelemetry tracing.
A simplified version of the MAF action endpoint looks like this:
app.MapPost("/api/actions/execute", async (ActionRequest request, IActionExecutor executor) => { var result = await executor.ExecuteActionAsync(request); return Results.Ok(result); });
And the A2A-facing endpoints follow this shape:
app.MapGet("/.well-known/agent-card.json", () =>{ return Results.Ok(new { name = "MAF Action Agent", description = "Executes actions based on NeMo analysis", version = "1.0.0", capabilities = new[] { "execute-actions", "trigger-alerts", "generate-reports" }, endpoint = "/a2a/maf-action-agent", a2a_version = "1.0" });});app.MapPost("/a2a/maf-action-agent", async (HttpContext context, IA2ABridge bridge) => { using var reader = new StreamReader(context.Request.Body); var body = await reader.ReadToEndAsync(); var response = await bridge.ProcessA2ARequestAsync(body); return Results.Text(response, "application/json"); });
That is the handoff: the agent advertises what it can do, then receives structured requests through the A2A endpoint.
The web UI as the orchestrator
The web chat interface is where the human interacts with the system. It discovers both agents, sends analysis requests to NeMo, and optionally sends action requests to MAF. The README calls out a NeMo-first routing pattern: chat responses come from NeMo analysis before optional MAF action execution.
A simplified orchestration pattern looks like this:
public async Task<ChatResponse> ProcessChatAsync(ChatRequest request){ var message = request.Message.ToLowerInvariant(); if (ShouldTriggerAction(message)) { var action = new ActionRequest { ActionType = InferActionType(message), Parameters = new Dictionary<string, object?> { ["message"] = request.Message } }; var actionResult = await orchestrator.ExecuteActionAsync(action); return new ChatResponse { RespondedBy = "MAF Action Agent", Content = actionResult.Details }; } var nemoReply = await orchestrator.SendNemoMessageAsync( request.Message, request.SessionId); return new ChatResponse { RespondedBy = "NeMo Data Analysis Agent", Content = nemoReply };}
That is a nice pattern for demos and real apps: default to insight, escalate to action only when the user explicitly asks for it.
A2A request shape
The architecture docs describe JSON-RPC 2.0 over HTTP as the protocol used between agents. The documented example sends a method such as analyze, passes parameters like metric, timeframe, and aggregation, and receives a structured result with trend, anomalies, forecast, and insights.
A simplified A2A-style request could look like this:
{ "jsonrpc": "2.0", "method": "message/send", "params": { "message": { "kind": "message", "role": "user", "parts": [ { "kind": "text", "text": "Analyze quarterly revenue trends" } ] } }, "id": "request-001"}
And the system can route that to the NeMo agent first. Later, a second request can trigger the MAF agent:
{ "actionType": "trigger-alert", "parameters": { "message": "Trigger alert for high CPU usage", "analysisSummary": "CPU usage increased 35% over baseline." }}
That second part is where things become useful. The action agent is not acting in isolation; it can act based on the previous analysis.
Why Aspire matters here
Aspire is doing a lot of the boring-but-important work.
The repo uses Aspire to coordinate startup order, service discovery, health checks, logging, and OpenTelemetry correlation across the NeMo agent, the MAF agent, and the web UI. The README describes Aspire as responsible for automatic agent endpoint registration, dependency management, continuous health checks, distributed tracing, unified logs, and NeMo pre-warm.
This is one of the hidden wins of the repo. Multi-agent demos often fail because they are hard to run. Here, Aspire gives the app a “one command, multiple services” developer experience:
aspire start
The README says that after starting the app, the web UI is available on port 5000, and the sample prompts include NeMo-only analysis, MAF-only alert triggering, and a combined NeMo + MAF workflow.
Why this repo is interesting
This repo is not just “another chat app.”
It demonstrates a few patterns that matter for real enterprise AI systems:
- Cross-framework agents
Python and .NET agents can collaborate without forcing everything into one stack. - Clear agent responsibility boundaries
One agent analyzes. One agent acts. That makes the architecture easier to explain, test, and evolve. - A2A as the interoperability layer
A2A gives each agent a common communication contract. - Aspire as the local cloud-native dev loop
Developers can run, observe, and debug the system locally before thinking about containers or cloud deployment. - Observability from the beginning
The architecture includes OpenTelemetry tracing, structured logging, and health checks instead of treating them as “we’ll add this later” work. The repo’s architecture highlights call out distributed tracing, resilience patterns, scalability, and a future roadmap for TLS, mutual authentication, and authorization.
Final thoughts
The big takeaway for me: A2A makes heterogeneous agent systems feel much more realistic.
In real organizations, not every team will use the same language, same framework, same model provider, or same runtime. Some teams will build in Python. Some will build in .NET. Some will use NVIDIA tooling. Some will use Microsoft Agent Framework. Some will deploy locally first, then move to Azure.
And that is fine.
The goal is not to force every agent into one framework. The goal is to give them a shared way to discover each other, communicate, and collaborate.
That is what this repo demonstrates: NVIDIA NeMo for analysis, MAF for action, A2A for interoperability, and Aspire for orchestration.
Agents talking to agents.
Finally, in a way we can actually run, observe, and explain.
Happy coding!
Greetings
El Bruno
More posts in my blog ElBruno.com.
More info in https://beacons.ai/elbruno




