Building Cross-Language .NET and Python Agent Applications with AutoGen
AutoGen enables cross-language agent applications by using a gRPC-based worker runtime that allows .NET and Python agents to communicate through a shared message bus using protobuf serialization.
Microsoft AutoGen is a multi-agent framework designed for building complex AI applications in the microsoft/autogen repository. Whether you are working in Python, .NET, or both, AutoGen provides a unified runtime that enables building cross-language .NET and Python agent applications through a shared communication layer and language-agnostic primitives.
Architecture Overview
AutoGen organizes its cross-language capabilities into a layered architecture that separates core runtime mechanics from high-level agent implementations:
| Layer | Purpose | Core Source |
|---|---|---|
| Core API | Language-agnostic primitives for agents, topics, subscriptions, message routing, and state persistence | Python: autogen_core/_agent.py.NET: InProcessRuntime.cs |
| AgentChat API | Opinionated, high-level helper classes (e.g., AssistantAgent, ToolAgent) for rapid prototyping |
Python: autogen_agentchat/agents.py.NET: BaseAgent.cs |
| Extensions | Concrete implementations of LLM clients, tools, and runtime transports (gRPC, HTTP) | Python: autogen_ext/runtimes/grpc.py.NET: GrpcWorkerAgentRuntime.cs |
| Samples | Ready-to-run demos showing cross-language interoperability | Python: hello_python_agent.py.NET: HelloAgent.cs |
How Cross-Language Communication Works
The cross-language flow relies on a gRPC worker runtime that acts as a bridge between Python and .NET processes:
-
Runtime Transport – A
GrpcWorkerAgentRuntimeruns as a sidecar process in both Python (autogen_ext/runtimes/grpc.py) and .NET (GrpcWorkerAgentRuntime.cs). It exposes theIAgentRuntimecontract over gRPC and forwards messages to the local runtime implementation (InProcessRuntimefor .NET or the Python equivalent). -
Agent Registration – Each language registers its agents with the remote runtime via
add_subscriptionandregister_agentAPIs. Subscriptions map a topic to an agent type, enabling the runtime to route messages correctly. -
Message Exchange – When an agent publishes a message (
runtime.publish_message), the runtime serializes it (JSON or protobuf) and sends it across the gRPC channel. The receiving runtime deserializes the payload, looks up the subscription, resolves the target agent using itsAgentId, and invokes theon_messagehandler. -
State and Lifecycle – Agents can persist state using
save_stateandload_statemethods. The runtime guarantees graceful shutdown throughstop_when_signalandstop_when_idlemethods.
Key Architectural Components
Understanding these core primitives is essential for building cross-language .NET and Python agent applications:
-
AgentId– A pair of(type, key)that uniquely identifies an agent across processes and languages. -
TopicId– A hierarchical string (e.g.,"HelloTopic") that drives message routing between agents. -
Subscription– Either a type subscription (TypeSubscription(topic_type, agent_type)) or a default subscription (DefaultSubscription(agent_type)) that determines which agents receive which messages. -
MessageContext– Carries metadata including sender information, cancellation tokens, and RPC flags. -
InProcessRuntime– The C# implementation handling message queues, agent factories, and local transport plumbing. -
GrpcWorkerAgentRuntime– The concrete implementation in both languages that handles cross-process gRPC communication.
Building a Cross-Language Application
Python Agent Implementation
The Python side uses GrpcWorkerAgentRuntime to connect to a .NET host and exchange protobuf messages. Here is the implementation from hello_python_agent.py:
import asyncio, logging, os, sys
from autogen_core import (
PROTOBUF_DATA_CONTENT_TYPE,
AgentId,
DefaultSubscription,
DefaultTopicId,
TypeSubscription,
try_get_known_serializers_for_type,
)
from autogen_ext.runtimes.grpc import GrpcWorkerAgentRuntime
from protos.agent_events_pb2 import NewMessageReceived, Output # protobuf definitions
from user_input import UserProxy # tiny console UI
logging.basicConfig(level=logging.DEBUG)
log = logging.getLogger("autogen_core")
async def main() -> None:
# ① Connect to the .NET Aspire host (the gRPC endpoint)
host = os.getenv("AGENT_HOST", "http://localhost:50673")
host = host.replace("http://", "").replace("https://", "")
runtime = GrpcWorkerAgentRuntime(host_address=host,
payload_serialization_format=PROTOBUF_DATA_CONTENT_TYPE)
await runtime.start()
# Register protobuf serializers for the message types we will send/receive
runtime.add_message_serializer(try_get_known_serializers_for_type(NewMessageReceived))
runtime.add_message_serializer(try_get_known_serializers_for_type(Output))
# ② Register a *user proxy* that forwards console input to the .NET "HelloAgent"
await UserProxy.register(runtime, "HelloAgent", lambda: UserProxy())
# ③ Subscribe to the topics the .NET side will publish
await runtime.add_subscription(DefaultSubscription(agent_type="HelloAgent"))
await runtime.add_subscription(TypeSubscription(topic_type="HelloTopic", agent_type="HelloAgent"))
await runtime.add_subscription(TypeSubscription(topic_type="agents.NewMessageReceived", agent_type="HelloAgent"))
await runtime.add_subscription(TypeSubscription(topic_type="agents.ConversationClosed", agent_type="HelloAgent"))
await runtime.add_subscription(TypeSubscription(topic_type="agents.Output", agent_type="HelloAgent"))
# ④ Send a greeting to the .NET agent
await runtime.publish_message(
message=NewMessageReceived(message="Hello from Python!"),
topic_id=DefaultTopicId("HelloTopic", "HelloAgents/python"),
sender=AgentId("HelloAgents", "python"),
)
# Send a second "output" message (demonstrates protobuf payload switching)
await runtime.publish_message(
message=Output(message="*** Python says ***"),
topic_id=DefaultTopicId("HelloTopic", "HelloAgents/python"),
sender=AgentId("HelloAgents", "python"),
)
# ⑤ Keep the process alive until the .NET side signals shutdown
await runtime.stop_when_signal()
if __name__ == "__main__":
asyncio.run(main())
.NET Agent Implementation
The .NET side implements HelloAgent in HelloAgent.cs using the BaseAgent class and handles protobuf messages from Python:
using Microsoft.AutoGen.Agents;
using Microsoft.AutoGen.Contracts;
using Microsoft.AutoGen.Core;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace Samples;
[TypeSubscription("HelloTopic")]
public class HelloAgent(
IHostApplicationLifetime hostApplicationLifetime,
AgentId id,
IAgentRuntime runtime,
Logger<BaseAgent>? logger = null)
: BaseAgent(id, runtime, "Hello Agent", logger),
IHandle<NewMessageReceived>,
IHandle<ConversationClosed>,
IHandle<Shutdown>
{
// Handles the greeting received from Python
public async ValueTask HandleAsync(NewMessageReceived item, MessageContext ctx)
{
Console.Out.WriteLine(item.Message); // prints "Hello from Python!"
var goodbye = new ConversationClosed
{
UserId = this.Id.Type,
UserMessage = "Goodbye"
};
await this.PublishMessageAsync(goodbye, new TopicId("HelloTopic"));
}
// Handles the goodbye message and triggers shutdown unless overridden
public async ValueTask HandleAsync(ConversationClosed item, MessageContext ctx)
{
Console.Out.WriteLine($"{item.UserId} said {item.UserMessage}");
if (Environment.GetEnvironmentVariable("STAY_ALIVE_ON_GOODBYE") != "true")
await this.PublishMessageAsync(new Shutdown(), new TopicId("HelloTopic"));
}
// Called when a Shutdown message arrives – shuts down the Aspire host
public async ValueTask HandleAsync(Shutdown _, MessageContext __)
{
Console.WriteLine("Shutting down...");
hostApplicationLifetime.StopApplication();
}
}
Running the Integration
To run the complete cross-language demo:
# 1️⃣ Start the .NET Aspire host (the integration test AppHost)
cd autogen/dotnet/samples/Hello/Hello.AppHost
dotnet run # launches InProcessRuntime + HelloAgent
# 2️⃣ In another terminal, launch the Python side
cd autogen/dotnet/test/Microsoft.AutoGen.Integration.Tests.AppHosts/core_xlang_hello_python_agent
python hello_python_agent.py
The Python console prints the greeting while the .NET console echoes the message, demonstrating successful cross-language communication followed by a graceful shutdown sequence.
Key Source Files
Understanding the implementation requires familiarity with these specific files in the microsoft/autogen repository:
-
python/packages/autogen-core/src/autogen_core/_agent.py– Protocol definition for agents including metadata, ID, lifecycle, and message handling. -
python/packages/autogen-core/src/autogen_core/_agent_runtime.py– Core runtime interface used by both in-process and gRPC workers. -
dotnet/src/Microsoft.AutoGen/Core/InProcessRuntime.cs– C# in-process runtime implementation handling message queues, subscriptions, and agent factories. -
dotnet/src/Microsoft.AutoGen/Extensions/GrpcWorkerAgentRuntime.cs– C# gRPC transport implementation for cross-process communication. -
python/packages/autogen-ext/src/autogen_ext/runtimes/grpc.py– Python gRPC worker runtime acting as a client wrapper around the .NET gRPC service. -
dotnet/samples/Hello/HelloAgent/HelloAgent.cs– Sample .NET agent demonstratingTypeSubscriptionand protobuf message handling. -
dotnet/test/Microsoft.AutoGen.Integration.Tests.AppHosts/core_xlang_hello_python_agent/hello_python_agent.py– Sample Python agent showing gRPC connection and message publishing. -
python/samples/core_chainlit/app_agent.py– Higher-level example demonstrating Python AgentChat integration with UI frameworks.
Summary
Building cross-language .NET and Python agent applications with AutoGen relies on a language-agnostic core architecture:
- gRPC Worker Runtime enables communication between Python and .NET processes through a shared transport layer using protobuf serialization.
- AgentId and TopicId provide universal addressing that works across language boundaries.
- Subscriptions define routing rules that allow agents in one language to receive messages from agents in another.
- InProcessRuntime and GrpcWorkerAgentRuntime implement the same interface in both languages, ensuring consistent behavior whether running locally or distributed.
Frequently Asked Questions
How do agents in different languages find each other?
Agents find each other through the TopicId subscription system. When a Python agent publishes a message to a topic like "HelloTopic", the gRPC runtime routes it to any subscribed .NET agents regardless of which process they run in. The AgentId (composed of type and key) ensures unique addressing across the distributed system.
What serialization format does AutoGen use for cross-language messages?
AutoGen uses Protocol Buffers (protobuf) for cross-language serialization. The Python runtime uses PROTOBUF_DATA_CONTENT_TYPE when configuring the GrpcWorkerAgentRuntime, and both sides register message serializers using try_get_known_serializers_for_type (Python) or equivalent generated code (C#). This ensures type-safe message exchange between languages.
Can I run the Python and .NET agents on different machines?
Yes. The gRPC worker runtime architecture supports distributed deployment. The Python GrpcWorkerAgentRuntime connects to a host address (configured via AGENT_HOST environment variable), which can be a remote .NET Aspire host or another Python runtime. The transport layer handles network communication, serialization, and message routing across machine boundaries.
How do I handle graceful shutdown in a cross-language scenario?
Use the stop_when_signal() method in Python and the IHostApplicationLifetime interface in .NET. The sample demonstrates this pattern: when the .NET HelloAgent receives a Shutdown message, it calls hostApplicationLifetime.StopApplication(), which signals the Python side to exit via the stop_when_signal() awaitable, ensuring both runtimes shut down cleanly.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →